+```
+
#### `joy-rename-components-to-slots`
Renames the `components` and `componentsProps` props to `slots` and `slotProps`, respectively.
diff --git a/packages/mui-codemod/package.json b/packages/mui-codemod/package.json
index 9bd494d7295205..af0846d3ab21d0 100644
--- a/packages/mui-codemod/package.json
+++ b/packages/mui-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/codemod",
- "version": "5.11.0",
+ "version": "5.11.6",
"bin": "./codemod.js",
"private": false,
"author": "MUI Team",
diff --git a/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.js b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.js
new file mode 100644
index 00000000000000..7851a3a6e94e1f
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.js
@@ -0,0 +1,92 @@
+/**
+ * @param {import('jscodeshift').FileInfo} file
+ * @param {import('jscodeshift').API} api
+ */
+export default function transformer(file, api, options) {
+ const j = api.jscodeshift;
+ const root = j(file.source);
+ const printOptions = options.printOptions;
+
+ root
+ .find(j.ImportDeclaration)
+ .filter(({ node }) => {
+ const sourceVal = node.source.value;
+
+ return [
+ '@mui/joy', // Process only Joy UI components
+ '@mui/joy/Avatar', // Filter default imports of components other than `Avatar`
+ ].includes(sourceVal);
+ })
+ .forEach((path) => {
+ path.node.specifiers.forEach((elementNode) => {
+ if (
+ (elementNode.type === 'ImportSpecifier' && elementNode.imported?.name === 'Avatar') ||
+ elementNode.type === 'ImportDefaultSpecifier'
+ ) {
+ // Process only Joy `Avatar` component
+ root.findJSXElements(elementNode.local.name).forEach((elementPath) => {
+ if (elementPath.node.type !== 'JSXElement') {
+ return;
+ }
+
+ const slotPropsAttributeNode = elementPath.node.openingElement.attributes.find(
+ (attributeNode) =>
+ attributeNode.type === 'JSXAttribute' &&
+ attributeNode.name.name === 'slotProps' &&
+ attributeNode.value.expression?.type === 'ObjectExpression',
+ );
+ const newAttributeNodes = [];
+ elementPath.node.openingElement.attributes.forEach((attributeNode) => {
+ if (attributeNode.type !== 'JSXAttribute') {
+ return;
+ }
+
+ if (attributeNode.name.name !== 'imgProps') {
+ newAttributeNodes.push(attributeNode);
+ return;
+ }
+
+ const val = attributeNode.value;
+ if (!val?.expression) {
+ return;
+ }
+
+ if (slotPropsAttributeNode) {
+ const imgObjInSlotProps = slotPropsAttributeNode.value.expression.properties.find(
+ (propNode) =>
+ propNode.key.name === 'img' && propNode.value.type === 'ObjectExpression',
+ );
+ if (imgObjInSlotProps) {
+ const newProperties = [
+ ...imgObjInSlotProps.value.properties,
+ ...attributeNode.value.expression.properties,
+ ];
+ imgObjInSlotProps.value.properties = newProperties;
+ } else {
+ slotPropsAttributeNode.value.expression.properties.push(
+ j.objectProperty(j.identifier('img'), attributeNode.value),
+ );
+ }
+ } else {
+ newAttributeNodes.push(
+ j.jsxAttribute(
+ j.jsxIdentifier('slotProps'),
+ j.jsxExpressionContainer(
+ j.objectExpression([
+ j.objectProperty(j.identifier('img'), attributeNode.value.expression),
+ ]),
+ ),
+ ),
+ );
+ }
+ });
+ elementPath.node.openingElement.attributes = newAttributeNodes;
+ });
+ }
+ });
+ });
+
+ const transformed = root.findJSXElements();
+
+ return transformed.toSource(printOptions);
+}
diff --git a/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test.js b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test.js
new file mode 100644
index 00000000000000..cf70e5d8260e73
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test.js
@@ -0,0 +1,29 @@
+import path from 'path';
+import { expect } from 'chai';
+import jscodeshift from 'jscodeshift';
+import transform from './joy-avatar-remove-imgProps';
+import readFile from '../util/readFile';
+
+function read(fileName) {
+ return readFile(path.join(__dirname, fileName));
+}
+
+describe('@mui/codemod', () => {
+ describe('v5.0.0', () => {
+ describe('joy-avatar-remove-imgProps', () => {
+ it('transforms `imgProps` prop to `slotProps.img`', () => {
+ const actual = transform(
+ {
+ source: read('./joy-avatar-remove-imgProps.test/actual.js'),
+ path: require.resolve('./joy-rename-components-to-slots.test/actual.js'),
+ },
+ { jscodeshift },
+ {},
+ );
+
+ const expected = read('./joy-avatar-remove-imgProps.test/expected.js');
+ expect(actual).to.equal(expected, 'The transformed version should be correct');
+ });
+ });
+ });
+});
diff --git a/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test/actual.js b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test/actual.js
new file mode 100644
index 00000000000000..e7ef020909124f
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test/actual.js
@@ -0,0 +1,13 @@
+// the codemod should transform only Joy UI `Avatar`;
+import { Avatar as JoyAvatar } from '@mui/joy';
+import Avatar from '@mui/joy/Avatar';
+import MaterialAvatar from '@mui/material/Avatar';
+
+;
diff --git a/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test/expected.js b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test/expected.js
new file mode 100644
index 00000000000000..03d9bfe01e8a5d
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test/expected.js
@@ -0,0 +1,16 @@
+// the codemod should transform only Joy UI `Avatar`;
+import { Avatar as JoyAvatar } from '@mui/joy';
+import Avatar from '@mui/joy/Avatar';
+import MaterialAvatar from '@mui/material/Avatar';
+
+;
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.js b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.js
new file mode 100644
index 00000000000000..1c16dd127173ae
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.js
@@ -0,0 +1,6 @@
+/**
+ * @param {import('jscodeshift').FileInfo} file
+ */
+export default function transformer(file) {
+ return file.source.replace(/Joy([A-Z]+)/gm, 'Mui$1');
+}
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test.js b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test.js
new file mode 100644
index 00000000000000..697cab58f1132f
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test.js
@@ -0,0 +1,29 @@
+import path from 'path';
+import { expect } from 'chai';
+import jscodeshift from 'jscodeshift';
+import transform from './joy-rename-classname-prefix';
+import readFile from '../util/readFile';
+
+function read(fileName) {
+ return readFile(path.join(__dirname, fileName));
+}
+
+describe('@mui/codemod', () => {
+ describe('v5.0.0', () => {
+ describe('joy-rename-classname-prefix', () => {
+ it('transforms classname prefix from Joy to Mui', () => {
+ const actual = transform(
+ {
+ source: read('./joy-rename-classname-prefix.test/actual.js'),
+ path: require.resolve('./joy-rename-classname-prefix.test/actual.js'),
+ },
+ { jscodeshift },
+ {},
+ );
+
+ const expected = read('./joy-rename-classname-prefix.test/expected.js');
+ expect(actual).to.equal(expected, 'The transformed version should be correct');
+ });
+ });
+ });
+});
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test/actual.js b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test/actual.js
new file mode 100644
index 00000000000000..6986c1164396c1
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test/actual.js
@@ -0,0 +1,2 @@
+ ;
+ ;
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test/expected.js b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test/expected.js
new file mode 100644
index 00000000000000..1d6db2df665b4d
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-classname-prefix.test/expected.js
@@ -0,0 +1,2 @@
+ ;
+ ;
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.js b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.js
new file mode 100644
index 00000000000000..96a81787f9184f
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.js
@@ -0,0 +1,42 @@
+/**
+ * @param {import('jscodeshift').FileInfo} file
+ * @param {import('jscodeshift').API} api
+ */
+export default function transformer(file, api, options) {
+ const j = api.jscodeshift;
+ const root = j(file.source);
+ const printOptions = options.printOptions;
+
+ root
+ .find(j.ImportDeclaration)
+ .filter(({ node }) => {
+ return node.source.value.startsWith('@mui/joy');
+ })
+ .forEach((path) => {
+ path.node.specifiers.forEach((node) => {
+ // Process only Joy UI components
+ root.findJSXElements(node.local.name).forEach((elementPath) => {
+ if (elementPath.node.type !== 'JSXElement') {
+ return;
+ }
+
+ elementPath.node.openingElement.attributes.forEach((attributeNode) => {
+ if (attributeNode.type !== 'JSXAttribute') {
+ return;
+ }
+ if (attributeNode.name.name === 'row') {
+ const val = attributeNode.value;
+ if (val === null || val?.expression?.value === true) {
+ attributeNode.name.name = 'orientation';
+ attributeNode.value = j.jsxExpressionContainer(j.literal('horizontal'));
+ }
+ }
+ });
+ });
+ });
+ });
+
+ const transformed = root.findJSXElements();
+
+ return transformed.toSource(printOptions);
+}
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test.js b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test.js
new file mode 100644
index 00000000000000..6a581c78b5688c
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test.js
@@ -0,0 +1,29 @@
+import path from 'path';
+import { expect } from 'chai';
+import jscodeshift from 'jscodeshift';
+import transform from './joy-rename-row-prop';
+import readFile from '../util/readFile';
+
+function read(fileName) {
+ return readFile(path.join(__dirname, fileName));
+}
+
+describe('@mui/codemod', () => {
+ describe('v5.0.0', () => {
+ describe('joy-rename-row-prop', () => {
+ it('transforms `row` prop to `orientation="horizontal"`', () => {
+ const actual = transform(
+ {
+ source: read('./joy-rename-row-prop.test/actual.js'),
+ path: require.resolve('./joy-rename-components-to-slots.test/actual.js'),
+ },
+ { jscodeshift },
+ {},
+ );
+
+ const expected = read('./joy-rename-row-prop.test/expected.js');
+ expect(actual).to.equal(expected, 'The transformed version should be correct');
+ });
+ });
+ });
+});
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test/actual.js b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test/actual.js
new file mode 100644
index 00000000000000..b56db9e0b84d30
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test/actual.js
@@ -0,0 +1,12 @@
+// the codemod should transform only Joy UI components;
+import { List as JoyList } from '@mui/joy';
+import JoyCard from '@mui/joy/Card';
+import RadioGroup from '@mui/joy/RadioGroup';
+import CustomComponent from 'components/Custom';
+
+
+
+
+
+
+
;
diff --git a/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test/expected.js b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test/expected.js
new file mode 100644
index 00000000000000..db078b2e074766
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-rename-row-prop.test/expected.js
@@ -0,0 +1,12 @@
+// the codemod should transform only Joy UI components;
+import { List as JoyList } from '@mui/joy';
+import JoyCard from '@mui/joy/Card';
+import RadioGroup from '@mui/joy/RadioGroup';
+import CustomComponent from 'components/Custom';
+
+
+
+
+
+
+
;
diff --git a/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.js b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.js
new file mode 100644
index 00000000000000..535d2a21ada6e7
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.js
@@ -0,0 +1,210 @@
+/**
+ * @param {import('jscodeshift').FileInfo} file
+ * @param {import('jscodeshift').API} api
+ */
+export default function transformer(file, api, options) {
+ const j = api.jscodeshift;
+ const root = j(file.source);
+ const printOptions = options.printOptions;
+
+ root
+ .find(j.ImportDeclaration)
+ .filter(({ node }) => {
+ const sourceVal = node.source.value;
+ if (sourceVal === '@mui/joy/TextField') {
+ node.source.value = '@mui/joy/Input';
+ }
+
+ return [
+ '@mui/joy', // Process only Joy UI components
+ '@mui/joy/TextField', // Filter default imports of components other than TextField
+ ].includes(sourceVal);
+ })
+ .forEach((path) => {
+ path.node.specifiers.forEach((elementNode) => {
+ if (
+ (elementNode.type === 'ImportSpecifier' && elementNode.imported?.name === 'TextField') ||
+ elementNode.type === 'ImportDefaultSpecifier'
+ ) {
+ if (elementNode.imported?.name === 'TextField') {
+ elementNode.imported.name = 'Input';
+ }
+
+ let newElementName;
+ root.findJSXElements(elementNode.local.name).forEach((elementPath) => {
+ if (elementPath.node.type !== 'JSXElement') {
+ return;
+ }
+ newElementName = elementPath.node.openingElement.name.name.replace(
+ /TextField/gm,
+ 'Input',
+ );
+ elementPath.node.openingElement.name.name = newElementName;
+
+ const formControlAttributeNodes = [];
+ const formLabelAttributeNodes = [];
+ const formHelperTextAttributeNodes = [];
+ const inputAttributeNodes = [];
+ let formLabelValue;
+ let formHelperTextValue;
+
+ elementPath.node.openingElement.attributes.forEach((attributeNode) => {
+ if (attributeNode.type !== 'JSXAttribute') {
+ return;
+ }
+ const attributeName = attributeNode.name.name;
+ switch (attributeName) {
+ case 'size':
+ case 'color':
+ case 'required':
+ formControlAttributeNodes.push(attributeNode);
+ break;
+
+ case 'slotProps':
+ if (attributeNode.value.expression?.type === 'ObjectExpression') {
+ attributeNode.value.expression.properties.forEach((propNode) => {
+ if (propNode.value.type !== 'ObjectExpression') {
+ return;
+ }
+ propNode.value.properties.forEach((prop) => {
+ const key = prop.key.value;
+ const newAttributeNode = j.jsxAttribute(
+ j.jsxIdentifier(key),
+ j.jsxExpressionContainer(prop.value),
+ );
+ switch (propNode.key.name) {
+ case 'root':
+ formControlAttributeNodes.push(newAttributeNode);
+ break;
+ case 'label':
+ formLabelAttributeNodes.push(newAttributeNode);
+ break;
+ case 'input':
+ inputAttributeNodes.push(newAttributeNode);
+ break;
+ case 'helperText':
+ formHelperTextAttributeNodes.push(newAttributeNode);
+ break;
+ default:
+ }
+ });
+ });
+ }
+ break;
+
+ case 'slots':
+ if (attributeNode.value.expression?.type === 'ObjectExpression') {
+ attributeNode.value.expression.properties.forEach((propNode) => {
+ const newAttributeNode = j.jsxAttribute(
+ j.jsxIdentifier('component'),
+ j.jsxExpressionContainer(propNode.value),
+ );
+ switch (propNode.key.name) {
+ case 'root':
+ formControlAttributeNodes.push(newAttributeNode);
+ break;
+ case 'label':
+ formLabelAttributeNodes.push(newAttributeNode);
+ break;
+ case 'input':
+ inputAttributeNodes.push(newAttributeNode);
+ break;
+ case 'helperText':
+ formHelperTextAttributeNodes.push(newAttributeNode);
+ break;
+ default:
+ }
+ });
+ }
+ break;
+
+ case 'label':
+ formLabelValue = attributeNode.value.value;
+ break;
+
+ case 'helperText':
+ formHelperTextValue = attributeNode.value.value;
+ break;
+
+ case 'id':
+ formControlAttributeNodes.push(attributeNode);
+ formLabelAttributeNodes.push(
+ j.jsxAttribute(
+ j.jsxIdentifier('id'),
+ j.literal(`${attributeNode.value.value}-label`),
+ ),
+ );
+ formHelperTextAttributeNodes.push(
+ j.jsxAttribute(
+ j.jsxIdentifier('id'),
+ j.literal(`${attributeNode.value.value}-helper-text`),
+ ),
+ );
+ break;
+
+ default:
+ }
+ if (
+ ![
+ 'size',
+ 'color',
+ 'slotProps',
+ 'slots',
+ 'label',
+ 'helperText',
+ 'id',
+ 'required',
+ ].includes(attributeName)
+ ) {
+ inputAttributeNodes.push(attributeNode);
+ }
+ });
+
+ elementPath.node.openingElement.attributes = inputAttributeNodes;
+
+ if (formControlAttributeNodes.length > 0 || formLabelValue || formHelperTextValue) {
+ const formControlIdentifier = j.jsxIdentifier('FormControl');
+ const childrenOfFormControl = [];
+
+ if (formLabelValue) {
+ const formLabelIdentifier = j.jsxIdentifier('FormLabel');
+ const formLabelElement = j.jsxElement(
+ j.jsxOpeningElement(formLabelIdentifier, formLabelAttributeNodes),
+ j.jsxClosingElement(formLabelIdentifier),
+ [j.jsxText('\n'), j.jsxText(formLabelValue), j.jsxText('\n')],
+ );
+ childrenOfFormControl.push(formLabelElement, j.jsxText('\n'));
+ }
+
+ childrenOfFormControl.push(elementPath.node, j.jsxText('\n'));
+
+ if (formHelperTextValue) {
+ const formHelperTextIdentifier = j.jsxIdentifier('FormHelperText');
+ const formHelperTextElement = j.jsxElement(
+ j.jsxOpeningElement(formHelperTextIdentifier, formHelperTextAttributeNodes),
+ j.jsxClosingElement(formHelperTextIdentifier),
+ [j.jsxText('\n'), j.jsxText(formHelperTextValue), j.jsxText('\n')],
+ );
+ childrenOfFormControl.push(formHelperTextElement);
+ }
+
+ elementPath.replace(
+ j.jsxElement(
+ j.jsxOpeningElement(formControlIdentifier, formControlAttributeNodes),
+ j.jsxClosingElement(formControlIdentifier),
+ [j.jsxText('\n'), ...childrenOfFormControl, j.jsxText('\n')],
+ ),
+ );
+ }
+ });
+ if (newElementName) {
+ elementNode.local.name = newElementName;
+ }
+ }
+ });
+ });
+
+ const transformed = root.findJSXElements();
+
+ return transformed.toSource(printOptions);
+}
diff --git a/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test.js b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test.js
new file mode 100644
index 00000000000000..ab59c68ccc8d24
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test.js
@@ -0,0 +1,29 @@
+import path from 'path';
+import { expect } from 'chai';
+import jscodeshift from 'jscodeshift';
+import transform from './joy-text-field-to-input';
+import readFile from '../util/readFile';
+
+function read(fileName) {
+ return readFile(path.join(__dirname, fileName));
+}
+
+describe('@mui/codemod', () => {
+ describe('v5.0.0', () => {
+ describe('joy-text-field-to-input', () => {
+ it('transform Joy TextField into Joy Input', () => {
+ const actual = transform(
+ {
+ source: read('./joy-text-field-to-input.test/actual.js'),
+ path: require.resolve('./joy-text-field-to-input.test/actual.js'),
+ },
+ { jscodeshift },
+ {},
+ );
+
+ const expected = read('./joy-text-field-to-input.test/expected.js');
+ expect(actual).to.equal(expected, 'The transformed version should be correct');
+ });
+ });
+ });
+});
diff --git a/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test/actual.js b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test/actual.js
new file mode 100644
index 00000000000000..de9f675f85629a
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test/actual.js
@@ -0,0 +1,38 @@
+import { TextField as JoyTextField } from "@mui/joy";
+import JoyTextField2 from "@mui/joy/TextField";
+// the codemod should transform only Joy TextField
+import TextField from "@mui/material/TextField"
+
+
+
+
+
+
;
diff --git a/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test/expected.js b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test/expected.js
new file mode 100644
index 00000000000000..dcbb41e1cab31e
--- /dev/null
+++ b/packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.test/expected.js
@@ -0,0 +1,35 @@
+import { Input as JoyInput } from "@mui/joy";
+import JoyInput2 from "@mui/joy/Input";
+// the codemod should transform only Joy TextField
+import TextField from "@mui/material/TextField"
+
+
+
+
+ Label
+
+
+
+ Help!
+
+
+
+
+
;
diff --git a/packages/mui-core-downloads-tracker/package.json b/packages/mui-core-downloads-tracker/package.json
index cd798e3e1f876d..366b698077a370 100644
--- a/packages/mui-core-downloads-tracker/package.json
+++ b/packages/mui-core-downloads-tracker/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/core-downloads-tracker",
- "version": "5.11.3",
+ "version": "5.11.6",
"private": false,
"author": "MUI Team",
"description": "Internal package to track number of downloads of our design system libraries",
diff --git a/packages/mui-joy/package.json b/packages/mui-joy/package.json
index e0c3036f78c71a..98f452d6980e7a 100644
--- a/packages/mui-joy/package.json
+++ b/packages/mui-joy/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/joy",
- "version": "5.0.0-alpha.61",
+ "version": "5.0.0-alpha.64",
"private": false,
"author": "MUI Team",
"description": "A library of beautifully designed React UI components.",
@@ -57,9 +57,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.7",
- "@mui/base": "5.0.0-alpha.112",
- "@mui/core-downloads-tracker": "^5.11.3",
- "@mui/system": "^5.11.2",
+ "@mui/base": "5.0.0-alpha.115",
+ "@mui/core-downloads-tracker": "^5.11.6",
+ "@mui/system": "^5.11.5",
"@mui/types": "^7.2.3",
"@mui/utils": "^5.11.2",
"clsx": "^1.2.1",
diff --git a/packages/mui-joy/src/Alert/alertClasses.ts b/packages/mui-joy/src/Alert/alertClasses.ts
index d2ee0ef9063024..c274e49c2b1237 100644
--- a/packages/mui-joy/src/Alert/alertClasses.ts
+++ b/packages/mui-joy/src/Alert/alertClasses.ts
@@ -40,10 +40,10 @@ export interface AlertClasses {
export type AlertClassKey = keyof AlertClasses;
export function getAlertUtilityClass(slot: string): string {
- return generateUtilityClass('JoyAlert', slot);
+ return generateUtilityClass('MuiAlert', slot);
}
-const alertClasses: AlertClasses = generateUtilityClasses('JoyAlert', [
+const alertClasses: AlertClasses = generateUtilityClasses('MuiAlert', [
'root',
'startDecorator',
'endDecorator',
diff --git a/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts b/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts
index fb6bbd46be099f..dad63ab18305fe 100644
--- a/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts
+++ b/packages/mui-joy/src/AspectRatio/aspectRatioClasses.ts
@@ -32,10 +32,10 @@ export interface AspectRatioClasses {
export type AspectRatioClassKey = keyof AspectRatioClasses;
export function getAspectRatioUtilityClass(slot: string): string {
- return generateUtilityClass('JoyAspectRatio', slot);
+ return generateUtilityClass('MuiAspectRatio', slot);
}
-const aspectRatioClasses: AspectRatioClasses = generateUtilityClasses('JoyAspectRatio', [
+const aspectRatioClasses: AspectRatioClasses = generateUtilityClasses('MuiAspectRatio', [
'root',
'content',
'colorPrimary',
diff --git a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts
index 22ced03f1bc388..b89e4badf388c0 100644
--- a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts
+++ b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts
@@ -74,10 +74,10 @@ export interface AutocompleteClasses {
export type AutocompleteClassKey = keyof AutocompleteClasses;
export function getAutocompleteUtilityClass(slot: string): string {
- return generateUtilityClass('JoyAutocomplete', slot);
+ return generateUtilityClass('MuiAutocomplete', slot);
}
-const autocompleteClasses: AutocompleteClasses = generateUtilityClasses('JoyAutocomplete', [
+const autocompleteClasses: AutocompleteClasses = generateUtilityClasses('MuiAutocomplete', [
'root',
'wrapper',
'input',
diff --git a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts
index a8a348abcaa19a..3c93fafebbadc9 100644
--- a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts
+++ b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts
@@ -36,11 +36,11 @@ export interface AutocompleteListboxClasses {
export type AutocompleteListboxClassKey = keyof AutocompleteListboxClasses;
export function getAutocompleteListboxUtilityClass(slot: string): string {
- return generateUtilityClass('JoyAutocompleteListbox', slot);
+ return generateUtilityClass('MuiAutocompleteListbox', slot);
}
const autocompleteListboxClasses: AutocompleteListboxClasses = generateUtilityClasses(
- 'JoyAutocompleteListbox',
+ 'MuiAutocompleteListbox',
[
'root',
'sizeSm',
diff --git a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts
index f393cf0e6e91c9..6974d494fc9ee5 100644
--- a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts
+++ b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts
@@ -34,11 +34,11 @@ export interface AutocompleteOptionClasses {
export type AutocompleteOptionClassKey = keyof AutocompleteOptionClasses;
export function getAutocompleteOptionUtilityClass(slot: string): string {
- return generateUtilityClass('JoyAutocompleteOption', slot);
+ return generateUtilityClass('MuiAutocompleteOption', slot);
}
const autocompleteOptionClasses: AutocompleteOptionClasses = generateUtilityClasses(
- 'JoyAutocompleteOption',
+ 'MuiAutocompleteOption',
[
'root',
'focused',
diff --git a/packages/mui-joy/src/Avatar/Avatar.tsx b/packages/mui-joy/src/Avatar/Avatar.tsx
index ac99fdfed9a804..380604ed179d08 100644
--- a/packages/mui-joy/src/Avatar/Avatar.tsx
+++ b/packages/mui-joy/src/Avatar/Avatar.tsx
@@ -150,7 +150,6 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {
color: colorProp = 'neutral',
size: sizeProp = 'md',
variant: variantProp = 'soft',
- imgProps,
src,
srcSet,
children: childrenProp,
@@ -186,7 +185,6 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {
alt,
src,
srcSet,
- ...imgProps,
},
className: classes.img,
elementType: AvatarImg,
@@ -203,7 +201,6 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {
// Use a hook instead of onError on the img element to support server-side rendering.
const loaded = useLoaded({
- ...imgProps,
...imageProps,
src,
srcSet,
@@ -248,11 +245,6 @@ Avatar.propTypes /* remove-proptypes */ = {
PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']),
PropTypes.string,
]),
- /**
- * [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes) applied to the `img` element if the component is used to display an image.
- * It can be used to listen for the loading error event.
- */
- imgProps: PropTypes.object,
/**
* The size of the component.
* It accepts theme values between 'sm' and 'lg'.
diff --git a/packages/mui-joy/src/Avatar/AvatarProps.ts b/packages/mui-joy/src/Avatar/AvatarProps.ts
index 8f211b7444e4ca..3b595d6528f8e9 100644
--- a/packages/mui-joy/src/Avatar/AvatarProps.ts
+++ b/packages/mui-joy/src/Avatar/AvatarProps.ts
@@ -36,13 +36,6 @@ export interface AvatarTypeMap {
* @default 'neutral'
*/
color?: OverridableStringUnion;
- /**
- * [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes) applied to the `img` element if the component is used to display an image.
- * It can be used to listen for the loading error event.
- */
- imgProps?: React.ImgHTMLAttributes & {
- sx?: SxProps;
- };
/**
* The size of the component.
* It accepts theme values between 'sm' and 'lg'.
diff --git a/packages/mui-joy/src/Avatar/avatarClasses.ts b/packages/mui-joy/src/Avatar/avatarClasses.ts
index 2094f9c82dcb8e..c162a2df49b002 100644
--- a/packages/mui-joy/src/Avatar/avatarClasses.ts
+++ b/packages/mui-joy/src/Avatar/avatarClasses.ts
@@ -38,10 +38,10 @@ export interface AvatarClasses {
export type AvatarClassKey = keyof AvatarClasses;
export function getAvatarUtilityClass(slot: string): string {
- return generateUtilityClass('JoyAvatar', slot);
+ return generateUtilityClass('MuiAvatar', slot);
}
-const avatarClasses: AvatarClasses = generateUtilityClasses('JoyAvatar', [
+const avatarClasses: AvatarClasses = generateUtilityClasses('MuiAvatar', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/AvatarGroup/avatarGroupClasses.ts b/packages/mui-joy/src/AvatarGroup/avatarGroupClasses.ts
index 3ac0acdd62d454..5b30e3efc4ea0b 100644
--- a/packages/mui-joy/src/AvatarGroup/avatarGroupClasses.ts
+++ b/packages/mui-joy/src/AvatarGroup/avatarGroupClasses.ts
@@ -8,9 +8,9 @@ export interface AvatarGroupClasses {
export type AvatarGroupClassKey = keyof AvatarGroupClasses;
export function getAvatarGroupUtilityClass(slot: string): string {
- return generateUtilityClass('JoyAvatarGroup', slot);
+ return generateUtilityClass('MuiAvatarGroup', slot);
}
-const avatarGroupClasses: AvatarGroupClasses = generateUtilityClasses('JoyAvatarGroup', ['root']);
+const avatarGroupClasses: AvatarGroupClasses = generateUtilityClasses('MuiAvatarGroup', ['root']);
export default avatarGroupClasses;
diff --git a/packages/mui-joy/src/Badge/Badge.tsx b/packages/mui-joy/src/Badge/Badge.tsx
index 69d553c1292e0f..d7c4230dbe84c7 100644
--- a/packages/mui-joy/src/Badge/Badge.tsx
+++ b/packages/mui-joy/src/Badge/Badge.tsx
@@ -163,7 +163,7 @@ const Badge = React.forwardRef(function Badge(inProps, ref) {
badgeInset: badgeInsetProp,
color: colorProp,
variant: variantProp,
- }) as BadgeProps;
+ });
let invisible = invisibleProp;
diff --git a/packages/mui-joy/src/Badge/badgeClasses.ts b/packages/mui-joy/src/Badge/badgeClasses.ts
index 1f1a2d8e6c914e..88a07b1b267e52 100644
--- a/packages/mui-joy/src/Badge/badgeClasses.ts
+++ b/packages/mui-joy/src/Badge/badgeClasses.ts
@@ -52,10 +52,10 @@ export interface BadgeClasses {
export type BadgeClassKey = keyof BadgeClasses;
export function getBadgeUtilityClass(slot: string): string {
- return generateUtilityClass('JoyBadge', slot);
+ return generateUtilityClass('MuiBadge', slot);
}
-const badgeClasses: BadgeClasses = generateUtilityClasses('JoyBadge', [
+const badgeClasses: BadgeClasses = generateUtilityClasses('MuiBadge', [
'root',
'badge',
'anchorOriginTopRight',
diff --git a/packages/mui-joy/src/Box/Box.tsx b/packages/mui-joy/src/Box/Box.tsx
index 9192e6a906140a..bd02093c87009e 100644
--- a/packages/mui-joy/src/Box/Box.tsx
+++ b/packages/mui-joy/src/Box/Box.tsx
@@ -1,15 +1,14 @@
import { createBox } from '@mui/system';
import PropTypes from 'prop-types';
-import { OverridableComponent } from '@mui/types';
import { unstable_ClassNameGenerator as ClassNameGenerator } from '../className';
-import { BoxTypeMap } from './BoxProps';
+import { Theme } from '../styles/types';
import defaultTheme from '../styles/defaultTheme';
-const Box = createBox({
+const Box = createBox({
defaultTheme,
defaultClassName: 'JoyBox-root',
generateClassName: ClassNameGenerator.generate,
-}) as OverridableComponent;
+});
Box.propTypes /* remove-proptypes */ = {
// ----------------------------- Warning --------------------------------
diff --git a/packages/mui-joy/src/Box/BoxProps.ts b/packages/mui-joy/src/Box/BoxProps.ts
index 090eab4d5c75f7..95b1c4a0241377 100644
--- a/packages/mui-joy/src/Box/BoxProps.ts
+++ b/packages/mui-joy/src/Box/BoxProps.ts
@@ -1,28 +1,10 @@
import { OverrideProps } from '@mui/types';
-import { SxProps, SystemProps } from '@mui/system';
+import { BoxTypeMap } from '@mui/system';
import { Theme } from '../styles/types';
export type BoxSlot = 'root';
-export interface BoxTypeMap {
- props: P &
- SystemProps & {
- children?: React.ReactNode;
- /**
- * The component used for the root node.
- * Either a string to use a HTML element or a component.
- */
- component?: React.ElementType;
- ref?: React.Ref;
- /**
- * The system prop that allows defining system overrides as well as additional CSS styles.
- */
- sx?: SxProps;
- };
- defaultComponent: D;
-}
-
export type BoxProps<
D extends React.ElementType = BoxTypeMap['defaultComponent'],
P = {},
-> = OverrideProps, D>;
+> = OverrideProps, D>;
diff --git a/packages/mui-joy/src/Button/buttonClasses.ts b/packages/mui-joy/src/Button/buttonClasses.ts
index a47e6f895db806..08c119f4be9b22 100644
--- a/packages/mui-joy/src/Button/buttonClasses.ts
+++ b/packages/mui-joy/src/Button/buttonClasses.ts
@@ -50,10 +50,10 @@ export interface ButtonClasses {
export type ButtonClassKey = keyof ButtonClasses;
export function getButtonUtilityClass(slot: string): string {
- return generateUtilityClass('JoyButton', slot);
+ return generateUtilityClass('MuiButton', slot);
}
-const buttonClasses: ButtonClasses = generateUtilityClasses('JoyButton', [
+const buttonClasses: ButtonClasses = generateUtilityClasses('MuiButton', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/Card/Card.tsx b/packages/mui-joy/src/Card/Card.tsx
index 1d35f6c933ff24..028f80fc0e0eff 100644
--- a/packages/mui-joy/src/Card/Card.tsx
+++ b/packages/mui-joy/src/Card/Card.tsx
@@ -16,15 +16,15 @@ import { resolveSxValue } from '../styles/styleUtils';
import { CardRowContext } from './CardContext';
const useUtilityClasses = (ownerState: CardOwnerState) => {
- const { size, variant, color, row } = ownerState;
+ const { size, variant, color, orientation } = ownerState;
const slots = {
root: [
'root',
+ orientation,
variant && `variant${capitalize(variant)}`,
color && `color${capitalize(color)}`,
size && `size${capitalize(size)}`,
- row && 'row',
],
};
@@ -80,7 +80,7 @@ const CardRoot = styled('div', {
transition: 'box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
position: 'relative',
display: 'flex',
- flexDirection: ownerState.row ? 'row' : 'column',
+ flexDirection: ownerState.orientation === 'horizontal' ? 'row' : 'column',
},
theme.variants[ownerState.variant!]?.[ownerState.color!],
ownerState.color !== 'context' &&
@@ -102,7 +102,7 @@ const Card = React.forwardRef(function Card(inProps, ref) {
size = 'md',
variant = 'plain',
children,
- row = false,
+ orientation = 'vertical',
...other
} = props;
const { getColor } = useColorInversion(variant);
@@ -112,7 +112,7 @@ const Card = React.forwardRef(function Card(inProps, ref) {
...props,
color,
component,
- row,
+ orientation,
size,
variant,
};
@@ -120,7 +120,7 @@ const Card = React.forwardRef(function Card(inProps, ref) {
const classes = useUtilityClasses(ownerState);
const result = (
-
+
{
*/
invertedColors?: boolean;
/**
- * If `true`, flex direction is set to 'row'.
- * @default false
+ * The component orientation.
+ * @default 'vertical'
*/
- row?: boolean;
+ orientation?: 'horizontal' | 'vertical';
/**
* The size of the component.
* It accepts theme values between 'sm' and 'lg'.
diff --git a/packages/mui-joy/src/Card/cardClasses.ts b/packages/mui-joy/src/Card/cardClasses.ts
index 6fae87bdfe765f..e4e54e9130168c 100644
--- a/packages/mui-joy/src/Card/cardClasses.ts
+++ b/packages/mui-joy/src/Card/cardClasses.ts
@@ -31,17 +31,19 @@ export interface CardClasses {
sizeMd: string;
/** Styles applied to the root element if `size="lg"`. */
sizeLg: string;
- /** Styles applied to the root element if `row={true}`. */
- row: string;
+ /** Styles applied to the root element if `orientation="horizontal"`. */
+ horizontal: string;
+ /** Styles applied to the root element if `orientation="vertical"`. */
+ vertical: string;
}
export type CardClassKey = keyof CardClasses;
export function getCardUtilityClass(slot: string): string {
- return generateUtilityClass('JoyCard', slot);
+ return generateUtilityClass('MuiCard', slot);
}
-const cardClasses: CardClasses = generateUtilityClasses('JoyCard', [
+const cardClasses: CardClasses = generateUtilityClasses('MuiCard', [
'root',
'colorPrimary',
'colorNeutral',
@@ -57,7 +59,8 @@ const cardClasses: CardClasses = generateUtilityClasses('JoyCard', [
'sizeSm',
'sizeMd',
'sizeLg',
- 'row',
+ 'horizontal',
+ 'vertical',
]);
export default cardClasses;
diff --git a/packages/mui-joy/src/CardContent/cardContentClasses.ts b/packages/mui-joy/src/CardContent/cardContentClasses.ts
index 0cda1a38dd9bd9..1470f035a9ee9a 100644
--- a/packages/mui-joy/src/CardContent/cardContentClasses.ts
+++ b/packages/mui-joy/src/CardContent/cardContentClasses.ts
@@ -8,9 +8,9 @@ export interface CardContentClasses {
export type CardContentClassKey = keyof CardContentClasses;
export function getCardContentUtilityClass(slot: string): string {
- return generateUtilityClass('JoyCardContent', slot);
+ return generateUtilityClass('MuiCardContent', slot);
}
-const cardClasses: CardContentClasses = generateUtilityClasses('JoyCardContent', ['root']);
+const cardClasses: CardContentClasses = generateUtilityClasses('MuiCardContent', ['root']);
export default cardClasses;
diff --git a/packages/mui-joy/src/CardCover/CardCover.tsx b/packages/mui-joy/src/CardCover/CardCover.tsx
index 71f1e2be14e877..b961093edc6697 100644
--- a/packages/mui-joy/src/CardCover/CardCover.tsx
+++ b/packages/mui-joy/src/CardCover/CardCover.tsx
@@ -25,8 +25,8 @@ const CardCoverRoot = styled('div', {
zIndex: 0,
top: 0,
left: 0,
- width: '100%',
- height: '100%',
+ right: 0,
+ bottom: 0,
borderRadius: 'var(--CardCover-radius)',
// use data-attribute instead of :first-child to support zero config SSR (emotion)
// use nested selector for integrating with nextjs image `fill` layout (spans are inserted on top of the img)
diff --git a/packages/mui-joy/src/CardCover/cardCoverClasses.ts b/packages/mui-joy/src/CardCover/cardCoverClasses.ts
index 382f467d22b39b..f6e324435740ef 100644
--- a/packages/mui-joy/src/CardCover/cardCoverClasses.ts
+++ b/packages/mui-joy/src/CardCover/cardCoverClasses.ts
@@ -8,9 +8,9 @@ export interface CardCoverClasses {
export type CardCoverClassKey = keyof CardCoverClasses;
export function getCardCoverUtilityClass(slot: string): string {
- return generateUtilityClass('JoyCardCover', slot);
+ return generateUtilityClass('MuiCardCover', slot);
}
-const cardCoverClasses: CardCoverClasses = generateUtilityClasses('JoyCardCover', ['root']);
+const cardCoverClasses: CardCoverClasses = generateUtilityClasses('MuiCardCover', ['root']);
export default cardCoverClasses;
diff --git a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts
index 14f85734a87023..e50de4cf97c99d 100644
--- a/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts
+++ b/packages/mui-joy/src/CardOverflow/cardOverflowClasses.ts
@@ -30,10 +30,10 @@ export interface CardOverflowClasses {
export type CardOverflowClassKey = keyof CardOverflowClasses;
export function getCardOverflowUtilityClass(slot: string): string {
- return generateUtilityClass('JoyCardOverflow', slot);
+ return generateUtilityClass('MuiCardOverflow', slot);
}
-const aspectRatioClasses: CardOverflowClasses = generateUtilityClasses('JoyCardOverflow', [
+const aspectRatioClasses: CardOverflowClasses = generateUtilityClasses('MuiCardOverflow', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/Checkbox/Checkbox.test.js b/packages/mui-joy/src/Checkbox/Checkbox.test.js
index 48731c34a5c7a9..61c396d2840a17 100644
--- a/packages/mui-joy/src/Checkbox/Checkbox.test.js
+++ b/packages/mui-joy/src/Checkbox/Checkbox.test.js
@@ -9,6 +9,7 @@ import {
} from 'test/utils';
import Checkbox, { checkboxClasses as classes } from '@mui/joy/Checkbox';
import { ThemeProvider } from '@mui/joy/styles';
+import CloseIcon from '../internal/svg-icons/Close';
describe(' ', () => {
const { render } = createRenderer();
@@ -142,4 +143,29 @@ describe(' ', () => {
expect(getByRole('checkbox')).to.have.attribute('aria-checked', 'mixed');
});
});
+
+ describe('icon', () => {
+ it('should render an indeterminate icon when both checked and indeterminate is true', () => {
+ const { getByTestId } = render( );
+ expect(getByTestId('HorizontalRuleIcon')).not.to.equal(null);
+ });
+ it('should render checked icon', () => {
+ const { getByTestId } = render( );
+ expect(getByTestId('CheckIcon')).not.to.equal(null);
+ });
+
+ it('should render unchecked icon', () => {
+ const { getByTestId } = render( } />);
+ expect(getByTestId('CloseIcon')).not.to.equal(null);
+ });
+ it('should not render icon', () => {
+ const { queryByTestId } = render(
+ } />,
+ );
+
+ expect(queryByTestId('CheckIcon')).to.equal(null);
+ expect(queryByTestId('CloseIcon')).to.equal(null);
+ expect(queryByTestId('HorizontalRuleIcon')).to.equal(null);
+ });
+ });
});
diff --git a/packages/mui-joy/src/Checkbox/Checkbox.tsx b/packages/mui-joy/src/Checkbox/Checkbox.tsx
index f8a82b7786d8ef..c0ff29800c711d 100644
--- a/packages/mui-joy/src/Checkbox/Checkbox.tsx
+++ b/packages/mui-joy/src/Checkbox/Checkbox.tsx
@@ -82,35 +82,41 @@ const CheckboxCheckbox = styled('span', {
name: 'JoyCheckbox',
slot: 'Checkbox',
overridesResolver: (props, styles) => styles.checkbox,
-})<{ ownerState: CheckboxOwnerState }>(({ theme, ownerState }) => [
- {
- boxSizing: 'border-box',
- borderRadius: theme.vars.radius.xs,
- width: 'var(--Checkbox-size)',
- height: 'var(--Checkbox-size)',
- display: 'inline-flex',
- justifyContent: 'center',
- alignItems: 'center',
- flexShrink: 0,
- // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button.
- transition:
- 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
- ...(ownerState.disableIcon && {
- display: 'contents',
- }),
- },
- ...(!ownerState.disableIcon
- ? [
- theme.variants[ownerState.variant!]?.[ownerState.color!],
- { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] },
- { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] },
- {
- [`&.${checkboxClasses.disabled}`]:
- theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!],
- },
- ]
- : []),
-]);
+})<{ ownerState: CheckboxOwnerState }>(({ theme, ownerState }) => {
+ const variantStyle = theme.variants[`${ownerState.variant!}`]?.[ownerState.color!];
+ return [
+ {
+ boxSizing: 'border-box',
+ borderRadius: theme.vars.radius.xs,
+ width: 'var(--Checkbox-size)',
+ height: 'var(--Checkbox-size)',
+ display: 'inline-flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexShrink: 0,
+ // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button.
+ transition:
+ 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
+ ...(ownerState.disableIcon && {
+ display: 'contents',
+ }),
+ },
+ ...(!ownerState.disableIcon
+ ? [
+ {
+ ...variantStyle,
+ backgroundColor: variantStyle?.backgroundColor ?? theme.vars.palette.background.surface,
+ },
+ { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] },
+ { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] },
+ {
+ [`&.${checkboxClasses.disabled}`]:
+ theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!],
+ },
+ ]
+ : []),
+ ];
+});
const CheckboxAction = styled('span', {
name: 'JoyCheckbox',
@@ -122,10 +128,10 @@ const CheckboxAction = styled('span', {
ownerState.overlay ? 'var(--internal-action-radius, inherit)' : 'inherit'
})`,
position: 'absolute',
- top: 0,
- left: 0,
- bottom: 0,
- right: 0,
+ top: 'calc(-1 * var(--variant-borderWidth, 0px))', // clickable on the border and focus outline does not move when checked/unchecked
+ left: 'calc(-1 * var(--variant-borderWidth, 0px))',
+ bottom: 'calc(-1 * var(--variant-borderWidth, 0px))',
+ right: 'calc(-1 * var(--variant-borderWidth, 0px))',
zIndex: 1, // The action element usually cover the area of nearest positioned parent
// TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button.
transition:
@@ -319,15 +325,23 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) {
ownerState,
});
+ let icon = uncheckedIcon;
+
+ if (disableIcon) {
+ icon = null;
+ } else if (indeterminate) {
+ icon = indeterminateIcon;
+ } else if (checked) {
+ icon = checkedIcon;
+ }
+
return (
- {indeterminate && !checked && !disableIcon && indeterminateIcon}
- {checked && !disableIcon && checkedIcon}
- {!checked && !disableIcon && !indeterminate && uncheckedIcon}
+ {icon}
{label && (
diff --git a/packages/mui-joy/src/Checkbox/checkboxClasses.ts b/packages/mui-joy/src/Checkbox/checkboxClasses.ts
index 0d612a729a6759..25e85b87ce7fb4 100644
--- a/packages/mui-joy/src/Checkbox/checkboxClasses.ts
+++ b/packages/mui-joy/src/Checkbox/checkboxClasses.ts
@@ -50,10 +50,10 @@ export interface CheckboxClasses {
export type CheckboxClassKey = keyof CheckboxClasses;
export function getCheckboxUtilityClass(slot: string): string {
- return generateUtilityClass('JoyCheckbox', slot);
+ return generateUtilityClass('MuiCheckbox', slot);
}
-const checkboxClasses: CheckboxClasses = generateUtilityClasses('JoyCheckbox', [
+const checkboxClasses: CheckboxClasses = generateUtilityClasses('MuiCheckbox', [
'root',
'checkbox',
'action',
diff --git a/packages/mui-joy/src/Chip/chipClasses.ts b/packages/mui-joy/src/Chip/chipClasses.ts
index 53ecbf73b7d765..0283a98aa5a74f 100644
--- a/packages/mui-joy/src/Chip/chipClasses.ts
+++ b/packages/mui-joy/src/Chip/chipClasses.ts
@@ -52,10 +52,10 @@ export interface ChipClasses {
export type ChipClassKey = keyof ChipClasses;
export function getChipUtilityClass(slot: string): string {
- return generateUtilityClass('JoyChip', slot);
+ return generateUtilityClass('MuiChip', slot);
}
-const chipClasses: ChipClasses = generateUtilityClasses('JoyChip', [
+const chipClasses: ChipClasses = generateUtilityClasses('MuiChip', [
'root',
'clickable',
'colorPrimary',
diff --git a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts
index da156ca19badc4..afbff52cbd17f0 100644
--- a/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts
+++ b/packages/mui-joy/src/ChipDelete/chipDeleteClasses.ts
@@ -32,10 +32,10 @@ export interface ChipDeleteClasses {
}
export function getChipDeleteUtilityClass(slot: string): string {
- return generateUtilityClass('JoyChipDelete', slot);
+ return generateUtilityClass('MuiChipDelete', slot);
}
-const chipDeleteClasses: ChipDeleteClasses = generateUtilityClasses('JoyChipDelete', [
+const chipDeleteClasses: ChipDeleteClasses = generateUtilityClasses('MuiChipDelete', [
'root',
'disabled',
'focusVisible',
diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx
index f239e32603fa20..48c6984e7077f7 100644
--- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx
+++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx
@@ -102,6 +102,7 @@ const CircularProgressRoot = styled('span', {
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
+ flexShrink: 0, // prevent from shrinking when CircularProgress is in a flex container.
position: 'relative',
color,
...(ownerState.children && {
diff --git a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts
index deec0ccf079ea6..f1da43b9e4ed54 100644
--- a/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts
+++ b/packages/mui-joy/src/CircularProgress/circularProgressClasses.ts
@@ -44,11 +44,11 @@ export interface CircularProgressClasses {
export type CircularProgressClassKey = keyof CircularProgressClasses;
export function getCircularProgressUtilityClass(slot: string): string {
- return generateUtilityClass('JoyCircularProgress', slot);
+ return generateUtilityClass('MuiCircularProgress', slot);
}
const circularProgressClasses: CircularProgressClasses = generateUtilityClasses(
- 'JoyCircularProgress',
+ 'MuiCircularProgress',
[
'root',
'determinate',
diff --git a/packages/mui-joy/src/Container/containerClasses.ts b/packages/mui-joy/src/Container/containerClasses.ts
index cc6ae505c27b0f..d2d5065caa00ac 100644
--- a/packages/mui-joy/src/Container/containerClasses.ts
+++ b/packages/mui-joy/src/Container/containerClasses.ts
@@ -5,10 +5,10 @@ export type { ContainerClassKey } from '@mui/system';
export type { ContainerClasses };
export function getContainerUtilityClass(slot: string): string {
- return generateUtilityClass('JoyContainer', slot);
+ return generateUtilityClass('MuiContainer', slot);
}
-const containerClasses: ContainerClasses = generateUtilityClasses('JoyContainer', [
+const containerClasses: ContainerClasses = generateUtilityClasses('MuiContainer', [
'root',
'disableGutters',
'fixed',
diff --git a/packages/mui-joy/src/Divider/Divider.tsx b/packages/mui-joy/src/Divider/Divider.tsx
index 85842211b06350..f8f0e3f2a8c125 100644
--- a/packages/mui-joy/src/Divider/Divider.tsx
+++ b/packages/mui-joy/src/Divider/Divider.tsx
@@ -9,12 +9,9 @@ import { DividerOwnerState, DividerTypeMap } from './DividerProps';
import { getDividerUtilityClass } from './dividerClasses';
const useUtilityClasses = (ownerState: DividerOwnerState) => {
+ const { orientation, inset } = ownerState;
const slots = {
- root: [
- 'root',
- ownerState.orientation === 'vertical' && 'vertical',
- ownerState.inset && `inset${capitalize(ownerState.inset)}`,
- ],
+ root: ['root', orientation, inset && `inset${capitalize(inset)}`],
};
return composeClasses(slots, getDividerUtilityClass, {});
diff --git a/packages/mui-joy/src/Divider/dividerClasses.ts b/packages/mui-joy/src/Divider/dividerClasses.ts
index f62f25de4838a8..649e9cbac267ea 100644
--- a/packages/mui-joy/src/Divider/dividerClasses.ts
+++ b/packages/mui-joy/src/Divider/dividerClasses.ts
@@ -3,6 +3,8 @@ import { generateUtilityClass, generateUtilityClasses } from '../className';
export interface DividerClasses {
/** Styles applied to the root element. */
root: string;
+ /** Styles applied to the root element if `orientation="horizontal"`. */
+ horizontal: string;
/** Styles applied to the root element if `orientation="vertical"`. */
vertical: string;
/** Styles applied to the root element if `inset="context"`. */
@@ -14,11 +16,12 @@ export interface DividerClasses {
export type DividerClassKey = keyof DividerClasses;
export function getDividerUtilityClass(slot: string): string {
- return generateUtilityClass('JoyDivider', slot);
+ return generateUtilityClass('MuiDivider', slot);
}
-const dividerClasses: DividerClasses = generateUtilityClasses('JoyDivider', [
+const dividerClasses: DividerClasses = generateUtilityClasses('MuiDivider', [
'root',
+ 'horizontal',
'vertical',
'insetContext',
'insetNone',
diff --git a/packages/mui-joy/src/FormControl/FormControl.tsx b/packages/mui-joy/src/FormControl/FormControl.tsx
index a0b2cf6077deec..a705c5c36a76a4 100644
--- a/packages/mui-joy/src/FormControl/FormControl.tsx
+++ b/packages/mui-joy/src/FormControl/FormControl.tsx
@@ -11,10 +11,11 @@ import formControlClasses, { getFormControlUtilityClass } from './formControlCla
import { FormControlProps, FormControlOwnerState, FormControlTypeMap } from './FormControlProps';
const useUtilityClasses = (ownerState: FormControlOwnerState) => {
- const { disabled, error, size, color } = ownerState;
+ const { disabled, error, size, color, orientation } = ownerState;
const slots = {
root: [
'root',
+ orientation,
disabled && 'disabled',
error && 'error',
color && `color${capitalize(color)}`,
@@ -30,8 +31,6 @@ export const FormControlRoot = styled('div', {
slot: 'Root',
overridesResolver: (props, styles) => styles.root,
})<{ ownerState: FormControlOwnerState }>(({ theme, ownerState }) => ({
- '--FormLabel-margin':
- ownerState.orientation === 'horizontal' ? '0 0.375rem 0 0' : '0 0 0.25rem 0',
'--FormLabel-alignSelf': ownerState.orientation === 'horizontal' ? 'align-items' : 'flex-start',
'--FormHelperText-margin': '0.375rem 0 0 0',
'--FormLabel-asterisk-color': theme.vars.palette.danger[500],
@@ -39,14 +38,19 @@ export const FormControlRoot = styled('div', {
...(ownerState.size === 'sm' && {
'--FormLabel-fontSize': theme.vars.fontSize.xs,
'--FormHelperText-fontSize': theme.vars.fontSize.xs,
+ '--FormLabel-margin':
+ ownerState.orientation === 'horizontal' ? '0 0.5rem 0 0' : '0 0 0.25rem 0',
}),
...(ownerState.size === 'md' && {
'--FormLabel-fontSize': theme.vars.fontSize.sm,
'--FormHelperText-fontSize': theme.vars.fontSize.sm,
+ '--FormLabel-margin':
+ ownerState.orientation === 'horizontal' ? '0 0.75rem 0 0' : '0 0 0.25rem 0',
}),
...(ownerState.size === 'lg' && {
'--FormLabel-fontSize': theme.vars.fontSize.md,
'--FormHelperText-fontSize': theme.vars.fontSize.sm,
+ '--FormLabel-margin': ownerState.orientation === 'horizontal' ? '0 1rem 0 0' : '0 0 0.25rem 0',
}),
[`&.${formControlClasses.error}`]: {
'--FormHelperText-color': theme.vars.palette.danger[500],
@@ -75,6 +79,7 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
error = false,
color,
size = 'md',
+ orientation = 'vertical',
...other
} = props;
@@ -90,6 +95,7 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) {
error,
required,
size,
+ orientation,
};
let registerEffect: undefined | (() => () => void);
diff --git a/packages/mui-joy/src/FormControl/formControlClasses.ts b/packages/mui-joy/src/FormControl/formControlClasses.ts
index f643e91c407885..12baad5eb5c96c 100644
--- a/packages/mui-joy/src/FormControl/formControlClasses.ts
+++ b/packages/mui-joy/src/FormControl/formControlClasses.ts
@@ -25,15 +25,19 @@ export interface FormControlClasses {
sizeMd: string;
/** Styles applied to the root element if `size="lg"`. */
sizeLg: string;
+ /** Styles applied to the root element if `orientation="horizontal"`. */
+ horizontal: string;
+ /** Styles applied to the root element if `orientation="vertical"`. */
+ vertical: string;
}
export type FormControlClassKey = keyof FormControlClasses;
export function getFormControlUtilityClass(slot: string): string {
- return generateUtilityClass('JoyFormControl', slot);
+ return generateUtilityClass('MuiFormControl', slot);
}
-const formControlClasses: FormControlClasses = generateUtilityClasses('JoyFormControl', [
+const formControlClasses: FormControlClasses = generateUtilityClasses('MuiFormControl', [
'root',
'error',
'disabled',
@@ -46,6 +50,8 @@ const formControlClasses: FormControlClasses = generateUtilityClasses('JoyFormCo
'sizeSm',
'sizeMd',
'sizeLg',
+ 'horizontal',
+ 'vertical',
]);
export default formControlClasses;
diff --git a/packages/mui-joy/src/FormHelperText/formHelperTextClasses.ts b/packages/mui-joy/src/FormHelperText/formHelperTextClasses.ts
index f0fabe902ad4d6..74d960952085d4 100644
--- a/packages/mui-joy/src/FormHelperText/formHelperTextClasses.ts
+++ b/packages/mui-joy/src/FormHelperText/formHelperTextClasses.ts
@@ -8,10 +8,10 @@ export interface FormHelperTextClasses {
export type FormHelperTextClassKey = keyof FormHelperTextClasses;
export function getFormHelperTextUtilityClass(slot: string): string {
- return generateUtilityClass('JoyFormHelperText', slot);
+ return generateUtilityClass('MuiFormHelperText', slot);
}
-const formHelperTextClasses: FormHelperTextClasses = generateUtilityClasses('JoyFormHelperText', [
+const formHelperTextClasses: FormHelperTextClasses = generateUtilityClasses('MuiFormHelperText', [
'root',
]);
diff --git a/packages/mui-joy/src/FormLabel/formLabelClasses.ts b/packages/mui-joy/src/FormLabel/formLabelClasses.ts
index b2624a7ef4c39a..bb92be9377df51 100644
--- a/packages/mui-joy/src/FormLabel/formLabelClasses.ts
+++ b/packages/mui-joy/src/FormLabel/formLabelClasses.ts
@@ -10,10 +10,10 @@ export interface FormLabelClasses {
export type FormLabelClassKey = keyof FormLabelClasses;
export function getFormLabelUtilityClass(slot: string): string {
- return generateUtilityClass('JoyFormLabel', slot);
+ return generateUtilityClass('MuiFormLabel', slot);
}
-const formLabelClasses: FormLabelClasses = generateUtilityClasses('JoyFormLabel', [
+const formLabelClasses: FormLabelClasses = generateUtilityClasses('MuiFormLabel', [
'root',
'asterisk',
]);
diff --git a/packages/mui-joy/src/Grid/Grid.test.js b/packages/mui-joy/src/Grid/Grid.test.js
index 1f14967e816b98..90d59e2aa5429a 100644
--- a/packages/mui-joy/src/Grid/Grid.test.js
+++ b/packages/mui-joy/src/Grid/Grid.test.js
@@ -18,6 +18,6 @@ describe('Joy UI ', () => {
refInstanceof: window.HTMLDivElement,
muiName: 'JoyGrid',
testVariantProps: { container: true, spacing: 5 },
- skip: ['componentsProp', 'classesRoot'],
+ skip: ['componentsProp', 'classesRoot', 'rootClass'],
}));
});
diff --git a/packages/mui-joy/src/Grid/gridClasses.ts b/packages/mui-joy/src/Grid/gridClasses.ts
index 709a01c33bd7cd..e34cc3c742f748 100644
--- a/packages/mui-joy/src/Grid/gridClasses.ts
+++ b/packages/mui-joy/src/Grid/gridClasses.ts
@@ -7,7 +7,7 @@ import { GridClasses } from '@mui/system/Unstable_Grid';
export type GridClassKey = keyof GridClasses;
export function getGridUtilityClass(slot: string): string {
- return generateUtilityClass('JoyGrid', slot);
+ return generateUtilityClass('MuiGrid', slot);
}
const SPACINGS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as const;
@@ -15,7 +15,7 @@ const DIRECTIONS = ['column-reverse', 'column', 'row-reverse', 'row'] as const;
const WRAPS = ['nowrap', 'wrap-reverse', 'wrap'] as const;
const GRID_SIZES = ['auto', true, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as const;
-const gridClasses: GridClasses = generateUtilityClasses('JoyGrid', [
+const gridClasses: GridClasses = generateUtilityClasses('MuiGrid', [
'root',
'container',
'item',
diff --git a/packages/mui-joy/src/IconButton/iconButtonClasses.ts b/packages/mui-joy/src/IconButton/iconButtonClasses.ts
index 3028400351d277..9d81a4a5db40a4 100644
--- a/packages/mui-joy/src/IconButton/iconButtonClasses.ts
+++ b/packages/mui-joy/src/IconButton/iconButtonClasses.ts
@@ -40,10 +40,10 @@ export interface IconButtonClasses {
export type IconButtonClassKey = keyof IconButtonClasses;
export function getIconButtonUtilityClass(slot: string): string {
- return generateUtilityClass('JoyIconButton', slot);
+ return generateUtilityClass('MuiIconButton', slot);
}
-const iconButtonClasses: IconButtonClasses = generateUtilityClasses('JoyIconButton', [
+const iconButtonClasses: IconButtonClasses = generateUtilityClasses('MuiIconButton', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/Input/Input.test.js b/packages/mui-joy/src/Input/Input.test.js
index 77d37fa9f47e35..991362f9522b98 100644
--- a/packages/mui-joy/src/Input/Input.test.js
+++ b/packages/mui-joy/src/Input/Input.test.js
@@ -7,6 +7,7 @@ import {
createRenderer,
screen,
act,
+ fireEvent,
} from 'test/utils';
import Input, { inputClasses as classes } from '@mui/joy/Input';
import { ThemeProvider } from '@mui/joy/styles';
@@ -34,6 +35,11 @@ describe('Joy ', () => {
describeJoyColorInversion( , { muiName: 'JoyInput', classes });
+ it('should have placeholder', () => {
+ const { getByPlaceholderText } = render( );
+ expect(getByPlaceholderText('Placeholder')).toBeVisible();
+ });
+
it('should have error classes', () => {
const { container } = render( );
expect(container.firstChild).to.have.class(classes.error);
@@ -71,10 +77,10 @@ describe('Joy ', () => {
it('should reset the focused state if getting disabled', () => {
const handleBlur = spy();
const handleFocus = spy();
- const { container, setProps } = render( );
+ const { getByRole, setProps } = render( );
act(() => {
- container.querySelector('input').focus();
+ getByRole('textbox').focus();
});
expect(handleFocus.callCount).to.equal(1);
@@ -84,4 +90,54 @@ describe('Joy ', () => {
expect(handleFocus.callCount).to.equal(1);
});
});
+
+ describe('slotProps: input', () => {
+ it('`onKeyDown` and `onKeyUp` should work', () => {
+ const handleKeyDown = spy();
+ const handleKeyUp = spy();
+ const { getByRole } = render(
+ ,
+ );
+
+ act(() => {
+ getByRole('textbox').focus();
+ });
+ fireEvent.keyDown(getByRole('textbox'));
+ fireEvent.keyUp(getByRole('textbox'));
+
+ expect(handleKeyDown.callCount).to.equal(1);
+ expect(handleKeyUp.callCount).to.equal(1);
+ });
+
+ it('should call focus and blur', () => {
+ const handleBlur = spy();
+ const handleFocus = spy();
+ const { getByRole } = render(
+ ,
+ );
+
+ act(() => {
+ getByRole('textbox').focus();
+ });
+ expect(handleFocus.callCount).to.equal(1);
+ act(() => {
+ getByRole('textbox').blur();
+ });
+ expect(handleFocus.callCount).to.equal(1);
+ });
+
+ it('should override outer handlers', () => {
+ const handleFocus = spy();
+ const handleSlotFocus = spy();
+ const { getByRole } = render(
+ ,
+ );
+
+ act(() => {
+ getByRole('textbox').focus();
+ });
+ expect(handleFocus.callCount).to.equal(0);
+ expect(handleSlotFocus.callCount).to.equal(1);
+ });
+ });
});
diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx
index d311f0d7166ed4..2b52cdbc8dfa67 100644
--- a/packages/mui-joy/src/Input/Input.tsx
+++ b/packages/mui-joy/src/Input/Input.tsx
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { unstable_capitalize as capitalize } from '@mui/utils';
import { OverridableComponent } from '@mui/types';
import composeClasses from '@mui/base/composeClasses';
-import { EventHandlers } from '@mui/base/utils';
import { styled, useThemeProps } from '../styles';
import { useColorInversion } from '../styles/ColorInversion';
import useSlot from '../utils/useSlot';
@@ -262,7 +261,6 @@ const Input = React.forwardRef(function Input(inProps, ref) {
inputStateClasses,
getRootProps,
getInputProps,
- component = 'div',
formControl,
focused,
error: errorProp = false,
@@ -305,14 +303,13 @@ const Input = React.forwardRef(function Input(inProps, ref) {
};
const classes = useUtilityClasses(ownerState);
- const externalForwardedProps = { ...other, component };
const [SlotRoot, rootProps] = useSlot('root', {
ref,
className: [classes.root, rootStateClasses],
elementType: InputRoot,
getSlotProps: getRootProps,
- externalForwardedProps,
+ externalForwardedProps: other,
ownerState,
});
@@ -325,23 +322,23 @@ const Input = React.forwardRef(function Input(inProps, ref) {
}),
className: [classes.input, inputStateClasses],
elementType: InputInput,
- getSlotProps: (otherHandlers: EventHandlers) =>
- getInputProps({ ...otherHandlers, ...propsToForward }),
- externalForwardedProps,
+ getSlotProps: getInputProps,
+ internalForwardedProps: propsToForward,
+ externalForwardedProps: other,
ownerState,
});
const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', {
className: classes.startDecorator,
elementType: InputStartDecorator,
- externalForwardedProps,
+ externalForwardedProps: other,
ownerState,
});
const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', {
className: classes.endDecorator,
elementType: InputEndDecorator,
- externalForwardedProps,
+ externalForwardedProps: other,
ownerState,
});
diff --git a/packages/mui-joy/src/Input/inputClasses.ts b/packages/mui-joy/src/Input/inputClasses.ts
index e69e7b353ea00c..ce84dfc065f3b3 100644
--- a/packages/mui-joy/src/Input/inputClasses.ts
+++ b/packages/mui-joy/src/Input/inputClasses.ts
@@ -52,10 +52,10 @@ export interface InputClasses {
export type InputClassKey = keyof InputClasses;
export function getInputUtilityClass(slot: string): string {
- return generateUtilityClass('JoyInput', slot);
+ return generateUtilityClass('MuiInput', slot);
}
-const inputClasses: InputClasses = generateUtilityClasses('JoyInput', [
+const inputClasses: InputClasses = generateUtilityClasses('MuiInput', [
'root',
'input',
'formControl',
diff --git a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts
index 0e1437aa8bd3ad..5247048da0a98a 100644
--- a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts
+++ b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts
@@ -38,10 +38,10 @@ export interface LinearProgressClasses {
export type LinearProgressClassKey = keyof LinearProgressClasses;
export function getLinearProgressUtilityClass(slot: string): string {
- return generateUtilityClass('JoyLinearProgress', slot);
+ return generateUtilityClass('MuiLinearProgress', slot);
}
-const linearProgressClasses: LinearProgressClasses = generateUtilityClasses('JoyLinearProgress', [
+const linearProgressClasses: LinearProgressClasses = generateUtilityClasses('MuiLinearProgress', [
'root',
'determinate',
'colorPrimary',
diff --git a/packages/mui-joy/src/Link/Link.spec.tsx b/packages/mui-joy/src/Link/Link.spec.tsx
index bb08b658747462..6dde3ffaf1fd5b 100644
--- a/packages/mui-joy/src/Link/Link.spec.tsx
+++ b/packages/mui-joy/src/Link/Link.spec.tsx
@@ -20,6 +20,7 @@ import { expectType } from '@mui/types';
;
;
+ ; // should support plain string
// `level`
;
diff --git a/packages/mui-joy/src/Link/Link.tsx b/packages/mui-joy/src/Link/Link.tsx
index 8b6e35cd0b8fee..68a4849fc342fe 100644
--- a/packages/mui-joy/src/Link/Link.tsx
+++ b/packages/mui-joy/src/Link/Link.tsx
@@ -40,19 +40,29 @@ const StartDecorator = styled('span', {
name: 'JoyLink',
slot: 'StartDecorator',
overridesResolver: (props, styles) => styles.startDecorator,
-})<{ ownerState: LinkOwnerState }>({
+})<{ ownerState: LinkOwnerState }>(({ ownerState }) => ({
display: 'inline-flex',
marginInlineEnd: 'clamp(4px, var(--Link-gap, 0.375em), 0.75rem)',
-});
+ ...(typeof ownerState.startDecorator !== 'string' &&
+ (ownerState.alignItems === 'flex-start' ||
+ (ownerState.sx as any)?.alignItems === 'flex-start') && {
+ marginTop: '2px', // this makes the alignment perfect in most cases
+ }),
+}));
const EndDecorator = styled('span', {
name: 'JoyLink',
slot: 'endDecorator',
overridesResolver: (props, styles) => styles.endDecorator,
-})<{ ownerState: LinkOwnerState }>({
+})<{ ownerState: LinkOwnerState }>(({ ownerState }) => ({
display: 'inline-flex',
marginInlineStart: 'clamp(4px, var(--Link-gap, 0.25em), 0.5rem)', // for end decorator, 0.25em looks better.
-});
+ ...(typeof ownerState.startDecorator !== 'string' &&
+ (ownerState.alignItems === 'flex-start' ||
+ (ownerState.sx as any)?.alignItems === 'flex-start') && {
+ marginTop: '2px', // this makes the alignment perfect in most cases
+ }),
+}));
const LinkRoot = styled('a', {
name: 'JoyLink',
diff --git a/packages/mui-joy/src/Link/LinkProps.ts b/packages/mui-joy/src/Link/LinkProps.ts
index 93e5ec8857cd2a..62db58e2ab6b7a 100644
--- a/packages/mui-joy/src/Link/LinkProps.ts
+++ b/packages/mui-joy/src/Link/LinkProps.ts
@@ -7,6 +7,7 @@ import {
ApplyColorInversion,
TypographySystem,
VariantProp,
+ TextColor,
} from '../styles/types';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
@@ -61,7 +62,7 @@ export interface LinkTypeMap {
/**
* The system color.
*/
- textColor?: SystemProps['color'];
+ textColor?: TextColor;
/**
* Element placed before the children.
*/
diff --git a/packages/mui-joy/src/Link/linkClasses.ts b/packages/mui-joy/src/Link/linkClasses.ts
index bcde0b06ab16c3..f1d1ffd84307d9 100644
--- a/packages/mui-joy/src/Link/linkClasses.ts
+++ b/packages/mui-joy/src/Link/linkClasses.ts
@@ -62,10 +62,10 @@ export interface LinkClasses {
export type LinkClassKey = keyof LinkClasses;
export function getLinkUtilityClass(slot: string): string {
- return generateUtilityClass('JoyLink', slot);
+ return generateUtilityClass('MuiLink', slot);
}
-const linkClasses: LinkClasses = generateUtilityClasses('JoyLink', [
+const linkClasses: LinkClasses = generateUtilityClasses('MuiLink', [
'root',
'disabled',
'focusVisible',
diff --git a/packages/mui-joy/src/List/List.test.js b/packages/mui-joy/src/List/List.test.js
index 948f7d06762ade..b5b5f04906d3f3 100644
--- a/packages/mui-joy/src/List/List.test.js
+++ b/packages/mui-joy/src/List/List.test.js
@@ -52,7 +52,7 @@ describe('Joy
', () => {
expect(container.firstChild).to.have.class(classes.sizeMd);
});
- it('should have nesting classes', () => {
+ it('should have `nesting` classes', () => {
const { getByRole } = render(
@@ -61,9 +61,9 @@ describe('Joy
', () => {
expect(getByRole('list')).to.have.class(classes.nesting);
});
- it('should have row classes', () => {
- const { getByRole } = render(
);
- expect(getByRole('list')).to.have.class(classes.row);
+ it('should have `orientation` classes', () => {
+ const { getByRole } = render(
);
+ expect(getByRole('list')).to.have.class(classes.horizontal);
});
describe('MenuList - integration', () => {
diff --git a/packages/mui-joy/src/List/List.tsx b/packages/mui-joy/src/List/List.tsx
index 477b4d3d6f89a7..c1fded1e949db2 100644
--- a/packages/mui-joy/src/List/List.tsx
+++ b/packages/mui-joy/src/List/List.tsx
@@ -16,16 +16,16 @@ import ListProvider from './ListProvider';
import RadioGroupContext from '../RadioGroup/RadioGroupContext';
const useUtilityClasses = (ownerState: ListOwnerState) => {
- const { variant, color, size, nesting, row, instanceSize } = ownerState;
+ const { variant, color, size, nesting, orientation, instanceSize } = ownerState;
const slots = {
root: [
'root',
+ orientation,
variant && `variant${capitalize(variant)}`,
color && `color${capitalize(color)}`,
!instanceSize && !nesting && size && `size${capitalize(size)}`,
instanceSize && `size${capitalize(instanceSize)}`,
nesting && 'nesting',
- row && 'row',
],
};
@@ -41,7 +41,7 @@ export const StyledList = styled('ul')<{ ownerState: ListOwnerState }>(({ theme,
'--List-item-paddingY': '0.25rem',
'--List-item-paddingX': '0.5rem',
'--List-item-fontSize': theme.vars.fontSize.sm,
- '--List-decorator-size': ownerState.row ? '1.5rem' : '2rem',
+ '--List-decorator-size': ownerState.orientation === 'horizontal' ? '1.5rem' : '2rem',
'--Icon-fontSize': '1.125rem',
};
}
@@ -52,7 +52,7 @@ export const StyledList = styled('ul')<{ ownerState: ListOwnerState }>(({ theme,
'--List-item-paddingY': '0.375rem',
'--List-item-paddingX': '0.75rem',
'--List-item-fontSize': theme.vars.fontSize.md,
- '--List-decorator-size': ownerState.row ? '1.75rem' : '2.5rem',
+ '--List-decorator-size': ownerState.orientation === 'horizontal' ? '1.75rem' : '2.5rem',
'--Icon-fontSize': '1.25rem',
};
}
@@ -63,7 +63,7 @@ export const StyledList = styled('ul')<{ ownerState: ListOwnerState }>(({ theme,
'--List-item-paddingY': '0.5rem',
'--List-item-paddingX': '1rem',
'--List-item-fontSize': theme.vars.fontSize.md,
- '--List-decorator-size': ownerState.row ? '2.25rem' : '3rem',
+ '--List-decorator-size': ownerState.orientation === 'horizontal' ? '2.25rem' : '3rem',
'--Icon-fontSize': '1.5rem',
};
}
@@ -103,7 +103,7 @@ export const StyledList = styled('ul')<{ ownerState: ListOwnerState }>(({ theme,
'--List-item-endActionTranslateX': 'calc(-0.5 * var(--List-item-paddingRight))',
margin: 'initial',
// --List-padding is not declared to let list uses --List-divider-gap by default.
- ...(ownerState.row
+ ...(ownerState.orientation === 'horizontal'
? {
...(ownerState.wrap
? {
@@ -126,7 +126,7 @@ export const StyledList = styled('ul')<{ ownerState: ListOwnerState }>(({ theme,
borderRadius: 'var(--List-radius)',
listStyle: 'none',
display: 'flex',
- flexDirection: ownerState.row ? 'row' : 'column',
+ flexDirection: ownerState.orientation === 'horizontal' ? 'row' : 'column',
...(ownerState.wrap && {
flexWrap: 'wrap',
}),
@@ -158,7 +158,7 @@ const List = React.forwardRef(function List(inProps, ref) {
className,
children,
size = inProps.size ?? 'md',
- row = false,
+ orientation = 'vertical',
wrap = false,
variant = 'plain',
color: colorProp = 'neutral',
@@ -184,7 +184,7 @@ const List = React.forwardRef(function List(inProps, ref) {
instanceSize: inProps.size,
size,
nesting,
- row,
+ orientation,
wrap,
variant,
color,
@@ -206,7 +206,7 @@ const List = React.forwardRef(function List(inProps, ref) {
-
+
{children}
@@ -241,14 +241,14 @@ List.propTypes /* remove-proptypes */ = {
*/
component: PropTypes.elementType,
/**
- * @ignore
+ * The component orientation.
+ * @default 'vertical'
*/
- role: PropTypes /* @typescript-to-proptypes-ignore */.string,
+ orientation: PropTypes.oneOf(['horizontal', 'vertical']),
/**
- * If `true`, display the list in horizontal direction.
- * @default false
+ * @ignore
*/
- row: PropTypes.bool,
+ role: PropTypes /* @typescript-to-proptypes-ignore */.string,
/**
* The size of the component (affect other nested list* components).
* @default 'md'
diff --git a/packages/mui-joy/src/List/ListProps.ts b/packages/mui-joy/src/List/ListProps.ts
index e0c5636694edc1..51657459e4f6dd 100644
--- a/packages/mui-joy/src/List/ListProps.ts
+++ b/packages/mui-joy/src/List/ListProps.ts
@@ -20,10 +20,10 @@ export interface ListTypeMap {
*/
color?: OverridableStringUnion;
/**
- * If `true`, display the list in horizontal direction.
- * @default false
+ * The component orientation.
+ * @default 'vertical'
*/
- row?: boolean;
+ orientation?: 'horizontal' | 'vertical';
/**
* The size of the component (affect other nested list* components).
* @default 'md'
diff --git a/packages/mui-joy/src/List/listClasses.ts b/packages/mui-joy/src/List/listClasses.ts
index 431c29214f62c3..e10f210091073d 100644
--- a/packages/mui-joy/src/List/listClasses.ts
+++ b/packages/mui-joy/src/List/listClasses.ts
@@ -5,8 +5,6 @@ export interface ListClasses {
root: string;
/** Classname applied to the root element if wrapped with nested context. */
nesting: string;
- /** Classname applied to the root element if `row` is true. */
- row: string;
/** Classname applied to the root element if `scoped` is true. */
scoped: string;
/** Classname applied to the root element if `size="sm"`. */
@@ -37,18 +35,21 @@ export interface ListClasses {
variantSoft: string;
/** Classname applied to the root element if `variant="solid"`. */
variantSolid: string;
+ /** Styles applied to the root element if `orientation="horizontal"`. */
+ horizontal: string;
+ /** Styles applied to the root element if `orientation="vertical"`. */
+ vertical: string;
}
export type ListClassKey = keyof ListClasses;
export function getListUtilityClass(slot: string): string {
- return generateUtilityClass('JoyList', slot);
+ return generateUtilityClass('MuiList', slot);
}
-const listClasses: ListClasses = generateUtilityClasses('JoyList', [
+const listClasses: ListClasses = generateUtilityClasses('MuiList', [
'root',
'nesting',
- 'row',
'scoped',
'sizeSm',
'sizeMd',
@@ -64,6 +65,8 @@ const listClasses: ListClasses = generateUtilityClasses('JoyList', [
'variantOutlined',
'variantSoft',
'variantSolid',
+ 'horizontal',
+ 'vertical',
]);
export default listClasses;
diff --git a/packages/mui-joy/src/ListDivider/ListDivider.test.js b/packages/mui-joy/src/ListDivider/ListDivider.test.js
index b1211c10d5c4c9..7d86050aa3bdbe 100644
--- a/packages/mui-joy/src/ListDivider/ListDivider.test.js
+++ b/packages/mui-joy/src/ListDivider/ListDivider.test.js
@@ -38,7 +38,7 @@ describe('Joy ', () => {
it('should have aria-orientation set to vertical', () => {
render(
-
+
,
);
@@ -47,7 +47,7 @@ describe('Joy ', () => {
it('should not add aria-orientation if role is custom', () => {
render(
-
+
,
);
diff --git a/packages/mui-joy/src/ListDivider/ListDivider.tsx b/packages/mui-joy/src/ListDivider/ListDivider.tsx
index 5a73ad4e810b8c..74098d3cf080ef 100644
--- a/packages/mui-joy/src/ListDivider/ListDivider.tsx
+++ b/packages/mui-joy/src/ListDivider/ListDivider.tsx
@@ -11,11 +11,13 @@ import { getListDividerUtilityClass } from './listDividerClasses';
import RowListContext from '../List/RowListContext';
const useUtilityClasses = (ownerState: ListDividerOwnerState) => {
+ const { orientation, inset } = ownerState;
const slots = {
root: [
'root',
+ orientation,
// `insetContext` class is already produced by Divider
- ownerState.inset && ownerState.inset !== 'context' && `inset${capitalize(ownerState.inset)}`,
+ inset && inset !== 'context' && `inset${capitalize(inset)}`,
],
};
diff --git a/packages/mui-joy/src/ListDivider/listDividerClasses.ts b/packages/mui-joy/src/ListDivider/listDividerClasses.ts
index 7ed0ad0b202fd6..104df4ac6754c1 100644
--- a/packages/mui-joy/src/ListDivider/listDividerClasses.ts
+++ b/packages/mui-joy/src/ListDivider/listDividerClasses.ts
@@ -9,19 +9,25 @@ export interface ListDividerClasses {
insetStartDecorator: string;
/** Styles applied to the root element if `inset="startContent"`. */
insetStartContent: string;
+ /** Styles applied to the root element if `orientation="horizontal"`. */
+ horizontal: string;
+ /** Styles applied to the root element if `orientation="vertical"`. */
+ vertical: string;
}
export type ListDividerClassKey = keyof ListDividerClasses;
export function getListDividerUtilityClass(slot: string): string {
- return generateUtilityClass('JoyListDivider', slot);
+ return generateUtilityClass('MuiListDivider', slot);
}
-const listDividerClasses: ListDividerClasses = generateUtilityClasses('JoyListDivider', [
+const listDividerClasses: ListDividerClasses = generateUtilityClasses('MuiListDivider', [
'root',
'insetGutter',
'insetStartDecorator',
'insetStartContent',
+ 'horizontal',
+ 'vertical',
]);
export default listDividerClasses;
diff --git a/packages/mui-joy/src/ListItem/listItemClasses.ts b/packages/mui-joy/src/ListItem/listItemClasses.ts
index 0dd5c74faa9c56..3f5d429fe0652b 100644
--- a/packages/mui-joy/src/ListItem/listItemClasses.ts
+++ b/packages/mui-joy/src/ListItem/listItemClasses.ts
@@ -40,10 +40,10 @@ export interface ListItemClasses {
export type ListItemClassKey = keyof ListItemClasses;
export function getListItemUtilityClass(slot: string): string {
- return generateUtilityClass('JoyListItem', slot);
+ return generateUtilityClass('MuiListItem', slot);
}
-const listItemClasses: ListItemClasses = generateUtilityClasses('JoyListItem', [
+const listItemClasses: ListItemClasses = generateUtilityClasses('MuiListItem', [
'root',
'startAction',
'endAction',
diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx
index f89921c6957537..9e11457414172e 100644
--- a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx
+++ b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx
@@ -89,9 +89,13 @@ export const StyledListItemButton = styled('div')<{ ownerState: ListItemButtonOw
}),
[theme.focus.selector]: theme.focus.default,
},
- theme.variants[ownerState.variant!]?.[ownerState.color!],
- { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] },
- { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] },
+ {
+ ...theme.variants[ownerState.variant!]?.[ownerState.color!],
+ ...(!ownerState.selected && {
+ '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!],
+ '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!],
+ }),
+ },
{
[`&.${listItemButtonClasses.disabled}`]:
theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!],
diff --git a/packages/mui-joy/src/ListItemButton/listItemButtonClasses.ts b/packages/mui-joy/src/ListItemButton/listItemButtonClasses.ts
index a4a196a681f91b..d1e08eef83861a 100644
--- a/packages/mui-joy/src/ListItemButton/listItemButtonClasses.ts
+++ b/packages/mui-joy/src/ListItemButton/listItemButtonClasses.ts
@@ -40,10 +40,10 @@ export interface ListItemButtonClasses {
export type ListItemButtonClassKey = keyof ListItemButtonClasses;
export function getListItemButtonUtilityClass(slot: string): string {
- return generateUtilityClass('JoyListItemButton', slot);
+ return generateUtilityClass('MuiListItemButton', slot);
}
-const listItemButtonClasses: ListItemButtonClasses = generateUtilityClasses('JoyListItemButton', [
+const listItemButtonClasses: ListItemButtonClasses = generateUtilityClasses('MuiListItemButton', [
'root',
'horizontal',
'vertical',
diff --git a/packages/mui-joy/src/ListItemContent/listItemContentClasses.ts b/packages/mui-joy/src/ListItemContent/listItemContentClasses.ts
index 02ce494988f50f..bfe99fad3e5f0a 100644
--- a/packages/mui-joy/src/ListItemContent/listItemContentClasses.ts
+++ b/packages/mui-joy/src/ListItemContent/listItemContentClasses.ts
@@ -8,11 +8,11 @@ export interface ListItemContentClasses {
export type ListItemContentClassKey = keyof ListItemContentClasses;
export function getListItemContentUtilityClass(slot: string): string {
- return generateUtilityClass('JoyListItemContent', slot);
+ return generateUtilityClass('MuiListItemContent', slot);
}
const listItemContentClasses: ListItemContentClasses = generateUtilityClasses(
- 'JoyListItemContent',
+ 'MuiListItemContent',
['root'],
);
diff --git a/packages/mui-joy/src/ListItemDecorator/listItemDecoratorClasses.ts b/packages/mui-joy/src/ListItemDecorator/listItemDecoratorClasses.ts
index 3a06f107e9c91c..cd8f6deb1de243 100644
--- a/packages/mui-joy/src/ListItemDecorator/listItemDecoratorClasses.ts
+++ b/packages/mui-joy/src/ListItemDecorator/listItemDecoratorClasses.ts
@@ -8,11 +8,11 @@ export interface ListItemDecoratorClasses {
export type ListItemDecoratorClassKey = keyof ListItemDecoratorClasses;
export function getListItemDecoratorUtilityClass(slot: string): string {
- return generateUtilityClass('JoyListItemDecorator', slot);
+ return generateUtilityClass('MuiListItemDecorator', slot);
}
const listItemDecoratorClasses: ListItemDecoratorClasses = generateUtilityClasses(
- 'JoyListItemDecorator',
+ 'MuiListItemDecorator',
['root'],
);
diff --git a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts
index de8f9045987e8a..cc22ad928d1e73 100644
--- a/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts
+++ b/packages/mui-joy/src/ListSubheader/listSubheaderClasses.ts
@@ -32,10 +32,10 @@ export interface ListSubheaderClasses {
export type ListSubheaderClassKey = keyof ListSubheaderClasses;
export function getListSubheaderUtilityClass(slot: string): string {
- return generateUtilityClass('JoyListSubheader', slot);
+ return generateUtilityClass('MuiListSubheader', slot);
}
-const listSubheaderClasses: ListSubheaderClasses = generateUtilityClasses('JoyListSubheader', [
+const listSubheaderClasses: ListSubheaderClasses = generateUtilityClasses('MuiListSubheader', [
'root',
'sticky',
'colorPrimary',
diff --git a/packages/mui-joy/src/Menu/menuClasses.ts b/packages/mui-joy/src/Menu/menuClasses.ts
index 89ff38f2e5fc36..c34fec1f4f410e 100644
--- a/packages/mui-joy/src/Menu/menuClasses.ts
+++ b/packages/mui-joy/src/Menu/menuClasses.ts
@@ -38,10 +38,10 @@ export interface MenuClasses {
export type MenuClassKey = keyof MenuClasses;
export function getMenuUtilityClass(slot: string): string {
- return generateUtilityClass('JoyMenu', slot);
+ return generateUtilityClass('MuiMenu', slot);
}
-const menuClasses: MenuClasses = generateUtilityClasses('JoyMenu', [
+const menuClasses: MenuClasses = generateUtilityClasses('MuiMenu', [
'root',
'expanded',
'colorPrimary',
diff --git a/packages/mui-joy/src/MenuItem/menuItemClasses.ts b/packages/mui-joy/src/MenuItem/menuItemClasses.ts
index 19d99c80a7144c..d2bf5854911a3d 100644
--- a/packages/mui-joy/src/MenuItem/menuItemClasses.ts
+++ b/packages/mui-joy/src/MenuItem/menuItemClasses.ts
@@ -36,10 +36,10 @@ export interface MenuItemClasses {
export type MenuItemClassKey = keyof MenuItemClasses;
export function getMenuItemUtilityClass(slot: string): string {
- return generateUtilityClass('JoyMenuItem', slot);
+ return generateUtilityClass('MuiMenuItem', slot);
}
-const menuItemClasses: MenuItemClasses = generateUtilityClasses('JoyMenuItem', [
+const menuItemClasses: MenuItemClasses = generateUtilityClasses('MuiMenuItem', [
'root',
'focusVisible',
'disabled',
diff --git a/packages/mui-joy/src/MenuList/menuListClasses.ts b/packages/mui-joy/src/MenuList/menuListClasses.ts
index 7d80b26fdbaeab..ee8c7e1b0a2f73 100644
--- a/packages/mui-joy/src/MenuList/menuListClasses.ts
+++ b/packages/mui-joy/src/MenuList/menuListClasses.ts
@@ -36,10 +36,10 @@ export interface MenuListClasses {
export type MenuListClassKey = keyof MenuListClasses;
export function getMenuListUtilityClass(slot: string): string {
- return generateUtilityClass('JoyMenuList', slot);
+ return generateUtilityClass('MuiMenuList', slot);
}
-const menuClasses: MenuListClasses = generateUtilityClasses('JoyMenuList', [
+const menuClasses: MenuListClasses = generateUtilityClasses('MuiMenuList', [
'root',
'nested',
'sizeSm',
diff --git a/packages/mui-joy/src/Modal/modalClasses.ts b/packages/mui-joy/src/Modal/modalClasses.ts
index 8c89001fde5e54..d409e040450f11 100644
--- a/packages/mui-joy/src/Modal/modalClasses.ts
+++ b/packages/mui-joy/src/Modal/modalClasses.ts
@@ -10,9 +10,9 @@ export interface ModalClasses {
export type ModalClassKey = keyof ModalClasses;
export function getModalUtilityClass(slot: string): string {
- return generateUtilityClass('JoyModal', slot);
+ return generateUtilityClass('MuiModal', slot);
}
-const modalClasses: ModalClasses = generateUtilityClasses('JoyModal', ['root', 'backdrop']);
+const modalClasses: ModalClasses = generateUtilityClasses('MuiModal', ['root', 'backdrop']);
export default modalClasses;
diff --git a/packages/mui-joy/src/ModalClose/modalCloseClasses.ts b/packages/mui-joy/src/ModalClose/modalCloseClasses.ts
index 7127b7989791f2..69dff9f8b45218 100644
--- a/packages/mui-joy/src/ModalClose/modalCloseClasses.ts
+++ b/packages/mui-joy/src/ModalClose/modalCloseClasses.ts
@@ -36,10 +36,10 @@ export interface ModalCloseClasses {
export type ModalCloseClassKey = keyof ModalCloseClasses;
export function getModalCloseUtilityClass(slot: string): string {
- return generateUtilityClass('JoyModalClose', slot);
+ return generateUtilityClass('MuiModalClose', slot);
}
-const modalCloseClasses: ModalCloseClasses = generateUtilityClasses('JoyModalClose', [
+const modalCloseClasses: ModalCloseClasses = generateUtilityClasses('MuiModalClose', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx
index 09a11c1dbf4798..40d892eb80e522 100644
--- a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx
+++ b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx
@@ -41,22 +41,31 @@ const ModalDialogRoot = styled(SheetRoot, {
'--ModalClose-radius':
'max((var(--ModalDialog-radius) - var(--variant-borderWidth, 0px)) - var(--ModalClose-inset), min(var(--ModalClose-inset) / 2, (var(--ModalDialog-radius) - var(--variant-borderWidth, 0px)) / 2))',
...(ownerState.size === 'sm' && {
- '--ModalDialog-padding': theme.spacing(1.25),
+ '--ModalDialog-padding': theme.spacing(2),
'--ModalDialog-radius': theme.vars.radius.sm,
- '--ModalClose-inset': theme.spacing(0.75),
+ '--ModalDialog-gap': theme.spacing(0.75),
+ '--ModalDialog-titleOffset': theme.spacing(0.25),
+ '--ModalDialog-descriptionOffset': theme.spacing(0.25),
+ '--ModalClose-inset': theme.spacing(1.25),
fontSize: theme.vars.fontSize.sm,
}),
...(ownerState.size === 'md' && {
- '--ModalDialog-padding': theme.spacing(2),
+ '--ModalDialog-padding': theme.spacing(2.5),
'--ModalDialog-radius': theme.vars.radius.md,
- '--ModalClose-inset': theme.spacing(1),
+ '--ModalDialog-gap': theme.spacing(1.5),
+ '--ModalDialog-titleOffset': theme.spacing(0.25),
+ '--ModalDialog-descriptionOffset': theme.spacing(0.75),
+ '--ModalClose-inset': theme.spacing(1.5),
fontSize: theme.vars.fontSize.md,
}),
...(ownerState.size === 'lg' && {
'--ModalDialog-padding': theme.spacing(3),
'--ModalDialog-radius': theme.vars.radius.md,
+ '--ModalDialog-gap': theme.spacing(2),
+ '--ModalDialog-titleOffset': theme.spacing(0.75),
+ '--ModalDialog-descriptionOffset': theme.spacing(1),
'--ModalClose-inset': theme.spacing(1.5),
- fontSize: theme.vars.fontSize.md,
+ fontSize: theme.vars.fontSize.lg,
}),
boxSizing: 'border-box',
boxShadow: theme.shadow.md,
@@ -80,6 +89,23 @@ const ModalDialogRoot = styled(SheetRoot, {
left: '50%',
transform: 'translate(-50%, -50%)',
}),
+ [`& [id="${ownerState['aria-labelledby']}"]`]: {
+ '--Typography-margin': 'calc(-1 * var(--ModalDialog-titleOffset)) 0 var(--ModalDialog-gap) 0',
+ '--Typography-fontSize': '1.125em',
+ [`& + [id="${ownerState['aria-describedby']}"]`]: {
+ '--private_ModalDialog-descriptionOffset': 'calc(-1 * var(--ModalDialog-descriptionOffset))',
+ },
+ },
+ [`& [id="${ownerState['aria-describedby']}"]`]: {
+ '--Typography-fontSize': '1em',
+ '--Typography-margin':
+ 'var(--private_ModalDialog-descriptionOffset, var(--ModalDialog-gap)) 0 0 0',
+ '&:not(:last-child)': {
+ // create spacing between description and the next element.
+ '--Typography-margin':
+ 'var(--private_ModalDialog-descriptionOffset, var(--ModalDialog-gap)) 0 var(--ModalDialog-gap) 0',
+ },
+ },
}));
const ModalDialog = React.forwardRef(function ModalDialog(inProps, ref) {
diff --git a/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts b/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts
index 409b21143c7bf0..0f05af5a4e7207 100644
--- a/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts
+++ b/packages/mui-joy/src/ModalDialog/modalDialogClasses.ts
@@ -40,10 +40,10 @@ export interface ModalDialogClasses {
export type ModalDialogClassKey = keyof ModalDialogClasses;
export function getModalDialogUtilityClass(slot: string): string {
- return generateUtilityClass('JoyModalDialog', slot);
+ return generateUtilityClass('MuiModalDialog', slot);
}
-const modalDialogClasses: ModalDialogClasses = generateUtilityClasses('JoyModalDialog', [
+const modalDialogClasses: ModalDialogClasses = generateUtilityClasses('MuiModalDialog', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/Option/optionClasses.ts b/packages/mui-joy/src/Option/optionClasses.ts
index 8acd565b081c30..80f4b4643fae85 100644
--- a/packages/mui-joy/src/Option/optionClasses.ts
+++ b/packages/mui-joy/src/Option/optionClasses.ts
@@ -36,10 +36,10 @@ export interface OptionClasses {
export type OptionClassKey = keyof OptionClasses;
export function getOptionUtilityClass(slot: string): string {
- return generateUtilityClass('JoyOption', slot);
+ return generateUtilityClass('MuiOption', slot);
}
-const optionClasses: OptionClasses = generateUtilityClasses('JoyOption', [
+const optionClasses: OptionClasses = generateUtilityClasses('MuiOption', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/Radio/Radio.tsx b/packages/mui-joy/src/Radio/Radio.tsx
index c92f3933aaf353..19bd72e5305f01 100644
--- a/packages/mui-joy/src/Radio/Radio.tsx
+++ b/packages/mui-joy/src/Radio/Radio.tsx
@@ -91,8 +91,10 @@ const RadioRoot = styled('span', {
}),
...(ownerState['data-parent'] === 'RadioGroup' &&
ownerState['data-first-child'] === undefined && {
- marginInlineStart: ownerState.row ? 'var(--RadioGroup-gap)' : undefined,
- marginBlockStart: ownerState.row ? undefined : 'var(--RadioGroup-gap)',
+ marginInlineStart:
+ ownerState.orientation === 'horizontal' ? 'var(--RadioGroup-gap)' : undefined,
+ marginBlockStart:
+ ownerState.orientation === 'horizontal' ? undefined : 'var(--RadioGroup-gap)',
}),
},
];
@@ -102,36 +104,42 @@ const RadioRadio = styled('span', {
name: 'JoyRadio',
slot: 'Radio',
overridesResolver: (props, styles) => styles.radio,
-})<{ ownerState: RadioOwnerState }>(({ ownerState, theme }) => [
- {
- margin: 0,
- boxSizing: 'border-box',
- width: 'var(--Radio-size)',
- height: 'var(--Radio-size)',
- borderRadius: 'var(--Radio-size)',
- display: 'inline-flex',
- justifyContent: 'center',
- alignItems: 'center',
- flexShrink: 0,
- // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button.
- transition:
- 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
- ...(ownerState.disableIcon && {
- display: 'contents',
- }),
- },
- ...(!ownerState.disableIcon
- ? [
- theme.variants[ownerState.variant!]?.[ownerState.color!],
- { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] },
- { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] },
- {
- [`&.${radioClasses.disabled}`]:
- theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!],
- },
- ]
- : []),
-]);
+})<{ ownerState: RadioOwnerState }>(({ ownerState, theme }) => {
+ const variantStyle = theme.variants[`${ownerState.variant!}`]?.[ownerState.color!];
+ return [
+ {
+ margin: 0,
+ boxSizing: 'border-box',
+ width: 'var(--Radio-size)',
+ height: 'var(--Radio-size)',
+ borderRadius: 'var(--Radio-size)',
+ display: 'inline-flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexShrink: 0,
+ // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button.
+ transition:
+ 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
+ ...(ownerState.disableIcon && {
+ display: 'contents',
+ }),
+ },
+ ...(!ownerState.disableIcon
+ ? [
+ {
+ ...variantStyle,
+ backgroundColor: variantStyle?.backgroundColor ?? theme.vars.palette.background.surface,
+ },
+ { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] },
+ { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] },
+ {
+ [`&.${radioClasses.disabled}`]:
+ theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!],
+ },
+ ]
+ : []),
+ ];
+});
const RadioAction = styled('span', {
name: 'JoyRadio',
@@ -144,10 +152,10 @@ const RadioAction = styled('span', {
// Automatic radius adjustment when composing with ListItem or Sheet
ownerState.overlay ? 'var(--internal-action-radius, inherit)' : 'inherit'
})`,
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
+ top: 'calc(-1 * var(--variant-borderWidth, 0px))', // clickable on the border and focus outline does not move when checked/unchecked
+ left: 'calc(-1 * var(--variant-borderWidth, 0px))',
+ bottom: 'calc(-1 * var(--variant-borderWidth, 0px))',
+ right: 'calc(-1 * var(--variant-borderWidth, 0px))',
zIndex: 1, // The action element usually cover the area of nearest positioned parent
// TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button.
transition:
@@ -301,7 +309,7 @@ const Radio = React.forwardRef(function Radio(inProps, ref) {
size,
disableIcon,
overlay,
- row: radioGroup?.row,
+ orientation: radioGroup?.orientation,
};
const classes = useUtilityClasses(ownerState);
diff --git a/packages/mui-joy/src/Radio/RadioProps.ts b/packages/mui-joy/src/Radio/RadioProps.ts
index 8dab90b9e4eb65..01b56fc1a95d1c 100644
--- a/packages/mui-joy/src/Radio/RadioProps.ts
+++ b/packages/mui-joy/src/Radio/RadioProps.ts
@@ -102,7 +102,7 @@ export interface RadioOwnerState extends ApplyColorInversion {
* @internal
* The value from the RadioGroup component.
*/
- row?: boolean;
+ orientation?: 'horizontal' | 'vertical';
/**
* @internal
* The internal prop for controlling CSS margin of the element.
diff --git a/packages/mui-joy/src/Radio/radioClasses.ts b/packages/mui-joy/src/Radio/radioClasses.ts
index 876afacb826e33..8c3238dd9e8f05 100644
--- a/packages/mui-joy/src/Radio/radioClasses.ts
+++ b/packages/mui-joy/src/Radio/radioClasses.ts
@@ -50,10 +50,10 @@ export interface RadioClasses {
export type RadioClassKey = keyof RadioClasses;
export function getRadioUtilityClass(slot: string): string {
- return generateUtilityClass('JoyRadio', slot);
+ return generateUtilityClass('MuiRadio', slot);
}
-const radioClasses: RadioClasses = generateUtilityClasses('JoyRadio', [
+const radioClasses: RadioClasses = generateUtilityClasses('MuiRadio', [
'root',
'radio',
'icon',
diff --git a/packages/mui-joy/src/RadioGroup/RadioGroup.test.js b/packages/mui-joy/src/RadioGroup/RadioGroup.test.js
index 09e4d4305e5b1c..5d86f4cdca55f2 100644
--- a/packages/mui-joy/src/RadioGroup/RadioGroup.test.js
+++ b/packages/mui-joy/src/RadioGroup/RadioGroup.test.js
@@ -16,15 +16,15 @@ describe(' ', () => {
ThemeProvider,
muiName: 'JoyRadioGroup',
refInstanceof: window.HTMLDivElement,
- testVariantProps: { row: true },
+ testVariantProps: { orientation: 'horizontal' },
testCustomVariant: true,
skip: ['componentProp', 'componentsProp', 'classesRoot', 'propsSpread'],
}));
- it('should have row class if `row` is true', () => {
- const { getByRole } = render( );
+ it('should have `orientation` class', () => {
+ const { getByRole } = render( );
- expect(getByRole('radiogroup')).to.have.class(classes.row);
+ expect(getByRole('radiogroup')).to.have.class(classes.horizontal);
});
it('the root component has the radiogroup role', () => {
diff --git a/packages/mui-joy/src/RadioGroup/RadioGroup.tsx b/packages/mui-joy/src/RadioGroup/RadioGroup.tsx
index e4c65cb3bcb7d6..613f6166fd8b62 100644
--- a/packages/mui-joy/src/RadioGroup/RadioGroup.tsx
+++ b/packages/mui-joy/src/RadioGroup/RadioGroup.tsx
@@ -15,11 +15,11 @@ import RadioGroupContext from './RadioGroupContext';
import FormControlContext from '../FormControl/FormControlContext';
const useUtilityClasses = (ownerState: RadioGroupOwnerState) => {
- const { row, size, variant, color } = ownerState;
+ const { orientation, size, variant, color } = ownerState;
const slots = {
root: [
'root',
- row && 'row',
+ orientation,
variant && `variant${capitalize(variant)}`,
color && `color${capitalize(color)}`,
size && `size${capitalize(size)}`,
@@ -44,7 +44,7 @@ const RadioGroupRoot = styled('div', {
'--RadioGroup-gap': '1.25rem',
}),
display: 'flex',
- flexDirection: ownerState.row ? 'row' : 'column',
+ flexDirection: ownerState.orientation === 'horizontal' ? 'row' : 'column',
borderRadius: theme.vars.radius.sm,
...theme.variants[ownerState.variant!]?.[ownerState.color!],
}));
@@ -68,7 +68,7 @@ const RadioGroup = React.forwardRef(function RadioGroup(inProps, ref) {
color = 'neutral',
variant = 'plain',
size = 'md',
- row = false,
+ orientation = 'vertical',
role = 'radiogroup',
...other
} = props;
@@ -80,7 +80,7 @@ const RadioGroup = React.forwardRef(function RadioGroup(inProps, ref) {
});
const ownerState = {
- row,
+ orientation,
size,
variant,
color,
@@ -110,7 +110,7 @@ const RadioGroup = React.forwardRef(function RadioGroup(inProps, ref) {
() => ({
disableIcon,
overlay,
- row,
+ orientation,
size,
name,
value,
@@ -122,7 +122,7 @@ const RadioGroup = React.forwardRef(function RadioGroup(inProps, ref) {
}
},
}),
- [disableIcon, name, onChange, overlay, row, setValueState, size, value],
+ [disableIcon, name, onChange, overlay, orientation, setValueState, size, value],
);
return (
@@ -203,6 +203,11 @@ RadioGroup.propTypes /* remove-proptypes */ = {
* You can pull out the new value by accessing `event.target.value` (string).
*/
onChange: PropTypes.func,
+ /**
+ * The component orientation.
+ * @default 'vertical'
+ */
+ orientation: PropTypes.oneOf(['horizontal', 'vertical']),
/**
* The radio's `overlay` prop. If specified, the value is passed down to every radios under this element.
*/
@@ -211,11 +216,6 @@ RadioGroup.propTypes /* remove-proptypes */ = {
* @ignore
*/
role: PropTypes /* @typescript-to-proptypes-ignore */.string,
- /**
- * If `true`, flex direction is set to 'row'.
- * @default false
- */
- row: PropTypes.bool,
/**
* The size of the component.
* @default 'md'
diff --git a/packages/mui-joy/src/RadioGroup/RadioGroupContext.ts b/packages/mui-joy/src/RadioGroup/RadioGroupContext.ts
index 98aa303457d668..cdaf2b2d0e2314 100644
--- a/packages/mui-joy/src/RadioGroup/RadioGroupContext.ts
+++ b/packages/mui-joy/src/RadioGroup/RadioGroupContext.ts
@@ -4,7 +4,7 @@ import { RadioProps } from '../Radio/RadioProps';
const RadioGroupContext = React.createContext<
| undefined
| (Pick & {
- row?: boolean;
+ orientation?: 'horizontal' | 'vertical';
name?: string;
value?: unknown;
onChange?: React.ChangeEventHandler;
diff --git a/packages/mui-joy/src/RadioGroup/RadioGroupProps.ts b/packages/mui-joy/src/RadioGroup/RadioGroupProps.ts
index 9ace70b64edad2..56614803acb19a 100644
--- a/packages/mui-joy/src/RadioGroup/RadioGroupProps.ts
+++ b/packages/mui-joy/src/RadioGroup/RadioGroupProps.ts
@@ -45,10 +45,10 @@ export interface RadioGroupTypeMap
*/
onChange?: (event: React.ChangeEvent) => void;
/**
- * If `true`, flex direction is set to 'row'.
- * @default false
+ * The component orientation.
+ * @default 'vertical'
*/
- row?: boolean;
+ orientation?: 'horizontal' | 'vertical';
/**
* The size of the component.
* @default 'md'
diff --git a/packages/mui-joy/src/RadioGroup/radioGroupClasses.ts b/packages/mui-joy/src/RadioGroup/radioGroupClasses.ts
index c9479df822a1ff..2e09301f2e0b2b 100644
--- a/packages/mui-joy/src/RadioGroup/radioGroupClasses.ts
+++ b/packages/mui-joy/src/RadioGroup/radioGroupClasses.ts
@@ -3,8 +3,6 @@ import { generateUtilityClass, generateUtilityClasses } from '../className';
export interface RadioGroupClasses {
/** Styles applied to the root element. */
root: string;
- /** Styles applied to the root element, if `row` is true. */
- row: string;
/** Styles applied to the root element if `size="sm"`. */
sizeSm: string;
/** Styles applied to the root element if `size="md"`. */
@@ -31,17 +29,20 @@ export interface RadioGroupClasses {
variantSoft: string;
/** Styles applied to the root element if `variant="solid"`. */
variantSolid: string;
+ /** Styles applied to the root element if `orientation="horizontal"`. */
+ horizontal: string;
+ /** Styles applied to the root element if `orientation="vertical"`. */
+ vertical: string;
}
export type RadioGroupClassKey = keyof RadioGroupClasses;
export function getRadioGroupUtilityClass(slot: string): string {
- return generateUtilityClass('JoyRadioGroup', slot);
+ return generateUtilityClass('MuiRadioGroup', slot);
}
-const radioGroupClasses: RadioGroupClasses = generateUtilityClasses('JoyRadioGroup', [
+const radioGroupClasses: RadioGroupClasses = generateUtilityClasses('MuiRadioGroup', [
'root',
- 'row',
'colorPrimary',
'colorNeutral',
'colorDanger',
@@ -55,6 +56,8 @@ const radioGroupClasses: RadioGroupClasses = generateUtilityClasses('JoyRadioGro
'sizeSm',
'sizeMd',
'sizeLg',
+ 'horizontal',
+ 'vertical',
]);
export default radioGroupClasses;
diff --git a/packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts b/packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts
index aff1b911f67bb9..80762be3e81fd0 100644
--- a/packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts
+++ b/packages/mui-joy/src/ScopedCssBaseline/scopedCssBaselineClasses.ts
@@ -8,9 +8,9 @@ export interface ScopedCssBaselineClasses {
export type ScopedCssBaselineClassKey = keyof ScopedCssBaselineClasses;
export function getScopedCssBaselineUtilityClass(slot: string): string {
- return generateUtilityClass('JoyScopedCssBaseline', slot);
+ return generateUtilityClass('MuiScopedCssBaseline', slot);
}
-const scopedCssBaselineClasses = generateUtilityClasses('JoyScopedCssBaseline', ['root']);
+const scopedCssBaselineClasses = generateUtilityClasses('MuiScopedCssBaseline', ['root']);
export default scopedCssBaselineClasses;
diff --git a/packages/mui-joy/src/Select/Select.tsx b/packages/mui-joy/src/Select/Select.tsx
index df805b7899ad71..25b4db952e6d5a 100644
--- a/packages/mui-joy/src/Select/Select.tsx
+++ b/packages/mui-joy/src/Select/Select.tsx
@@ -241,6 +241,7 @@ const SelectListbox = styled(StyledList, {
theme.vars.palette.background.popup,
'--List-item-stickyTop': 'calc(var(--List-padding, var(--List-divider-gap)) * -1)', // negative amount of the List's padding block
...scopedVariables,
+ minWidth: 'max-content', // prevent options from shrinking if some of them is wider than the Select's root.
outline: 0,
boxShadow: theme.shadow.md,
zIndex: 1000,
diff --git a/packages/mui-joy/src/Select/selectClasses.ts b/packages/mui-joy/src/Select/selectClasses.ts
index abee1ec07e1a77..be2fc2b5f5e545 100644
--- a/packages/mui-joy/src/Select/selectClasses.ts
+++ b/packages/mui-joy/src/Select/selectClasses.ts
@@ -54,10 +54,10 @@ export interface SelectClasses {
export type SelectClassKey = keyof SelectClasses;
export function getSelectUtilityClass(slot: string): string {
- return generateUtilityClass('JoySelect', slot);
+ return generateUtilityClass('MuiSelect', slot);
}
-const selectClasses: SelectClasses = generateUtilityClasses('JoySelect', [
+const selectClasses: SelectClasses = generateUtilityClasses('MuiSelect', [
'root',
'button',
'indicator',
diff --git a/packages/mui-joy/src/Sheet/Sheet.tsx b/packages/mui-joy/src/Sheet/Sheet.tsx
index e5387555386955..58dcfc607d58d0 100644
--- a/packages/mui-joy/src/Sheet/Sheet.tsx
+++ b/packages/mui-joy/src/Sheet/Sheet.tsx
@@ -4,6 +4,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import * as React from 'react';
+import { getPath } from '@mui/system';
import { useThemeProps } from '../styles';
import styled from '../styles/styled';
import { resolveSxValue } from '../styles/styleUtils';
@@ -32,12 +33,22 @@ export const SheetRoot = styled('div', {
})<{ ownerState: SheetOwnerState }>(({ theme, ownerState }) => {
const variantStyle = theme.variants[ownerState.variant!]?.[ownerState.color!];
const childRadius = resolveSxValue({ theme, ownerState }, 'borderRadius');
+ const bgcolor = resolveSxValue({ theme, ownerState }, 'bgcolor');
+ const backgroundColor = resolveSxValue({ theme, ownerState }, 'backgroundColor');
+ const background = resolveSxValue({ theme, ownerState }, 'background');
+ const resolvedBg =
+ (getPath(theme, `palette.${bgcolor}`) as string) ||
+ bgcolor ||
+ (getPath(theme, `palette.${backgroundColor}`) as string) ||
+ backgroundColor ||
+ background ||
+ variantStyle?.backgroundColor ||
+ variantStyle?.background ||
+ theme.vars.palette.background.surface;
return [
{
- '--List-item-stickyBackground':
- variantStyle?.backgroundColor ||
- variantStyle?.background ||
- theme.vars.palette.background.surface, // for sticky List
+ '--List-item-stickyBackground': resolvedBg, // for sticky List
+ '--Sheet-background': resolvedBg, // for sticky table cell
// minus the sheet's border width to have consistent radius between sheet and children
...(childRadius !== undefined && {
'--List-radius': `calc(${childRadius} - var(--variant-borderWidth, 0px))`,
diff --git a/packages/mui-joy/src/Sheet/sheetClasses.ts b/packages/mui-joy/src/Sheet/sheetClasses.ts
index 55ced58d433eef..fdb787316ae8d4 100644
--- a/packages/mui-joy/src/Sheet/sheetClasses.ts
+++ b/packages/mui-joy/src/Sheet/sheetClasses.ts
@@ -30,10 +30,10 @@ export interface SheetClasses {
export type SheetClassKey = keyof SheetClasses;
export function getSheetUtilityClass(slot: string): string {
- return generateUtilityClass('JoySheet', slot);
+ return generateUtilityClass('MuiSheet', slot);
}
-const sheetClasses: SheetClasses = generateUtilityClasses('JoySheet', [
+const sheetClasses: SheetClasses = generateUtilityClasses('MuiSheet', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/Slider/Slider.tsx b/packages/mui-joy/src/Slider/Slider.tsx
index b89d6263c8cb3f..0b4ef05bf277e3 100644
--- a/packages/mui-joy/src/Slider/Slider.tsx
+++ b/packages/mui-joy/src/Slider/Slider.tsx
@@ -17,8 +17,9 @@ import { SliderTypeMap, SliderOwnerState } from './SliderProps';
const valueToPercent = (value: number, min: number, max: number) =>
((value - min) * 100) / (max - min);
-const Identity = (x: any) => x;
-
+function Identity(x: any) {
+ return x;
+}
const useUtilityClasses = (ownerState: SliderOwnerState) => {
const { disabled, dragging, marked, orientation, track, variant, color, size } = ownerState;
@@ -775,7 +776,11 @@ Slider.propTypes /* remove-proptypes */ = {
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
/**
* A transformation function, to change the scale of the slider.
- * @default (x) => x
+ * @param {any} x
+ * @returns {any}
+ * @default function Identity(x) {
+ * return x;
+ * }
*/
scale: PropTypes.func,
/**
@@ -838,7 +843,11 @@ Slider.propTypes /* remove-proptypes */ = {
*
* - {number} value The value label's value to format
* - {number} index The value label's index to format
- * @default (x) => x
+ * @param {any} x
+ * @returns {any}
+ * @default function Identity(x) {
+ * return x;
+ * }
*/
valueLabelFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
diff --git a/packages/mui-joy/src/Slider/SliderProps.ts b/packages/mui-joy/src/Slider/SliderProps.ts
index 7fb6fcd843503f..b35ed5ab294397 100644
--- a/packages/mui-joy/src/Slider/SliderProps.ts
+++ b/packages/mui-joy/src/Slider/SliderProps.ts
@@ -51,6 +51,15 @@ export type SliderTypeMap = {
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps;
+ /**
+ * Controls when the value label is displayed:
+ *
+ * - `auto` the value label will display when the thumb is hovered or focused.
+ * - `on` will display persistently.
+ * - `off` will never display.
+ * @default 'off'
+ */
+ valueLabelDisplay?: 'on' | 'auto' | 'off';
/**
* The variant to use.
* @default 'solid'
diff --git a/packages/mui-joy/src/Slider/sliderClasses.ts b/packages/mui-joy/src/Slider/sliderClasses.ts
index 286229b5239959..0d36a1ab015376 100644
--- a/packages/mui-joy/src/Slider/sliderClasses.ts
+++ b/packages/mui-joy/src/Slider/sliderClasses.ts
@@ -66,10 +66,10 @@ export interface SliderClasses {
export type SliderClassKey = keyof SliderClasses;
export function getSliderUtilityClass(slot: string): string {
- return generateUtilityClass('JoySlider', slot);
+ return generateUtilityClass('MuiSlider', slot);
}
-const sliderClasses: SliderClasses = generateUtilityClasses('JoySlider', [
+const sliderClasses: SliderClasses = generateUtilityClasses('MuiSlider', [
'root',
'disabled',
'dragging',
diff --git a/packages/mui-joy/src/Stack/stackClasses.ts b/packages/mui-joy/src/Stack/stackClasses.ts
index c62c14861714c0..ce9bc161100fab 100644
--- a/packages/mui-joy/src/Stack/stackClasses.ts
+++ b/packages/mui-joy/src/Stack/stackClasses.ts
@@ -5,9 +5,9 @@ export type { StackClassKey } from '@mui/system';
export type { StackClasses };
export function getStackUtilityClass(slot: string): string {
- return generateUtilityClass('JoyStack', slot);
+ return generateUtilityClass('MuiStack', slot);
}
-const stackClasses: StackClasses = generateUtilityClasses('JoyStack', ['root']);
+const stackClasses: StackClasses = generateUtilityClasses('MuiStack', ['root']);
export default stackClasses;
diff --git a/packages/mui-joy/src/SvgIcon/svgIconClasses.ts b/packages/mui-joy/src/SvgIcon/svgIconClasses.ts
index 69a3ac7895e944..392a45ccf875eb 100644
--- a/packages/mui-joy/src/SvgIcon/svgIconClasses.ts
+++ b/packages/mui-joy/src/SvgIcon/svgIconClasses.ts
@@ -44,10 +44,10 @@ export interface SvgIconClasses {
export type SvgIconClassKey = keyof SvgIconClasses;
export function getSvgIconUtilityClass(slot: string): string {
- return generateUtilityClass('JoySvgIcon', slot);
+ return generateUtilityClass('MuiSvgIcon', slot);
}
-const svgIconClasses: SvgIconClasses = generateUtilityClasses('JoySvgIcon', [
+const svgIconClasses: SvgIconClasses = generateUtilityClasses('MuiSvgIcon', [
'root',
'colorInherit',
'colorPrimary',
diff --git a/packages/mui-joy/src/Switch/switchClasses.ts b/packages/mui-joy/src/Switch/switchClasses.ts
index 35c8b26356fb1b..37e0f3b486a87a 100644
--- a/packages/mui-joy/src/Switch/switchClasses.ts
+++ b/packages/mui-joy/src/Switch/switchClasses.ts
@@ -52,10 +52,10 @@ export interface SwitchClasses {
export type SwitchClassKey = keyof SwitchClasses;
export function getSwitchUtilityClass(slot: string): string {
- return generateUtilityClass('JoySwitch', slot);
+ return generateUtilityClass('MuiSwitch', slot);
}
-const switchClasses: SwitchClasses = generateUtilityClasses('JoySwitch', [
+const switchClasses: SwitchClasses = generateUtilityClasses('MuiSwitch', [
'root',
'checked',
'disabled',
diff --git a/packages/mui-joy/src/Tab/Tab.tsx b/packages/mui-joy/src/Tab/Tab.tsx
index 0281613caa9e72..9b47cba53b6118 100644
--- a/packages/mui-joy/src/Tab/Tab.tsx
+++ b/packages/mui-joy/src/Tab/Tab.tsx
@@ -41,14 +41,15 @@ const TabRoot = styled(StyledListItemButton, {
return {
justifyContent: 'center',
flexGrow: 1,
+ ...(!ownerState.row &&
+ ownerState.orientation === 'horizontal' && {
+ justifyContent: 'flex-start',
+ }),
...(ownerState.selected && {
boxShadow: theme.shadow.sm,
fontWeight: 'initial',
...(!variantStyle?.backgroundColor && {
backgroundColor: theme.vars.palette.background.surface,
- '&:hover': {
- backgroundColor: theme.vars.palette.background.surface,
- },
}),
}),
};
diff --git a/packages/mui-joy/src/Tab/tabClasses.ts b/packages/mui-joy/src/Tab/tabClasses.ts
index 0dea454def0396..4ccf7f0e8f8e31 100644
--- a/packages/mui-joy/src/Tab/tabClasses.ts
+++ b/packages/mui-joy/src/Tab/tabClasses.ts
@@ -40,10 +40,10 @@ export interface TabClasses {
export type TabClassKey = keyof TabClasses;
export function getTabUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTab', slot);
+ return generateUtilityClass('MuiTab', slot);
}
-const tabListClasses: TabClasses = generateUtilityClasses('JoyTab', [
+const tabListClasses: TabClasses = generateUtilityClasses('MuiTab', [
'root',
'disabled',
'focusVisible',
diff --git a/packages/mui-joy/src/TabList/TabList.tsx b/packages/mui-joy/src/TabList/TabList.tsx
index 8a497b9931c20d..1c3bd030a184b8 100644
--- a/packages/mui-joy/src/TabList/TabList.tsx
+++ b/packages/mui-joy/src/TabList/TabList.tsx
@@ -64,7 +64,6 @@ const TabList = React.forwardRef(function TabList(inProps, ref) {
const { isRtl, orientation, getRootProps, processChildren } = useTabsList({ ...props, ref });
- const row = orientation === 'horizontal';
const size = sizeProp ?? tabsSize;
const ownerState = {
@@ -74,7 +73,6 @@ const TabList = React.forwardRef(function TabList(inProps, ref) {
variant,
color,
size,
- row,
nesting: false,
};
@@ -97,7 +95,7 @@ const TabList = React.forwardRef(function TabList(inProps, ref) {
return (
// @ts-ignore conflicted ref types
-
+
{processedChildren}
diff --git a/packages/mui-joy/src/TabList/tabListClasses.ts b/packages/mui-joy/src/TabList/tabListClasses.ts
index 083a6ba599a11d..6d2e4778d9315b 100644
--- a/packages/mui-joy/src/TabList/tabListClasses.ts
+++ b/packages/mui-joy/src/TabList/tabListClasses.ts
@@ -36,10 +36,10 @@ export interface TabListClasses {
export type TabListClassKey = keyof TabListClasses;
export function getTabListUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTabList', slot);
+ return generateUtilityClass('MuiTabList', slot);
}
-const tabListClasses: TabListClasses = generateUtilityClasses('JoyTabList', [
+const tabListClasses: TabListClasses = generateUtilityClasses('MuiTabList', [
'root',
'colorPrimary',
'colorNeutral',
diff --git a/packages/mui-joy/src/TabPanel/TabPanel.tsx b/packages/mui-joy/src/TabPanel/TabPanel.tsx
index ea89caece2a300..3dc419ebd93e0b 100644
--- a/packages/mui-joy/src/TabPanel/TabPanel.tsx
+++ b/packages/mui-joy/src/TabPanel/TabPanel.tsx
@@ -12,10 +12,10 @@ import { getTabPanelUtilityClass } from './tabPanelClasses';
import { TabPanelOwnerState, TabPanelTypeMap } from './TabPanelProps';
const useUtilityClasses = (ownerState: TabPanelOwnerState) => {
- const { hidden, size } = ownerState;
+ const { hidden, size, orientation } = ownerState;
const slots = {
- root: ['root', hidden && 'hidden', size && `size${capitalize(size)}`],
+ root: ['root', hidden && 'hidden', size && `size${capitalize(size)}`, orientation],
};
return composeClasses(slots, getTabPanelUtilityClass, {});
@@ -55,9 +55,9 @@ const TabPanel = React.forwardRef(function TabPanel(inProps, ref) {
const { orientation } = useTabContext() || { orientation: 'horizontal' };
const tabsSize = React.useContext(SizeTabsContext);
- const { children, value, component, size: sizeProp, ...other } = props;
+ const { children, value = 0, component, size: sizeProp, ...other } = props;
- const { hidden, getRootProps } = useTabPanel(props);
+ const { hidden, getRootProps } = useTabPanel({ ...props, value });
const size = sizeProp ?? tabsSize;
@@ -118,8 +118,9 @@ TabPanel.propTypes /* remove-proptypes */ = {
]),
/**
* The value of the TabPanel. It will be shown when the Tab with the corresponding value is selected.
+ * @default 0
*/
- value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
} as any;
export default TabPanel;
diff --git a/packages/mui-joy/src/TabPanel/TabPanelProps.ts b/packages/mui-joy/src/TabPanel/TabPanelProps.ts
index a7694f0848d91c..4361b22cb599a3 100644
--- a/packages/mui-joy/src/TabPanel/TabPanelProps.ts
+++ b/packages/mui-joy/src/TabPanel/TabPanelProps.ts
@@ -9,7 +9,12 @@ export interface TabPanelPropsSizeOverrides {}
export interface TabPanelTypeMap {
props: P &
- Omit & {
+ Omit & {
+ /**
+ * The value of the TabPanel. It will be shown when the Tab with the corresponding value is selected.
+ * @default 0
+ */
+ value?: number | string;
/**
* The size of the component.
*/
diff --git a/packages/mui-joy/src/TabPanel/tabPanelClasses.ts b/packages/mui-joy/src/TabPanel/tabPanelClasses.ts
index 46be9d789d32ed..727adfec6d0cf6 100644
--- a/packages/mui-joy/src/TabPanel/tabPanelClasses.ts
+++ b/packages/mui-joy/src/TabPanel/tabPanelClasses.ts
@@ -11,20 +11,26 @@ export interface TabPanelClasses {
sizeMd: string;
/** Classname applied to the root element if `size="lg"`. */
sizeLg: string;
+ /** Styles applied to the root element if `orientation="horizontal"`. */
+ horizontal: string;
+ /** Styles applied to the root element if `orientation="vertical"`. */
+ vertical: string;
}
export type TabPanelClassKey = keyof TabPanelClasses;
export function getTabPanelUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTabPanel', slot);
+ return generateUtilityClass('MuiTabPanel', slot);
}
-const tabListClasses: TabPanelClasses = generateUtilityClasses('JoyTabPanel', [
+const tabListClasses: TabPanelClasses = generateUtilityClasses('MuiTabPanel', [
'root',
'hidden',
'sizeSm',
'sizeMd',
'sizeLg',
+ 'horizontal',
+ 'vertical',
]);
export default tabListClasses;
diff --git a/packages/mui-joy/src/Table/Table.test.tsx b/packages/mui-joy/src/Table/Table.test.tsx
new file mode 100644
index 00000000000000..b6723e6af6e6fe
--- /dev/null
+++ b/packages/mui-joy/src/Table/Table.test.tsx
@@ -0,0 +1,124 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { createRenderer, describeConformance, describeJoyColorInversion } from 'test/utils';
+import { ThemeProvider } from '@mui/joy/styles';
+import Table, { tableClasses as classes } from '@mui/joy/Table';
+import { unstable_capitalize as capitalize } from '@mui/utils';
+
+describe('', () => {
+ const { render } = createRenderer({ emotionCompat: true });
+
+ describeConformance(, () => ({
+ classes,
+ inheritComponent: 'table',
+ render,
+ ThemeProvider,
+ muiName: 'JoyTable',
+ refInstanceof: window.HTMLTableElement,
+ testComponentPropWith: 'header',
+ testVariantProps: { variant: 'solid' },
+ testCustomVariant: true,
+ skip: [
+ 'classesRoot',
+ 'componentsProp',
+ // Emotion `compat` is not set with `createMount` for these tests
+ 'componentProp',
+ 'mergeClassName',
+ 'propsSpread',
+ 'reactTestRenderer',
+ 'refForwarding',
+ ],
+ }));
+
+ describeJoyColorInversion(, { muiName: 'JoyTable', classes, emotionCompat: true });
+
+ describe('prop: variant', () => {
+ it('plain by default', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(classes.variantPlain);
+ });
+
+ (['plain', 'outlined', 'soft', 'solid'] as const).forEach((variant) => {
+ it(`should render ${variant}`, () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(
+ classes[`variant${capitalize(variant)}` as keyof typeof classes],
+ );
+ });
+ });
+ });
+
+ describe('prop: color', () => {
+ it('adds a neutral class by default', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(classes.colorNeutral);
+ });
+
+ (['primary', 'success', 'info', 'danger', 'neutral', 'warning'] as const).forEach((color) => {
+ it(`should render ${color}`, () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(
+ classes[`color${capitalize(color)}` as keyof typeof classes],
+ );
+ });
+ });
+ });
+
+ describe('prop: size', () => {
+ it('adds a md class by default', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(classes.sizeMd);
+ });
+
+ (['sm', 'lg'] as const).forEach((size) => {
+ it(`should render ${size}`, () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(
+ classes[`size${capitalize(size)}` as keyof typeof classes],
+ );
+ });
+ });
+ });
+
+ describe('prop: borderAxis', () => {
+ it('adds `xBetween` by default', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(classes.borderAxisXBetween);
+ });
+
+ (['none', 'x', 'xBetween', 'y', 'yBetween', 'both', 'bothBetween'] as const).forEach((axis) => {
+ it(`should render border-axis ${axis}`, () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(
+ classes[`borderAxis${capitalize(axis)}` as keyof typeof classes],
+ );
+ });
+ });
+ });
+
+ it('adds `hoverRow` class', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(classes.hoverRow);
+ });
+
+ it('adds `noWrap` class', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(classes.noWrap);
+ });
+
+ it('adds `stickyHeader` class', () => {
+ const { getByRole } = render();
+
+ expect(getByRole('table')).to.have.class(classes.stickyHeader);
+ });
+});
diff --git a/packages/mui-joy/src/Table/Table.tsx b/packages/mui-joy/src/Table/Table.tsx
new file mode 100644
index 00000000000000..03dd4879cc8f5a
--- /dev/null
+++ b/packages/mui-joy/src/Table/Table.tsx
@@ -0,0 +1,422 @@
+import * as React from 'react';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+import { unstable_capitalize as capitalize } from '@mui/utils';
+import { unstable_composeClasses as composeClasses } from '@mui/base';
+import { OverridableComponent } from '@mui/types';
+import { useThemeProps } from '../styles';
+import { useColorInversion } from '../styles/ColorInversion';
+import styled from '../styles/styled';
+import { getTableUtilityClass } from './tableClasses';
+import { TableProps, TableOwnerState, TableTypeMap } from './TableProps';
+import typographyClasses from '../Typography/typographyClasses';
+import { TypographyContext } from '../Typography/Typography';
+
+const useUtilityClasses = (ownerState: TableOwnerState) => {
+ const { size, variant, color, borderAxis, stickyHeader, noWrap, hoverRow } = ownerState;
+ const slots = {
+ root: [
+ 'root',
+ stickyHeader && 'stickyHeader',
+ noWrap && 'noWrap',
+ hoverRow && 'hoverRow',
+ borderAxis && `borderAxis${capitalize(borderAxis)}`,
+ variant && `variant${capitalize(variant)}`,
+ color && `color${capitalize(color)}`,
+ size && `size${capitalize(size)}`,
+ ],
+ };
+
+ return composeClasses(slots, getTableUtilityClass, {});
+};
+
+const tableSelector = {
+ getColumn(col: number | string) {
+ if (typeof col === 'number' && col < 0) {
+ return `& tr > *:nth-last-child(${Math.abs(col)})`;
+ }
+ return `& tr > *:nth-child(${col})`;
+ },
+ /**
+ * Except first column
+ */
+ getColumnExceptFirst() {
+ return '& tr > *:not(:first-child)';
+ },
+ /**
+ * Every cell in the table
+ */
+ getCell() {
+ return '& th, & td';
+ },
+ /**
+ * `th` cell of the table (could exist in the body)
+ */
+ getHeadCell() {
+ return '& th';
+ },
+ /**
+ * Only the cell of `thead`
+ */
+ getHeaderCell() {
+ return '& thead th';
+ },
+ getHeaderCellOfRow(row: number | string) {
+ return `& thead tr:nth-child(${row}) th`;
+ },
+ getBottomHeaderCell() {
+ return '& thead th:not([colspan])';
+ },
+ getHeaderNestedFirstColumn() {
+ return '& thead tr:not(:first-of-type) th:not([colspan]):first-child';
+ },
+ /**
+ * The body cell that contains data
+ */
+ getDataCell() {
+ return '& td';
+ },
+ getDataCellExceptLastRow() {
+ return '& tr:not(:last-child) > td';
+ },
+ /**
+ * The body cell either `td` or `th`
+ */
+ getBodyCellExceptLastRow() {
+ return `${this.getDataCellExceptLastRow()}, & tr:not(:last-child) > th[scope="row"]`;
+ },
+ getBodyCellOfRow(row: number | string) {
+ if (typeof row === 'number' && row < 0) {
+ return `& tbody tr:nth-last-child(${Math.abs(row)}) td, & tbody tr:nth-last-child(${Math.abs(
+ row,
+ )}) th[scope="row"]`;
+ }
+ return `& tbody tr:nth-child(${row}) td, & tbody tr:nth-child(${row}) th[scope="row"]`;
+ },
+ getBodyRow(row?: number | string) {
+ if (row === undefined) {
+ return `& tbody tr`;
+ }
+ return `& tbody tr:nth-child(${row})`;
+ },
+ getFooterCell() {
+ return '& tfoot th, & tfoot td';
+ },
+};
+
+const TableRoot = styled('table', {
+ name: 'JoyTable',
+ slot: 'Root',
+ overridesResolver: (props, styles) => styles.root,
+})<{ ownerState: TableOwnerState }>(({ theme, ownerState }) => {
+ const variantStyle = theme.variants[ownerState.variant!]?.[ownerState.color!];
+ return [
+ {
+ '--Table-headerUnderlineThickness': '2px',
+ '--TableCell-borderColor': variantStyle?.borderColor ?? theme.vars.palette.divider,
+ '--TableCell-headBackground': `var(--Sheet-background, ${theme.vars.palette.background.surface})`,
+ ...(ownerState.size === 'sm' && {
+ '--private_TableCell-height': 'var(--TableCell-height, 32px)',
+ '--TableCell-paddingX': '0.25rem',
+ '--TableCell-paddingY': '0.25rem',
+ fontSize: theme.vars.fontSize.xs,
+ }),
+ ...(ownerState.size === 'md' && {
+ '--private_TableCell-height': 'var(--TableCell-height, 40px)',
+ '--TableCell-paddingX': '0.5rem',
+ '--TableCell-paddingY': '0.375rem',
+ fontSize: theme.vars.fontSize.sm,
+ }),
+ ...(ownerState.size === 'lg' && {
+ '--private_TableCell-height': 'var(--TableCell-height, 48px)',
+ '--TableCell-paddingX': '0.75rem',
+ '--TableCell-paddingY': '0.5rem',
+ fontSize: theme.vars.fontSize.md,
+ }),
+ tableLayout: 'fixed',
+ width: '100%',
+ borderSpacing: '0px',
+ borderCollapse: 'separate',
+ color: theme.vars.palette.text.primary,
+ ...theme.variants[ownerState.variant!]?.[ownerState.color!],
+ '& caption': {
+ color: theme.vars.palette.text.tertiary,
+ padding: 'calc(2 * var(--TableCell-paddingY)) var(--TableCell-paddingX)',
+ },
+ [tableSelector.getDataCell()]: {
+ padding: 'var(--TableCell-paddingY) var(--TableCell-paddingX)',
+ height: 'var(--private_TableCell-height)',
+ borderColor: 'var(--TableCell-borderColor)', // must come after border bottom
+ backgroundColor: 'var(--TableCell-dataBackground)', // use `background-color` in case the Sheet has gradient background
+ ...(ownerState.noWrap && {
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ }),
+ },
+ [tableSelector.getHeadCell()]: {
+ textAlign: 'left',
+ padding: 'var(--TableCell-paddingY) var(--TableCell-paddingX)',
+ backgroundColor: 'var(--TableCell-headBackground)', // use `background-color` in case the Sheet has gradient background
+ height: 'var(--private_TableCell-height)',
+ fontWeight: theme.vars.fontWeight.lg,
+ borderColor: 'var(--TableCell-borderColor)',
+ color: theme.vars.palette.text.secondary,
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ },
+ [tableSelector.getHeaderCell()]: {
+ verticalAlign: 'bottom',
+ // Automatic radius adjustment with Sheet
+ '&:first-child': {
+ borderTopLeftRadius: 'var(--TableCell-cornerRadius, var(--internal-action-radius))',
+ },
+ '&:last-child': {
+ borderTopRightRadius: 'var(--TableCell-cornerRadius, var(--internal-action-radius))',
+ },
+ },
+ '& tfoot tr > *': {
+ backgroundColor: `var(--TableCell-footBackground, ${theme.vars.palette.background.level1})`,
+ // Automatic radius adjustment with Sheet
+ '&:first-child': {
+ borderBottomLeftRadius: 'var(--TableCell-cornerRadius, var(--internal-action-radius))',
+ },
+ '&:last-child': {
+ borderBottomRightRadius: 'var(--TableCell-cornerRadius, var(--internal-action-radius))',
+ },
+ },
+ },
+ (ownerState.borderAxis?.startsWith('x') || ownerState.borderAxis?.startsWith('both')) && {
+ // insert border between rows
+ [tableSelector.getHeaderCell()]: {
+ borderBottomWidth: 1,
+ borderBottomStyle: 'solid',
+ },
+ [tableSelector.getBottomHeaderCell()]: {
+ borderBottomWidth: 'var(--Table-headerUnderlineThickness)',
+ borderBottomStyle: 'solid',
+ },
+ [tableSelector.getBodyCellExceptLastRow()]: {
+ borderBottomWidth: 1,
+ borderBottomStyle: 'solid',
+ },
+ [tableSelector.getFooterCell()]: {
+ borderTopWidth: 1,
+ borderTopStyle: 'solid',
+ },
+ },
+ (ownerState.borderAxis?.startsWith('y') || ownerState.borderAxis?.startsWith('both')) && {
+ // insert border between columns
+ [`${tableSelector.getColumnExceptFirst()}, ${tableSelector.getHeaderNestedFirstColumn()}`]: {
+ borderLeftWidth: 1,
+ borderLeftStyle: 'solid',
+ },
+ },
+ (ownerState.borderAxis === 'x' || ownerState.borderAxis === 'both') && {
+ // insert border at the top of header and bottom of body
+ [tableSelector.getHeaderCellOfRow(1)]: {
+ borderTopWidth: 1,
+ borderTopStyle: 'solid',
+ },
+ [tableSelector.getBodyCellOfRow(-1)]: {
+ borderBottomWidth: 1,
+ borderBottomStyle: 'solid',
+ },
+ [tableSelector.getFooterCell()]: {
+ borderBottomWidth: 1,
+ borderBottomStyle: 'solid',
+ },
+ },
+ (ownerState.borderAxis === 'y' || ownerState.borderAxis === 'both') && {
+ // insert border on the left of first column and right of the last column
+ [tableSelector.getColumn(1)]: {
+ borderLeftWidth: 1,
+ borderLeftStyle: 'solid',
+ },
+ [tableSelector.getColumn(-1)]: {
+ borderRightWidth: 1,
+ borderRightStyle: 'solid',
+ },
+ },
+ ownerState.stripe && {
+ [tableSelector.getBodyRow(ownerState.stripe)]: {
+ // For customization, a table cell can look for this variable with a fallback value.
+ background: `var(--TableRow-stripeBackground, ${theme.vars.palette.background.level1})`,
+ color: theme.vars.palette.text.primary,
+ },
+ },
+ ownerState.hoverRow && {
+ [tableSelector.getBodyRow()]: {
+ '&:hover': {
+ background: `var(--TableRow-hoverBackground, ${theme.vars.palette.background.level2})`,
+ },
+ },
+ },
+ ownerState.stickyHeader && {
+ // The column header
+ [tableSelector.getHeadCell()]: {
+ position: 'sticky',
+ top: 0,
+ },
+ [tableSelector.getHeaderCell()]: {
+ zIndex: 1,
+ },
+ [tableSelector.getHeaderCellOfRow(2)]: {
+ // support upto 2 rows for the sticky header
+ top: 'var(--private_TableCell-height)',
+ },
+ },
+ {
+ // Typography integration
+ [tableSelector.getCell()]: {
+ [`& .${typographyClasses.noWrap}`]: {
+ display: 'block',
+ },
+ },
+ },
+ ];
+});
+
+const Table = React.forwardRef(function Table(inProps, ref) {
+ const props = useThemeProps({
+ props: inProps,
+ name: 'JoyTable',
+ });
+
+ const {
+ className,
+ component,
+ children,
+ borderAxis = 'xBetween',
+ hoverRow = false,
+ noWrap = false,
+ size = 'md',
+ variant = 'plain',
+ color: colorProp = 'neutral',
+ stripe,
+ stickyHeader = false,
+ ...other
+ } = props;
+ const { getColor } = useColorInversion(variant);
+ const color = getColor(inProps.color, colorProp);
+
+ const ownerState = {
+ ...props,
+ borderAxis,
+ hoverRow,
+ noWrap,
+ component,
+ size,
+ color,
+ variant,
+ stripe,
+ stickyHeader,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ return (
+
+
+ {children}
+
+
+ );
+}) as OverridableComponent;
+
+Table.propTypes /* remove-proptypes */ = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * The axis to display a border on the table cell.
+ * @default 'xBetween'
+ */
+ borderAxis: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['both', 'bothBetween', 'none', 'x', 'xBetween', 'y', 'yBetween']),
+ PropTypes.string,
+ ]),
+ /**
+ * Children of the table
+ */
+ children: PropTypes.node,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The color of the component. It supports those theme colors that make sense for this component.
+ * @default 'neutral'
+ */
+ color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']),
+ PropTypes.string,
+ ]),
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * If `true`, the table row will shade on hover.
+ * @default false
+ */
+ hoverRow: PropTypes.bool,
+ /**
+ * If `true`, the body cells will not wrap, but instead will truncate with a text overflow ellipsis.
+ *
+ * Note: Header cells are always truncated with overflow ellipsis.
+ *
+ * @default false
+ */
+ noWrap: PropTypes.bool,
+ /**
+ * The size of the component.
+ * It accepts theme values between 'sm' and 'lg'.
+ * @default 'md'
+ */
+ size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['sm', 'md', 'lg']),
+ PropTypes.string,
+ ]),
+ /**
+ * Set the header sticky.
+ *
+ * ā ļø It doesn't work with IE11.
+ * @default false
+ */
+ stickyHeader: PropTypes.bool,
+ /**
+ * The odd or even row of the table body will have subtle background color.
+ */
+ stripe: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['odd', 'even']),
+ PropTypes.string,
+ ]),
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
+ PropTypes.func,
+ PropTypes.object,
+ ]),
+ /**
+ * The variant to use.
+ * @default 'plain'
+ */
+ variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['outlined', 'plain', 'soft', 'solid']),
+ PropTypes.string,
+ ]),
+} as any;
+
+export default Table;
diff --git a/packages/mui-joy/src/Table/TableProps.ts b/packages/mui-joy/src/Table/TableProps.ts
new file mode 100644
index 00000000000000..79dcdfacd3bdd2
--- /dev/null
+++ b/packages/mui-joy/src/Table/TableProps.ts
@@ -0,0 +1,79 @@
+import * as React from 'react';
+import { OverridableStringUnion, OverrideProps } from '@mui/types';
+import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../styles/types';
+
+export type TableSlot = 'root';
+
+export interface TablePropsSizeOverrides {}
+export interface TablePropsColorOverrides {}
+export interface TablePropsVariantOverrides {}
+export interface TablePropsBorderAxisOverrides {}
+
+export interface TableTypeMap {
+ props: P & {
+ /**
+ * The axis to display a border on the table cell.
+ * @default 'xBetween'
+ */
+ borderAxis?: OverridableStringUnion<
+ 'none' | 'x' | 'xBetween' | 'y' | 'yBetween' | 'both' | 'bothBetween',
+ TablePropsBorderAxisOverrides
+ >;
+ /**
+ * Children of the table
+ */
+ children?: React.ReactNode;
+ /**
+ * The color of the component. It supports those theme colors that make sense for this component.
+ * @default 'neutral'
+ */
+ color?: OverridableStringUnion;
+ /**
+ * If `true`, the table row will shade on hover.
+ * @default false
+ */
+ hoverRow?: boolean;
+ /**
+ * If `true`, the body cells will not wrap, but instead will truncate with a text overflow ellipsis.
+ *
+ * Note: Header cells are always truncated with overflow ellipsis.
+ *
+ * @default false
+ */
+ noWrap?: boolean;
+ /**
+ * The size of the component.
+ * It accepts theme values between 'sm' and 'lg'.
+ * @default 'md'
+ */
+ size?: OverridableStringUnion<'sm' | 'md' | 'lg', TablePropsSizeOverrides>;
+ /**
+ * Set the header sticky.
+ *
+ * ā ļø It doesn't work with IE11.
+ * @default false
+ */
+ stickyHeader?: boolean;
+ /**
+ * The odd or even row of the table body will have subtle background color.
+ */
+ stripe?: 'odd' | 'even' | (string & Record);
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ /**
+ * The variant to use.
+ * @default 'plain'
+ */
+ variant?: OverridableStringUnion;
+ };
+ defaultComponent: D;
+}
+
+export type TableProps<
+ D extends React.ElementType = TableTypeMap['defaultComponent'],
+ P = { component?: React.ElementType },
+> = OverrideProps, D>;
+
+export interface TableOwnerState extends ApplyColorInversion {}
diff --git a/packages/mui-joy/src/Table/index.ts b/packages/mui-joy/src/Table/index.ts
new file mode 100644
index 00000000000000..0bcd389394be06
--- /dev/null
+++ b/packages/mui-joy/src/Table/index.ts
@@ -0,0 +1,4 @@
+export { default } from './Table';
+export * from './tableClasses';
+export { default as tableClasses } from './tableClasses';
+export * from './TableProps';
diff --git a/packages/mui-joy/src/Table/tableClasses.ts b/packages/mui-joy/src/Table/tableClasses.ts
new file mode 100644
index 00000000000000..9164a3216cce28
--- /dev/null
+++ b/packages/mui-joy/src/Table/tableClasses.ts
@@ -0,0 +1,90 @@
+import { generateUtilityClass, generateUtilityClasses } from '../className';
+
+export interface TableClasses {
+ /** Styles applied to the root element. */
+ root: string;
+ /** Styles applied to the root element if `color="primary"`. */
+ colorPrimary: string;
+ /** Styles applied to the root element if `color="neutral"`. */
+ colorNeutral: string;
+ /** Styles applied to the root element if `color="danger"`. */
+ colorDanger: string;
+ /** Styles applied to the root element if `color="info"`. */
+ colorInfo: string;
+ /** Styles applied to the root element if `color="success"`. */
+ colorSuccess: string;
+ /** Styles applied to the root element if `color="warning"`. */
+ colorWarning: string;
+ /** Styles applied to the root element when color inversion is triggered. */
+ colorContext: string;
+ /** Styles applied to the root element if `variant="plain"`. */
+ variantPlain: string;
+ /** Styles applied to the root element if `variant="outlined"`. */
+ variantOutlined: string;
+ /** Styles applied to the root element if `variant="soft"`. */
+ variantSoft: string;
+ /** Styles applied to the root element if `variant="solid"`. */
+ variantSolid: string;
+ /** Styles applied to the root element if `size="sm"`. */
+ sizeSm: string;
+ /** Styles applied to the root element if `size="md"`. */
+ sizeMd: string;
+ /** Styles applied to the root element if `size="lg"`. */
+ sizeLg: string;
+ /** Styles applied to the root element if `stickyHeader` is true. */
+ stickyHeader: string;
+ /** Styles applied to the root element if `noWrap` is true. */
+ noWrap: string;
+ /** Styles applied to the root element if `hoverRow` is true. */
+ hoverRow: string;
+ /** Styles applied to the root element if `borderAxis="none"`. */
+ borderAxisNone: string;
+ /** Styles applied to the root element if `borderAxis="x"`. */
+ borderAxisX: string;
+ /** Styles applied to the root element if `borderAxis="xBetween"`. */
+ borderAxisXBetween: string;
+ /** Styles applied to the root element if `borderAxis="y"`. */
+ borderAxisY: string;
+ /** Styles applied to the root element if `borderAxis="yBetween"`. */
+ borderAxisYBetween: string;
+ /** Styles applied to the root element if `borderAxis="both"`. */
+ borderAxisBoth: string;
+ /** Styles applied to the root element if `borderAxis="bothBetween"`. */
+ borderAxisBothBetween: string;
+}
+
+export type TableClassKey = keyof TableClasses;
+
+export function getTableUtilityClass(slot: string): string {
+ return generateUtilityClass('JoyTable', slot);
+}
+
+const tableClasses: TableClasses = generateUtilityClasses('JoyTable', [
+ 'root',
+ 'colorPrimary',
+ 'colorNeutral',
+ 'colorDanger',
+ 'colorInfo',
+ 'colorSuccess',
+ 'colorWarning',
+ 'colorContext',
+ 'variantPlain',
+ 'variantOutlined',
+ 'variantSoft',
+ 'variantSolid',
+ 'sizeSm',
+ 'sizeMd',
+ 'sizeLg',
+ 'stickyHeader',
+ 'noWrap',
+ 'hoverRow',
+ 'borderAxisNone',
+ 'borderAxisX',
+ 'borderAxisXBetween',
+ 'borderAxisY',
+ 'borderAxisYBetween',
+ 'borderAxisBoth',
+ 'borderAxisBothBetween',
+]);
+
+export default tableClasses;
diff --git a/packages/mui-joy/src/Tabs/Tabs.tsx b/packages/mui-joy/src/Tabs/Tabs.tsx
index a6526f4a7ca4a0..fb9f8747cb053a 100644
--- a/packages/mui-joy/src/Tabs/Tabs.tsx
+++ b/packages/mui-joy/src/Tabs/Tabs.tsx
@@ -46,6 +46,7 @@ const TabsRoot = styled(SheetRoot, {
flexDirection: 'column',
...(ownerState.orientation === 'vertical' && {
flexDirection: 'row',
+ alignItems: 'flex-start',
}),
}));
@@ -58,7 +59,7 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
const {
children,
value: valueProp,
- defaultValue,
+ defaultValue = valueProp === undefined ? 0 : undefined,
orientation = 'horizontal',
direction = 'ltr',
component,
@@ -72,7 +73,7 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
const { getColor } = useColorInversion(variant);
const color = getColor(inProps.color, colorProp);
- const { tabsContextValue } = useTabs({ ...props, orientation });
+ const { tabsContextValue } = useTabs({ ...props, orientation, defaultValue });
const ownerState = {
...props,
diff --git a/packages/mui-joy/src/Tabs/tabsClasses.ts b/packages/mui-joy/src/Tabs/tabsClasses.ts
index b3db18a28400a1..f3b25f08c13333 100644
--- a/packages/mui-joy/src/Tabs/tabsClasses.ts
+++ b/packages/mui-joy/src/Tabs/tabsClasses.ts
@@ -40,10 +40,10 @@ export interface TabsClasses {
export type TabsClassKey = keyof TabsClasses;
export function getTabsUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTabs', slot);
+ return generateUtilityClass('MuiTabs', slot);
}
-const tabListClasses: TabsClasses = generateUtilityClasses('JoyTabs', [
+const tabListClasses: TabsClasses = generateUtilityClasses('MuiTabs', [
'root',
'horizontal',
'vertical',
diff --git a/packages/mui-joy/src/TextField/TextField.spec.tsx b/packages/mui-joy/src/TextField/TextField.spec.tsx
deleted file mode 100644
index 66181e546a7782..00000000000000
--- a/packages/mui-joy/src/TextField/TextField.spec.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as React from 'react';
-import TextField from '@mui/joy/TextField';
-import { expectType } from '@mui/types';
-
- }
- endDecorator={
}
-/>;
- {
- expectType, typeof event>(event);
- }}
-/>;
diff --git a/packages/mui-joy/src/TextField/TextField.test.js b/packages/mui-joy/src/TextField/TextField.test.js
deleted file mode 100644
index 848c8ec5ec8c75..00000000000000
--- a/packages/mui-joy/src/TextField/TextField.test.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import { spy } from 'sinon';
-import { describeConformance, createRenderer, act, fireEvent } from 'test/utils';
-import { ThemeProvider } from '@mui/joy/styles';
-import TextField, { textFieldClasses as classes } from '@mui/joy/TextField';
-import { inputClasses } from '@mui/joy/Input';
-import { formHelperTextClasses } from '@mui/joy/FormHelperText';
-
-describe('Joy ', () => {
- const { render } = createRenderer();
-
- describeConformance( , () => ({
- classes,
- inheritComponent: 'div',
- render,
- ThemeProvider,
- muiName: 'JoyTextField',
- refInstanceof: window.HTMLDivElement,
- skip: ['componentsProp', 'classesRoot', 'themeVariants'],
- }));
-
- it('should have root className', () => {
- const { container } = render( );
- expect(container.firstChild).to.have.class(classes.root);
- });
-
- it('should accept className prop', () => {
- const { container } = render( );
- expect(container.firstChild).to.have.class('foo-bar');
- });
-
- it('should provide proper id to elements', () => {
- const { container, getByRole } = render(
- ,
- );
- expect(getByRole('textbox')).to.have.attribute('id', 'textfield');
- expect(container.querySelector('label')).to.have.attribute('id', 'textfield-label');
- expect(container.querySelector(`.${formHelperTextClasses.root}`)).to.have.attribute(
- 'id',
- 'textfield-helper-text',
- );
- });
-
- it('should have configurable variant', () => {
- const { container, rerender } = render( );
- expect(container.firstChild).to.have.class(classes.variantOutlined); // default variant
-
- rerender( );
- expect(container.firstChild).to.have.class(classes.variantSoft);
- expect(container.querySelector(`.${inputClasses.root}`)).to.have.class(
- inputClasses.variantSoft,
- );
- });
-
- it('should have configurable size', () => {
- const { container, rerender } = render( );
- expect(container.firstChild).to.have.class(classes.sizeMd); // default variant
-
- rerender( );
- expect(container.firstChild).to.have.class(classes.sizeSm);
- expect(container.querySelector(`.${inputClasses.root}`)).to.have.class(inputClasses.sizeSm);
- });
-
- it('should have configurable color', () => {
- const { container } = render( );
-
- expect(container.firstChild).to.have.class(classes.colorPrimary);
- expect(container.querySelector(`.${inputClasses.root}`)).to.have.class(
- inputClasses.colorPrimary,
- );
- });
-
- it('should pass `type` to Input', () => {
- const { getByLabelText } = render( );
-
- expect(getByLabelText(/password/)).to.have.attribute('type', 'password');
- });
-
- it('should pass `name` to Input', () => {
- const { getByRole } = render( );
-
- expect(getByRole('textbox')).to.have.attribute('name', 'username');
- });
-
- it('should be error', () => {
- const { container } = render( );
-
- expect(container.firstChild).to.have.class(classes.error);
- expect(container.querySelector(`.${inputClasses.root}`)).to.have.class(inputClasses.error);
- });
-
- it('should be disabled', () => {
- const { container } = render( );
-
- expect(container.firstChild).to.have.class(classes.disabled);
- expect(container.querySelector(`.${inputClasses.root}`)).to.have.class(inputClasses.disabled);
- });
-
- it('should have fullWidth classes', () => {
- const { container } = render( );
-
- expect(container.firstChild).to.have.class(classes.fullWidth);
- expect(container.querySelector(`.${inputClasses.root}`)).to.have.class(inputClasses.fullWidth);
- });
-
- it('should show asterisk if required', () => {
- const { getByRole, container } = render(
- label} />,
- );
-
- expect(container.querySelector(`label`)).to.have.text('label\u2009*');
- expect(getByRole('textbox')).to.have.attribute('required');
- });
-
- it('should accept defaultValue', () => {
- const { getByRole } = render( );
- expect(getByRole('textbox')).to.have.value('foo');
- });
-
- it('should pass value and handler props to Input', () => {
- const handleChange = spy();
- const handleFocus = spy();
- const handleBlur = spy();
- const handleKeyUp = spy();
- const handleKeyDown = spy();
- const { getByRole } = render(
- ,
- );
- const input = getByRole('textbox');
-
- expect(input).to.have.value('bar');
-
- act(() => {
- input.focus();
- });
- expect(handleFocus.callCount).to.equal(1);
-
- fireEvent.keyDown(input, { key: 'a' });
- expect(handleKeyDown.callCount).to.equal(1);
-
- fireEvent.change(input, { target: { value: 'a' } });
- expect(handleChange.callCount).to.equal(1);
-
- fireEvent.keyUp(input, { key: 'a' });
- expect(handleKeyUp.callCount).to.equal(1);
-
- act(() => {
- input.blur();
- });
- expect(handleBlur.callCount).to.equal(1);
- });
-
- it('should accept startDecorator', () => {
- const { getByText } = render( );
- expect(getByText('foo')).toBeVisible();
- });
-
- it('should accept endDecorator', () => {
- const { getByText } = render( );
- expect(getByText('bar')).toBeVisible();
- });
-});
diff --git a/packages/mui-joy/src/TextField/TextField.tsx b/packages/mui-joy/src/TextField/TextField.tsx
index 4059e705590488..cfcd020fa3926a 100644
--- a/packages/mui-joy/src/TextField/TextField.tsx
+++ b/packages/mui-joy/src/TextField/TextField.tsx
@@ -1,356 +1,8 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import clsx from 'clsx';
-import { OverridableComponent } from '@mui/types';
-import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils';
-import composeClasses from '@mui/base/composeClasses';
-import FormLabel from '../FormLabel';
-import FormHelperText from '../FormHelperText';
-import JoyInput from '../Input';
-import { styled, useThemeProps } from '../styles';
-import useSlot from '../utils/useSlot';
-import { TextFieldOwnerState, TextFieldTypeMap } from './TextFieldProps';
-import textFieldClasses, { getTextFieldUtilityClass } from './textFieldClasses';
-
-const useUtilityClasses = (ownerState: TextFieldOwnerState) => {
- const { error, disabled, variant, size, color, fullWidth } = ownerState;
- const slots = {
- root: [
- 'root',
- error && 'error',
- disabled && 'disabled',
- fullWidth && 'fullWidth',
- variant && `variant${capitalize(variant)}`,
- size && `size${capitalize(size)}`,
- color && `color${capitalize(color)}`,
- ],
- };
-
- return composeClasses(slots, getTextFieldUtilityClass, {});
-};
-
-const TextFieldRoot = styled('div', {
- name: 'JoyTextField',
- slot: 'Root',
- overridesResolver: (props, styles) => styles.root,
-})<{ ownerState: TextFieldOwnerState }>(({ theme, ownerState }) => ({
- '--FormLabel-margin': '0 0 0.25rem 0',
- '--FormHelperText-margin': '0.25rem 0 0 0',
- '--FormLabel-asterisk-color': theme.vars.palette.danger[500],
- '--FormHelperText-color': theme.vars.palette[ownerState.color!]?.[500],
- ...(ownerState.size === 'sm' && {
- '--FormHelperText-fontSize': theme.vars.fontSize.xs,
- '--FormLabel-fontSize': theme.vars.fontSize.xs,
- }),
- [`&.${textFieldClasses.error}`]: {
- '--FormHelperText-color': theme.vars.palette.danger[500],
- },
- [`&.${textFieldClasses.disabled}`]: {
- '--FormLabel-color': theme.vars.palette[ownerState.color || 'neutral']?.plainDisabledColor,
- '--FormHelperText-color': theme.vars.palette[ownerState.color || 'neutral']?.plainDisabledColor,
- },
- display: 'flex',
- flexDirection: 'column',
- ...(ownerState.fullWidth && {
- width: '100%',
- }),
-}));
-
-const TextField = React.forwardRef(function TextField(inProps, ref) {
- const props = useThemeProps({
- props: inProps,
- name: 'JoyTextField',
- });
-
- const {
- children,
- className,
- component,
- slots = {},
- slotProps = {},
- label,
- helperText,
- id: idOverride,
- autoComplete,
- autoFocus,
- placeholder,
- defaultValue,
- value,
- name,
- onBlur,
- onChange,
- onFocus,
- color,
- disabled = false,
- error = false,
- required = false,
- size = 'md',
- variant = 'outlined',
- fullWidth = false,
- type = 'text',
- startDecorator,
- endDecorator,
- ...other
- } = props;
-
- const id = useId(idOverride);
- const helperTextId = helperText && id ? `${id}-helper-text` : undefined;
- const formLabelId = label && id ? `${id}-label` : undefined;
-
- const ownerState = {
- label,
- helperText,
- startDecorator,
- endDecorator,
- disabled,
- error,
- required,
- size,
- variant,
- fullWidth,
- type,
- ...props,
- };
-
- const classes = useUtilityClasses(ownerState);
-
- const [SlotRoot, rootProps] = useSlot('root', {
- ref,
- className: clsx(classes.root, className),
- elementType: TextFieldRoot,
- // @ts-ignore internal logic
- externalForwardedProps: { ...other, component, slots, slotProps },
- ownerState,
- });
-
- const Input = slots.input || JoyInput;
-
- return (
- // @ts-ignore neglect 'context' color
-
- {label && (
-
- {label}
-
- )}
-
-
- {helperText && (
-
- {helperText}
-
- )}
-
+/**
+ * @ignore - do not document.
+ */
+export default (function DeletedTextField() {
+ throw new Error(
+ 'MUI: `TextField` component has been removed in favor of Input composition.\n\nTo migrate, run `npx @mui/codemod v5.0.0/joy-text-field-to-input `.\nFor the codemod detail, visit https://github.com/mui/material-ui/blob/master/packages/mui-codemod/README.md#joy-text-field-to-input\n\nTo learn more why it has been removed, visit the RFC https://github.com/mui/material-ui/issues/34176',
);
-}) as OverridableComponent;
-
-TextField.propTypes /* remove-proptypes */ = {
- // ----------------------------- Warning --------------------------------
- // | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit TypeScript types and run "yarn proptypes" |
- // ----------------------------------------------------------------------
- /**
- * @ignore
- */
- autoComplete: PropTypes.string,
- /**
- * @ignore
- */
- autoFocus: PropTypes.bool,
- /**
- * @ignore
- */
- children: PropTypes.node,
- /**
- * @ignore
- */
- className: PropTypes.string,
- /**
- * The color of the component. It supports those theme colors that make sense for this component.
- * @default 'neutral'
- */
- color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
- PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']),
- PropTypes.string,
- ]),
- /**
- * The component used for the root node.
- * Either a string to use a HTML element or a component.
- */
- component: PropTypes.elementType,
- /**
- * @ignore
- */
- defaultValue: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.string),
- PropTypes.number,
- PropTypes.string,
- ]),
- /**
- * @ignore
- */
- disabled: PropTypes.bool,
- /**
- * Trailing adornment for this input.
- */
- endDecorator: PropTypes.node,
- /**
- * If `true`, the `input` will indicate an error.
- * The prop defaults to the value (`false`) inherited from the parent FormControl component.
- */
- error: PropTypes.bool,
- /**
- * If `true`, the button will take up the full width of its container.
- * @default false
- */
- fullWidth: PropTypes.bool,
- /**
- * The helper text content.
- */
- helperText: PropTypes.node,
- /**
- * The id of the `input` element.
- * Use this prop to make `label` and `helperText` accessible for screen readers.
- */
- id: PropTypes.string,
- /**
- * The label content.
- */
- label: PropTypes.node,
- /**
- * Name attribute of the `input` element.
- */
- name: PropTypes.string,
- /**
- * @ignore
- */
- onBlur: PropTypes.func,
- /**
- * @ignore
- */
- onChange: PropTypes.func,
- /**
- * @ignore
- */
- onFocus: PropTypes.func,
- /**
- * The short hint displayed in the `input` before the user enters a value.
- */
- placeholder: PropTypes.string,
- /**
- * @ignore
- */
- required: PropTypes.bool,
- /**
- * The size of the component.
- * @default 'md'
- */
- size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
- PropTypes.oneOf(['sm', 'md', 'lg']),
- PropTypes.string,
- ]),
- /**
- * @ignore
- */
- slotProps: PropTypes.shape({
- helperText: PropTypes.object,
- input: PropTypes.object,
- label: PropTypes.object,
- root: PropTypes.object,
- }),
- /**
- * @ignore
- */
- slots: PropTypes.shape({
- helperText: PropTypes.elementType,
- input: PropTypes.elementType,
- label: PropTypes.elementType,
- root: PropTypes.elementType,
- }),
- /**
- * Leading adornment for this input.
- */
- startDecorator: PropTypes.node,
- /**
- * @ignore
- */
- type: PropTypes /* @typescript-to-proptypes-ignore */.oneOf([
- 'button',
- 'checkbox',
- 'color',
- 'date',
- 'datetime-local',
- 'email',
- 'file',
- 'hidden',
- 'image',
- 'month',
- 'number',
- 'password',
- 'radio',
- 'range',
- 'reset',
- 'search',
- 'submit',
- 'tel',
- 'text',
- 'time',
- 'url',
- 'week',
- ]),
- /**
- * @ignore
- */
- value: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.string),
- PropTypes.number,
- PropTypes.string,
- ]),
- /**
- * The variant to use.
- * @default 'outlined'
- */
- variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
- PropTypes.oneOf(['outlined', 'plain', 'soft', 'solid']),
- PropTypes.string,
- ]),
-} as any;
-
-export default TextField;
+});
diff --git a/packages/mui-joy/src/TextField/TextFieldProps.ts b/packages/mui-joy/src/TextField/TextFieldProps.ts
deleted file mode 100644
index bded6834bac229..00000000000000
--- a/packages/mui-joy/src/TextField/TextFieldProps.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import * as React from 'react';
-import { OverrideProps } from '@mui/types';
-import { FormHelperTextProps } from '../FormHelperText/FormHelperTextProps';
-import { FormLabelProps } from '../FormLabel/FormLabelProps';
-import { InputProps } from '../Input/InputProps';
-
-export type TextFieldSlot = 'root';
-
-type InputRootKeys =
- | 'autoComplete'
- | 'autoFocus'
- | 'disabled'
- | 'error'
- | 'required'
- | 'fullWidth'
- | 'placeholder'
- | 'defaultValue'
- | 'value'
- | 'onChange'
- | 'onFocus'
- | 'onBlur'
- | 'type'
- | 'variant'
- | 'color'
- | 'size'
- | 'startDecorator'
- | 'endDecorator'
- | 'sx';
-
-export interface TextFieldTypeMap {
- props: P &
- Pick & {
- slots?: {
- root?: React.ElementType;
- label?: React.ElementType;
- input?: React.ElementType;
- helperText?: React.ElementType;
- };
- slotProps?: {
- root?: React.ComponentPropsWithRef<'div'>;
- label?: FormLabelProps;
- input?: Omit;
- helperText?: FormHelperTextProps;
- };
- /**
- * The id of the `input` element.
- * Use this prop to make `label` and `helperText` accessible for screen readers.
- */
- id?: string;
- /**
- * The label content.
- */
- label?: React.ReactNode;
- /**
- * Name attribute of the `input` element.
- */
- name?: string;
- /**
- * The short hint displayed in the `input` before the user enters a value.
- */
- placeholder?: string;
- /**
- * The helper text content.
- */
- helperText?: React.ReactNode;
- };
- defaultComponent: D;
-}
-
-export type TextFieldProps<
- D extends React.ElementType = TextFieldTypeMap['defaultComponent'],
- P = {
- component?: React.ElementType;
- },
-> = OverrideProps, D>;
-
-export interface TextFieldOwnerState extends TextFieldProps {}
diff --git a/packages/mui-joy/src/TextField/index.ts b/packages/mui-joy/src/TextField/index.ts
index d03daa37fe8c95..0198002999a808 100644
--- a/packages/mui-joy/src/TextField/index.ts
+++ b/packages/mui-joy/src/TextField/index.ts
@@ -1,4 +1 @@
export { default } from './TextField';
-export { default as textFieldClasses } from './textFieldClasses';
-export * from './textFieldClasses';
-export * from './TextFieldProps';
diff --git a/packages/mui-joy/src/TextField/textFieldClasses.ts b/packages/mui-joy/src/TextField/textFieldClasses.ts
deleted file mode 100644
index 018c4fe5aaca91..00000000000000
--- a/packages/mui-joy/src/TextField/textFieldClasses.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { generateUtilityClass, generateUtilityClasses } from '../className';
-
-export interface TextFieldClasses {
- /** Styles applied to the root element. */
- root: string;
- /** Styles applied to the root element if `disabled={true}`. */
- disabled: string;
- /** State class applied to the root element if `error={true}`. */
- error: string;
- /** Styles applied to the root element if the component is focused. */
- focused: string;
- /** Styles applied to the root element if `color="primary"`. */
- colorPrimary: string;
- /** Styles applied to the root element if `color="neutral"`. */
- colorNeutral: string;
- /** Styles applied to the root element if `color="danger"`. */
- colorDanger: string;
- /** Styles applied to the root element if `color="info"`. */
- colorInfo: string;
- /** Styles applied to the root element if `color="success"`. */
- colorSuccess: string;
- /** Styles applied to the root element if `color="warning"`. */
- colorWarning: string;
- /** Styles applied to the root element if `size="sm"`. */
- sizeSm: string;
- /** Styles applied to the root element if `size="md"`. */
- sizeMd: string;
- /** Styles applied to the root element if `size="lg"`. */
- sizeLg: string;
- /** Styles applied to the root element if `variant="plain"`. */
- variantPlain: string;
- /** Styles applied to the root element if `variant="outlined"`. */
- variantOutlined: string;
- /** Styles applied to the root element if `variant="soft"`. */
- variantSoft: string;
- /** Styles applied to the root element if `fullWidth={true}`. */
- fullWidth: string;
-}
-
-export type TextFieldClassKey = keyof TextFieldClasses;
-
-export function getTextFieldUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTextField', slot);
-}
-
-const textFieldClasses: TextFieldClasses = generateUtilityClasses('JoyTextField', [
- 'root',
- 'disabled',
- 'error',
- 'focused',
- 'colorPrimary',
- 'colorNeutral',
- 'colorDanger',
- 'colorInfo',
- 'colorSuccess',
- 'colorWarning',
- 'sizeSm',
- 'sizeMd',
- 'sizeLg',
- 'variantPlain',
- 'variantOutlined',
- 'variantSoft',
- 'fullWidth',
-]);
-
-export default textFieldClasses;
diff --git a/packages/mui-joy/src/Textarea/Textarea.test.tsx b/packages/mui-joy/src/Textarea/Textarea.test.tsx
index 2710f4f0f3e61d..dfe176c48421b9 100644
--- a/packages/mui-joy/src/Textarea/Textarea.test.tsx
+++ b/packages/mui-joy/src/Textarea/Textarea.test.tsx
@@ -7,6 +7,7 @@ import {
createRenderer,
screen,
act,
+ fireEvent,
} from 'test/utils';
import Textarea, { textareaClasses as classes } from '@mui/joy/Textarea';
import { ThemeProvider } from '@mui/joy/styles';
@@ -34,6 +35,11 @@ describe('Joy ', () => {
describeJoyColorInversion(, { muiName: 'JoyTextarea', classes });
+ it('should have placeholder', () => {
+ const { getByPlaceholderText } = render();
+ expect(getByPlaceholderText('Placeholder')).toBeVisible();
+ });
+
it('should have error classes', () => {
const { container } = render();
expect(container.firstChild).to.have.class(classes.error);
@@ -88,4 +94,54 @@ describe('Joy ', () => {
expect(handleFocus.callCount).to.equal(1);
});
});
+
+ describe('slotProps: input', () => {
+ it('`onKeyDown` and `onKeyUp` should work', () => {
+ const handleKeyDown = spy();
+ const handleKeyUp = spy();
+ const { container } = render(
+ ,
+ );
+
+ act(() => {
+ container.querySelector('textarea')!.focus();
+ });
+ fireEvent.keyDown(container.querySelector('textarea')!);
+ fireEvent.keyUp(container.querySelector('textarea')!);
+
+ expect(handleKeyDown.callCount).to.equal(1);
+ expect(handleKeyUp.callCount).to.equal(1);
+ });
+
+ it('should call focus and blur', () => {
+ const handleBlur = spy();
+ const handleFocus = spy();
+ const { container } = render(
+ ,
+ );
+
+ act(() => {
+ container.querySelector('textarea')!.focus();
+ });
+ expect(handleFocus.callCount).to.equal(1);
+ act(() => {
+ container.querySelector('textarea')!.blur();
+ });
+ expect(handleFocus.callCount).to.equal(1);
+ });
+
+ it('should override outer handlers', () => {
+ const handleFocus = spy();
+ const handleSlotFocus = spy();
+ const { container } = render(
+ ,
+ );
+
+ act(() => {
+ container.querySelector('textarea')!.focus();
+ });
+ expect(handleFocus.callCount).to.equal(0);
+ expect(handleSlotFocus.callCount).to.equal(1);
+ });
+ });
});
diff --git a/packages/mui-joy/src/Textarea/Textarea.tsx b/packages/mui-joy/src/Textarea/Textarea.tsx
index ad6695ea2d7c1b..eea78c01bfb43f 100644
--- a/packages/mui-joy/src/Textarea/Textarea.tsx
+++ b/packages/mui-joy/src/Textarea/Textarea.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { unstable_capitalize as capitalize } from '@mui/utils';
import { OverridableComponent } from '@mui/types';
-import { EventHandlers } from '@mui/base/utils';
import composeClasses from '@mui/base/composeClasses';
import TextareaAutosize from '@mui/base/TextareaAutosize';
import { styled, useThemeProps } from '../styles';
@@ -284,12 +283,12 @@ const Textarea = React.forwardRef(function Textarea(inProps, ref) {
className: [classes.textarea, inputStateClasses],
elementType: TextareaInput,
internalForwardedProps: {
+ ...propsToForward,
minRows,
maxRows,
},
externalForwardedProps: other,
- getSlotProps: (otherHandlers: EventHandlers) =>
- getInputProps({ ...otherHandlers, ...propsToForward }),
+ getSlotProps: getInputProps,
ownerState,
});
diff --git a/packages/mui-joy/src/Textarea/textareaClasses.ts b/packages/mui-joy/src/Textarea/textareaClasses.ts
index 36ca7c9f8a1393..7be2c4e519e9e0 100644
--- a/packages/mui-joy/src/Textarea/textareaClasses.ts
+++ b/packages/mui-joy/src/Textarea/textareaClasses.ts
@@ -48,10 +48,10 @@ export interface TextareaClasses {
export type TextareaClassKey = keyof TextareaClasses;
export function getTextareaUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTextarea', slot);
+ return generateUtilityClass('MuiTextarea', slot);
}
-const textareaClasses: TextareaClasses = generateUtilityClasses('JoyTextarea', [
+const textareaClasses: TextareaClasses = generateUtilityClasses('MuiTextarea', [
'root',
'textarea',
'startDecorator',
diff --git a/packages/mui-joy/src/Tooltip/Tooltip.tsx b/packages/mui-joy/src/Tooltip/Tooltip.tsx
index ba081823854461..8abb0da179ca67 100644
--- a/packages/mui-joy/src/Tooltip/Tooltip.tsx
+++ b/packages/mui-joy/src/Tooltip/Tooltip.tsx
@@ -172,6 +172,7 @@ const TooltipArrow = styled('span', {
let hystersisOpen = false;
let hystersisTimer: ReturnType | null = null;
+let cursorPosition = { x: 0, y: 0 };
export function testReset() {
hystersisOpen = false;
@@ -463,7 +464,6 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
open = false;
}
- const positionRef = React.useRef({ x: 0, y: 0 });
const popperRef = React.useRef(null);
const handleMouseMove = (event: React.MouseEvent) => {
@@ -472,7 +472,7 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
childrenProps.onMouseMove(event);
}
- positionRef.current = { x: event.clientX, y: event.clientY };
+ cursorPosition = { x: event.clientX, y: event.clientY };
if (popperRef.current) {
(popperRef.current as { update: () => void }).update();
@@ -565,10 +565,10 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
? {
getBoundingClientRect: () =>
({
- top: positionRef.current.y,
- left: positionRef.current.x,
- right: positionRef.current.x,
- bottom: positionRef.current.y,
+ top: cursorPosition.y,
+ left: cursorPosition.x,
+ right: cursorPosition.x,
+ bottom: cursorPosition.y,
width: 0,
height: 0,
} as DOMRect),
diff --git a/packages/mui-joy/src/Tooltip/tooltipClasses.ts b/packages/mui-joy/src/Tooltip/tooltipClasses.ts
index a3aa371e99bfd4..7c1453b4ac4134 100644
--- a/packages/mui-joy/src/Tooltip/tooltipClasses.ts
+++ b/packages/mui-joy/src/Tooltip/tooltipClasses.ts
@@ -50,10 +50,10 @@ export interface TooltipClasses {
export type TooltipClassKey = keyof TooltipClasses;
export function getTooltipUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTooltip', slot);
+ return generateUtilityClass('MuiTooltip', slot);
}
-const tooltipClasses: TooltipClasses = generateUtilityClasses('JoyTooltip', [
+const tooltipClasses: TooltipClasses = generateUtilityClasses('MuiTooltip', [
'root',
'tooltipArrow',
'arrow',
diff --git a/packages/mui-joy/src/Typography/Typography.spec.tsx b/packages/mui-joy/src/Typography/Typography.spec.tsx
index 804b52a15cca90..016b42ee9d9125 100644
--- a/packages/mui-joy/src/Typography/Typography.spec.tsx
+++ b/packages/mui-joy/src/Typography/Typography.spec.tsx
@@ -26,6 +26,7 @@ function Link(props: JSX.IntrinsicElements['a']) {
;
;
+ ; // should support plain string
(({ ownerState }) => ({
display: 'inline-flex',
marginInlineEnd: 'clamp(4px, var(--Typography-gap, 0.375em), 0.75rem)',
- ...((ownerState.sx as any)?.alignItems === 'flex-start' && {
- marginTop: '2px', // this makes the alignment perfect in most cases
- }),
+ ...(typeof ownerState.startDecorator !== 'string' &&
+ (ownerState.alignItems === 'flex-start' ||
+ (ownerState.sx as any)?.alignItems === 'flex-start') && {
+ marginTop: '2px', // this makes the alignment perfect in most cases
+ }),
}));
const EndDecorator = styled('span', {
@@ -51,9 +53,11 @@ const EndDecorator = styled('span', {
})<{ ownerState: TypographyOwnerState }>(({ ownerState }) => ({
display: 'inline-flex',
marginInlineStart: 'clamp(4px, var(--Typography-gap, 0.375em), 0.75rem)',
- ...((ownerState.sx as any)?.alignItems === 'flex-start' && {
- marginTop: '2px', // this makes the alignment perfect in most cases
- }),
+ ...(typeof ownerState.endDecorator !== 'string' &&
+ (ownerState.alignItems === 'flex-start' ||
+ (ownerState.sx as any)?.alignItems === 'flex-start') && {
+ marginTop: '2px', // this makes the alignment perfect in most cases
+ }),
}));
const TypographyRoot = styled('span', {
@@ -62,7 +66,7 @@ const TypographyRoot = styled('span', {
overridesResolver: (props, styles) => styles.root,
})<{ ownerState: TypographyOwnerState }>(({ theme, ownerState }) => ({
'--Icon-fontSize': '1.25em',
- margin: 0,
+ margin: 'var(--Typography-margin, 0px)',
...(ownerState.nesting
? {
display: 'inline',
@@ -82,6 +86,11 @@ const TypographyRoot = styled('span', {
}),
}),
...(ownerState.level && ownerState.level !== 'inherit' && theme.typography[ownerState.level]),
+ fontSize: `var(--Typography-fontSize, ${
+ ownerState.level && ownerState.level !== 'inherit'
+ ? theme.typography[ownerState.level]?.fontSize ?? 'inherit'
+ : 'inherit'
+ })`,
...(ownerState.noWrap && {
overflow: 'hidden',
textOverflow: 'ellipsis',
diff --git a/packages/mui-joy/src/Typography/TypographyProps.ts b/packages/mui-joy/src/Typography/TypographyProps.ts
index 37626e6c4d2f74..a9702034245640 100644
--- a/packages/mui-joy/src/Typography/TypographyProps.ts
+++ b/packages/mui-joy/src/Typography/TypographyProps.ts
@@ -7,6 +7,7 @@ import {
TypographySystem,
VariantProp,
ApplyColorInversion,
+ TextColor,
} from '../styles/types';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
@@ -84,7 +85,7 @@ export interface TypographyTypeMap
/**
* The system color.
*/
- textColor?: SystemProps['color'];
+ textColor?: TextColor;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
diff --git a/packages/mui-joy/src/Typography/typographyClasses.ts b/packages/mui-joy/src/Typography/typographyClasses.ts
index 703599e405d233..998fb2872b830f 100644
--- a/packages/mui-joy/src/Typography/typographyClasses.ts
+++ b/packages/mui-joy/src/Typography/typographyClasses.ts
@@ -56,10 +56,10 @@ export interface TypographyClasses {
export type TypographyClassKey = keyof TypographyClasses;
export function getTypographyUtilityClass(slot: string): string {
- return generateUtilityClass('JoyTypography', slot);
+ return generateUtilityClass('MuiTypography', slot);
}
-const typographyClasses: TypographyClasses = generateUtilityClasses('JoyTypography', [
+const typographyClasses: TypographyClasses = generateUtilityClasses('MuiTypography', [
'root',
'h1',
'h2',
diff --git a/packages/mui-joy/src/index.ts b/packages/mui-joy/src/index.ts
index 752ed94fb0cc0a..ff278a29015e67 100644
--- a/packages/mui-joy/src/index.ts
+++ b/packages/mui-joy/src/index.ts
@@ -172,7 +172,6 @@ export { default as Textarea } from './Textarea';
export * from './Textarea';
export { default as TextField } from './TextField';
-export * from './TextField';
export { default as Tooltip } from './Tooltip';
export * from './Tooltip';
diff --git a/packages/mui-joy/src/styles/CssVarsProvider.tsx b/packages/mui-joy/src/styles/CssVarsProvider.tsx
index aedb01a71e189d..1fd51b1bfa0715 100644
--- a/packages/mui-joy/src/styles/CssVarsProvider.tsx
+++ b/packages/mui-joy/src/styles/CssVarsProvider.tsx
@@ -6,6 +6,7 @@ import type { Theme, DefaultColorScheme, ExtendedColorScheme } from './types';
const shouldSkipGeneratingVar = (keys: string[]) =>
!!keys[0].match(/^(typography|variants|breakpoints|colorInversion|colorInversionConfig)$/) ||
+ !!keys[0].match(/sxConfig$/) || // ends with sxConfig
(keys[0] === 'palette' && !!keys[1]?.match(/^(mode)$/)) ||
(keys[0] === 'focus' && keys[1] !== 'thickness');
diff --git a/packages/mui-joy/src/styles/components.d.ts b/packages/mui-joy/src/styles/components.d.ts
index 82da1cf230c957..7f84e42f928861 100644
--- a/packages/mui-joy/src/styles/components.d.ts
+++ b/packages/mui-joy/src/styles/components.d.ts
@@ -148,7 +148,7 @@ import { TabProps, TabOwnerState, TabSlot } from '../Tab/TabProps';
import { TabListProps, TabListOwnerState, TabListSlot } from '../TabList/TabListProps';
import { TabPanelProps, TabPanelOwnerState, TabPanelSlot } from '../TabPanel/TabPanelProps';
import { TabsProps, TabsOwnerState, TabsSlot } from '../Tabs/TabsProps';
-import { TextFieldProps, TextFieldOwnerState, TextFieldSlot } from '../TextField/TextFieldProps';
+import { TableProps, TableOwnerState, TableSlot } from '../Table/TableProps';
import { TooltipProps, TooltipOwnerState, TooltipSlot } from '../Tooltip/TooltipProps';
import {
TypographyProps,
@@ -382,6 +382,10 @@ export interface Components {
defaultProps?: Partial;
styleOverrides?: OverridesStyleRules;
};
+ JoyTable?: {
+ defaultProps?: Partial;
+ styleOverrides?: OverridesStyleRules;
+ };
JoyTabList?: {
defaultProps?: Partial;
styleOverrides?: OverridesStyleRules;
@@ -398,10 +402,6 @@ export interface Components {
defaultProps?: Partial;
styleOverrides?: OverridesStyleRules;
};
- JoyTextField?: {
- defaultProps?: Partial;
- styleOverrides?: OverridesStyleRules;
- };
JoyTooltip?: {
defaultProps?: Partial;
styleOverrides?: OverridesStyleRules;
diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts
index 31e472a6b2f7c9..2da1062be28f26 100644
--- a/packages/mui-joy/src/styles/extendTheme.spec.ts
+++ b/packages/mui-joy/src/styles/extendTheme.spec.ts
@@ -54,6 +54,7 @@ import { TabOwnerState } from '@mui/joy/Tab';
import { TabListOwnerState } from '@mui/joy/TabList';
import { TabPanelOwnerState } from '@mui/joy/TabPanel';
import { TabsOwnerState } from '@mui/joy/Tabs';
+import { TableOwnerState } from '@mui/joy/Table';
import { TextareaOwnerState } from '@mui/joy/Textarea';
import { TooltipOwnerState } from '@mui/joy/Tooltip';
import { TypographyOwnerState } from '@mui/joy/Typography';
@@ -1040,6 +1041,19 @@ extendTheme({
},
},
},
+ JoyTable: {
+ defaultProps: {
+ size: 'sm',
+ variant: 'outlined',
+ color: 'neutral',
+ },
+ styleOverrides: {
+ root: ({ ownerState }) => {
+ expectType, typeof ownerState>(ownerState);
+ return {};
+ },
+ },
+ },
JoyTextarea: {
defaultProps: {
size: 'sm',
diff --git a/packages/mui-joy/src/styles/styleUtils.ts b/packages/mui-joy/src/styles/styleUtils.ts
index 476de7522a07f7..ed55b8a4ebf57b 100644
--- a/packages/mui-joy/src/styles/styleUtils.ts
+++ b/packages/mui-joy/src/styles/styleUtils.ts
@@ -34,8 +34,13 @@ export const resolveSxValue = (
return `${value}px`;
}
parsedValue = theme.vars?.radius[value as keyof typeof theme.vars.radius] || value;
+ } else {
+ parsedValue = value;
}
}
+ if (typeof value === 'function') {
+ parsedValue = value(theme);
+ }
}
return parsedValue || defaultValue;
};
diff --git a/packages/mui-joy/src/styles/types/theme.ts b/packages/mui-joy/src/styles/types/theme.ts
index 89c044bbea790d..d82884023fd34b 100644
--- a/packages/mui-joy/src/styles/types/theme.ts
+++ b/packages/mui-joy/src/styles/types/theme.ts
@@ -26,12 +26,12 @@ type Split = K extends string | number
? { [k in K]: Exclude }
: never;
-type ConcatDeep = T extends Record
+type ConcatDeep = T extends Record
? keyof T extends string | number
? V extends string | number
? keyof T
: keyof V extends string | number
- ? `${keyof T}-${ConcatDeep>}`
+ ? `${keyof T}${D}${ConcatDeep, D>}`
: never
: never
: never;
@@ -41,7 +41,7 @@ type ConcatDeep = T extends Record
* - { borderRadius: string | number } // the value can't be a union
* - { shadows: [string, string, ..., string] } // the value can't be an array
*/
-type NormalizeVars = ConcatDeep>;
+type NormalizeVars = ConcatDeep, D>;
export interface RuntimeColorSystem extends Omit {
palette: ColorSystem['palette'] & {
@@ -67,6 +67,13 @@ export interface ThemeVars extends ThemeScales, ColorSystemVars {}
export interface ThemeCssVarOverrides {}
+/**
+ * For providing `sx` autocomplete, e.g. `color`, `bgcolor`, `borderColor`.
+ */
+export type TextColor =
+ | NormalizeVars, '.'>
+ | (string & Record);
+
export type ThemeCssVar = OverridableStringUnion