From ee3091faa0d9a9b7ae450e6158bf58e7f1d04f6a Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 17:23:23 +0700 Subject: [PATCH 001/139] use getBy for better DX --- test/utils/describeConformance.js | 54 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index e2e6cc16a3f2c9..007e686e39a021 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -221,7 +221,7 @@ function forEachSlot(slots, callback) { } } -function testSlotsProp(element, getOptions) { +function testSlots(element, getOptions) { const { render, slots } = getOptions(); // eslint-disable-next-line react/prop-types @@ -243,9 +243,9 @@ function testSlotsProp(element, getOptions) { [capitalize(slotName)]: slotComponent, }; - const { queryByTestId } = render(React.cloneElement(element, { components })); - const renderedElement = queryByTestId('custom'); - expect(renderedElement).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { components })); + const renderedElement = getByTestId('custom'); + expect(renderedElement).toBeVisible(); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -264,9 +264,9 @@ function testSlotsProp(element, getOptions) { [slotName]: slotComponent, }; - const { queryByTestId } = render(React.cloneElement(element, { slots: components })); - const renderedElement = queryByTestId('custom'); - expect(renderedElement).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { slots: components })); + const renderedElement = getByTestId('custom'); + expect(renderedElement).toBeVisible(); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -307,12 +307,12 @@ function testSlotsProp(element, getOptions) { [slotName]: ComponentForSlotsProp, }; - const { queryByTestId } = render( + const { getByTestId } = render( React.cloneElement(element, { components, slots: slotOverrides }), ); - expect(queryByTestId('from-slots')).not.to.equal(null); - expect(queryByTestId('from-components')).to.equal(null); + expect(getByTestId('from-slots')).toBeVisible(); + expect(getByTestId('from-components')).toBeVisible(); }); if (slotOptions.testWithElement !== null) { @@ -335,12 +335,12 @@ function testSlotsProp(element, getOptions) { }, }; - const { queryByTestId } = render( + const { getByTestId } = render( React.cloneElement(element, { components, componentsProps }), ); - const renderedElement = queryByTestId('customized'); - expect(renderedElement).not.to.equal(null); + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -365,12 +365,12 @@ function testSlotsProp(element, getOptions) { }, }; - const { queryByTestId } = render( + const { getByTestId } = render( React.cloneElement(element, { slots: components, slotProps }), ); - const renderedElement = queryByTestId('customized'); - expect(renderedElement).not.to.equal(null); + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -381,7 +381,7 @@ function testSlotsProp(element, getOptions) { }); } -function testSlotPropsProp(element, getOptions) { +function testSlotProps(element, getOptions) { const { render, slots } = getOptions(); if (!render) { @@ -396,9 +396,9 @@ function testSlotPropsProp(element, getOptions) { }, }; - const { queryByTestId } = render(React.cloneElement(element, { componentsProps })); - const slotComponent = queryByTestId('custom'); - expect(slotComponent).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -412,9 +412,9 @@ function testSlotPropsProp(element, getOptions) { }, }; - const { queryByTestId } = render(React.cloneElement(element, { slotProps })); - const slotComponent = queryByTestId('custom'); - expect(slotComponent).not.to.equal(null); + const { getByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -436,8 +436,8 @@ function testSlotPropsProp(element, getOptions) { }, }; - const { queryByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); - const slotComponent = queryByTestId('custom'); + const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); + const slotComponent = getByTestId('custom'); expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); expect(slotComponent).not.to.have.attribute('data-from-components-props'); }); @@ -848,8 +848,8 @@ const fullSuite = { refForwarding: describeRef, rootClass: testRootClass, reactTestRenderer: testReactTestRenderer, - slotPropsProp: testSlotPropsProp, - slotsProp: testSlotsProp, + slotPropsProp: testSlotProps, + slotsProp: testSlots, themeDefaultProps: testThemeDefaultProps, themeStyleOverrides: testThemeStyleOverrides, themeVariants: testThemeVariants, From ae5629cc3e008e5a8a5e42df92832ec62e89c021 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 19:43:40 +0700 Subject: [PATCH 002/139] move Material UI components test --- test/utils/describeConformance.js | 160 ++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 7 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index 007e686e39a021..aa390d23ddd966 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -207,6 +207,12 @@ export function testReactTestRenderer(element) { }); } +/** + * + * @param {object} slots + * @param {(slotName: string, slotOptions: object) => void} callback + * @returns + */ function forEachSlot(slots, callback) { if (!slots) { return; @@ -307,12 +313,12 @@ function testSlots(element, getOptions) { [slotName]: ComponentForSlotsProp, }; - const { getByTestId } = render( + const { getByTestId, queryByTestId } = render( React.cloneElement(element, { components, slots: slotOverrides }), ); expect(getByTestId('from-slots')).toBeVisible(); - expect(getByTestId('from-components')).toBeVisible(); + expect(queryByTestId('from-components')).to.equal(null); }); if (slotOptions.testWithElement !== null) { @@ -476,9 +482,13 @@ function testSlotProps(element, getOptions) { * @property {object} [testVariantProps] * @property {(mount: (node: React.ReactNode) => import('enzyme').ReactWrapper) => (node: React.ReactNode) => import('enzyme').ReactWrapper} [wrapMount] - You can use this option to mount the component with enzyme in a WrapperComponent. Make sure the returned node corresponds to the input node and not the wrapper component. * @property {boolean} [testCustomVariant] - The component supports custom variant + * @property {object} components - Material UI's components prop + * @property {object} slots - MUI slots prop */ /** + * >>> This will be DEPRECEATED in v6 <<< + * * MUI components have a `components` prop that allows rendering a different * Components from @inheritComponent * @param {React.ReactElement} element @@ -486,13 +496,149 @@ function testSlotProps(element, getOptions) { */ function testComponentsProp(element, getOptions) { describe('prop components:', () => { - it('can render another root component with the `components` prop', () => { - const { mount, testComponentsRootPropWith: component = 'em' } = getOptions(); + const { components, slots, render } = getOptions(); - const wrapper = mount(React.cloneElement(element, { components: { Root: component } })); + if (!components) { + // the tests below will cover all the components + it('can render another root component with the `components` prop', () => { + const { mount, testComponentsRootPropWith: component = 'em' } = getOptions(); + const wrapper = mount(React.cloneElement(element, { components: { Root: component } })); - expect(findRootComponent(wrapper, { component }).exists()).to.equal(true); - }); + expect(findRootComponent(wrapper, { component }).exists()).to.equal(true); + }); + } else { + // eslint-disable-next-line react/prop-types + const CustomComponent = React.forwardRef(({ className }, ref) => ( + + )); + + forEachSlot(components, (slotName, slotOptions) => { + it(`allows overriding the ${slotName} slot with a component using the components.${capitalize( + slotName, + )} prop`, () => { + if (!render) { + throwMissingPropError('render'); + } + + const slotComponent = slotOptions.testWithComponent ?? CustomComponent; + + const { getByTestId } = render( + React.cloneElement(element, { components: { [capitalize(slotName)]: slotComponent } }), + ); + const renderedElement = getByTestId('custom'); + expect(renderedElement).toBeVisible(); + if (slotOptions.expectedClassName) { + expect(renderedElement).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`prioritizes the 'slots.${slotName}' over components.${capitalize( + slotName, + )} if both are defined`, () => { + if (!slots || !slots[slotName]) { + throw new Error( + `missing "slots" in options, unable to test "slots" overrides "components" for this Material UI component`, + ); + } + + // eslint-disable-next-line react/prop-types + const ComponentForComponentsProp = React.forwardRef(({ children }, ref) => { + const SlotComponent = slotOptions.testWithComponent ?? 'div'; + return ( + + {children} + + ); + }); + + // eslint-disable-next-line react/prop-types + const ComponentForSlotsProp = React.forwardRef(({ children }, ref) => { + const SlotComponent = slots[slotName].testWithComponent ?? 'div'; + return ( + + {children} + + ); + }); + + const { getByTestId, queryByTestId } = render( + React.cloneElement(element, { + components: { + [capitalize(slotName)]: ComponentForComponentsProp, + }, + slots: { + [slotName]: ComponentForSlotsProp, + }, + }), + ); + + expect(getByTestId('from-slots')).toBeVisible(); + expect(queryByTestId('from-components')).to.equal(null); + }); + + if (slotOptions.testWithElement !== null) { + it(`allows overriding the ${slotName} slot with an element using the components.${capitalize( + slotName, + )} prop`, () => { + if (!render) { + throwMissingPropError('render'); + } + + const slotElement = slotOptions.testWithElement ?? 'i'; + + const { getByTestId } = render( + React.cloneElement(element, { + components: { + [capitalize(slotName)]: slotElement, + }, + componentsProps: { + [slotName]: { + 'data-testid': 'customized', + }, + }, + }), + ); + + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); + + expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); + if (slotOptions.expectedClassName) { + expect(renderedElement).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`allows overriding the ${slotName} slot with an element using the slots.${slotName} prop`, () => { + if (!render) { + throwMissingPropError('render'); + } + + const slotElement = slotOptions.testWithElement ?? 'i'; + + const { getByTestId } = render( + React.cloneElement(element, { + slots: { + [slotName]: slotElement, + }, + slotProps: { + [slotName]: { + 'data-testid': 'customized', + }, + }, + }), + ); + + const renderedElement = getByTestId('customized'); + expect(renderedElement).toBeVisible(); + + expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); + if (slotOptions.expectedClassName) { + expect(renderedElement).to.have.class(slotOptions.expectedClassName); + } + }); + } + }); + } }); } From 52df00949ada19acebe9cb8f13e8b9cee7e89faf Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 19:47:53 +0700 Subject: [PATCH 003/139] fix Alert --- packages/mui-material/src/Alert/Alert.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/mui-material/src/Alert/Alert.test.js b/packages/mui-material/src/Alert/Alert.test.js index cecd3619b94a4e..476b0eebdb1659 100644 --- a/packages/mui-material/src/Alert/Alert.test.js +++ b/packages/mui-material/src/Alert/Alert.test.js @@ -18,11 +18,14 @@ describe('', () => { muiName: 'MuiAlert', testVariantProps: { variant: 'standard', color: 'success' }, testDeepOverrides: { slotName: 'message', slotClassName: classes.message }, + components: { + closeButton: {}, + closeIcon: {}, + }, slots: { closeButton: {}, closeIcon: {}, }, - skip: ['componentsProp'], })); describe('prop: square', () => { From 397c96d9c2bc27595e0e30a3974e49ff19bc4e57 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 20:36:19 +0700 Subject: [PATCH 004/139] update conformance --- test/utils/describeConformance.js | 94 ++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index aa390d23ddd966..9911219f10b73b 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -642,6 +642,87 @@ function testComponentsProp(element, getOptions) { }); } +function testComponentsPropsProp(element, getOptions) { + const { render, components } = getOptions(); + + if (!render) { + throwMissingPropError('render'); + } + + describe('prop componentsProps:', () => { + forEachSlot(components, (slotName, slotOptions) => { + it(`sets custom properties on the ${slotName} slot's element with the componentsProps.${slotName} prop`, () => { + const componentsProps = { + [slotName]: { + 'data-testid': 'custom', + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); + + if (slotOptions.expectedClassName) { + expect(slotComponent).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`sets custom properties on the ${slotName} slot's element with the slotProps.${slotName} prop`, () => { + const slotProps = { + [slotName]: { + 'data-testid': 'custom', + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).toBeVisible(); + + if (slotOptions.expectedClassName) { + expect(slotComponent).to.have.class(slotOptions.expectedClassName); + } + }); + + it(`prioritizes the 'slotProps.${slotName}' over componentsProps.${slotName} if both are defined`, () => { + const componentsProps = { + [slotName]: { + 'data-testid': 'custom', + 'data-from-components-props': 'true', + }, + }; + + const slotProps = { + [slotName]: { + 'data-testid': 'custom', + 'data-from-slot-props': 'true', + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); + const slotComponent = getByTestId('custom'); + expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); + expect(slotComponent).not.to.have.attribute('data-from-components-props'); + }); + + if (slotOptions.expectedClassName) { + it(`merges the class names provided in slotsProps.${slotName} with the built-in ones`, () => { + const slotProps = { + [slotName]: { + 'data-testid': 'custom', + className: randomStringValue(), + }, + }; + + const { getByTestId } = render(React.cloneElement(element, { slotProps })); + + expect(getByTestId('custom')).to.have.class(slotOptions.expectedClassName); + expect(getByTestId('custom')).to.have.class(slotProps[slotName].className); + }); + } + }); + }); +} + /** * MUI theme has a components section that allows specifying default props. * Components from @inheritComponent @@ -989,6 +1070,7 @@ function testThemeVariants(element, getOptions) { const fullSuite = { componentProp: testComponentProp, componentsProp: testComponentsProp, + componentsPropsProp: testComponentsPropsProp, mergeClassName: testClassName, propsSpread: testPropsSpread, refForwarding: describeRef, @@ -1013,6 +1095,7 @@ export default function describeConformance(minimalElement, getOptions) { after: runAfterHook = () => {}, only = Object.keys(fullSuite), slots, + components, skip = [], wrapMount, } = getOptions(); @@ -1021,11 +1104,16 @@ export default function describeConformance(minimalElement, getOptions) { (testKey) => only.indexOf(testKey) !== -1 && skip.indexOf(testKey) === -1, ); - const slotBasedTests = ['slotsProp', 'slotPropsProp']; - if (!slots) { // if `slots` are not defined, do not run tests that depend on them - filteredTests = filteredTests.filter((testKey) => !slotBasedTests.includes(testKey)); + filteredTests = filteredTests.filter( + (testKey) => !['slotsProp', 'slotPropsProp'].includes(testKey), + ); + } + + if (!components) { + // if `components` are not defined, do not run tests that depend on them + filteredTests = filteredTests.filter((testKey) => !['componentsPropsProp'].includes(testKey)); } const baseMount = createMount(); From 842cf9b7074139018f2f17eb5d890e99386da9a1 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 20:37:05 +0700 Subject: [PATCH 005/139] Revert "use getBy for better DX" This reverts commit ee3091faa0d9a9b7ae450e6158bf58e7f1d04f6a. --- test/utils/describeConformance.js | 52 +++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index 9911219f10b73b..e73ef3911c5f09 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -227,7 +227,7 @@ function forEachSlot(slots, callback) { } } -function testSlots(element, getOptions) { +function testSlotsProp(element, getOptions) { const { render, slots } = getOptions(); // eslint-disable-next-line react/prop-types @@ -249,9 +249,9 @@ function testSlots(element, getOptions) { [capitalize(slotName)]: slotComponent, }; - const { getByTestId } = render(React.cloneElement(element, { components })); - const renderedElement = getByTestId('custom'); - expect(renderedElement).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { components })); + const renderedElement = queryByTestId('custom'); + expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -270,9 +270,9 @@ function testSlots(element, getOptions) { [slotName]: slotComponent, }; - const { getByTestId } = render(React.cloneElement(element, { slots: components })); - const renderedElement = getByTestId('custom'); - expect(renderedElement).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { slots: components })); + const renderedElement = queryByTestId('custom'); + expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -313,11 +313,11 @@ function testSlots(element, getOptions) { [slotName]: ComponentForSlotsProp, }; - const { getByTestId, queryByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components, slots: slotOverrides }), ); - expect(getByTestId('from-slots')).toBeVisible(); + expect(queryByTestId('from-slots')).not.to.equal(null); expect(queryByTestId('from-components')).to.equal(null); }); @@ -341,12 +341,12 @@ function testSlots(element, getOptions) { }, }; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components, componentsProps }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -371,12 +371,12 @@ function testSlots(element, getOptions) { }, }; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { slots: components, slotProps }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -387,7 +387,7 @@ function testSlots(element, getOptions) { }); } -function testSlotProps(element, getOptions) { +function testSlotPropsProp(element, getOptions) { const { render, slots } = getOptions(); if (!render) { @@ -402,9 +402,9 @@ function testSlotProps(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -418,9 +418,9 @@ function testSlotProps(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { slotProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -442,8 +442,8 @@ function testSlotProps(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); - const slotComponent = getByTestId('custom'); + const { queryByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); + const slotComponent = queryByTestId('custom'); expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); expect(slotComponent).not.to.have.attribute('data-from-components-props'); }); @@ -1076,8 +1076,8 @@ const fullSuite = { refForwarding: describeRef, rootClass: testRootClass, reactTestRenderer: testReactTestRenderer, - slotPropsProp: testSlotProps, - slotsProp: testSlots, + slotPropsProp: testSlotPropsProp, + slotsProp: testSlotsProp, themeDefaultProps: testThemeDefaultProps, themeStyleOverrides: testThemeStyleOverrides, themeVariants: testThemeVariants, From fc8a694213eca9ab69637aabc91578cc15f51834 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 20:43:48 +0700 Subject: [PATCH 006/139] update Autocomplete --- .../src/Autocomplete/Autocomplete.test.js | 6 +++ test/utils/describeConformance.js | 40 ++++++++++--------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js index f4282ecdfe31b0..8cab5bab7632eb 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js @@ -55,6 +55,12 @@ describe('', () => { testStateOverrides: { prop: 'fullWidth', value: true, styleKey: 'fullWidth' }, refInstanceof: window.HTMLDivElement, testComponentPropWith: 'div', + components: { + clearIndicator: { expectedClassName: classes.clearIndicator }, + paper: { expectedClassName: classes.paper }, + popper: { expectedClassName: classes.popper }, + popupIndicator: { expectedClassName: classes.popupIndicator }, + }, slots: { clearIndicator: { expectedClassName: classes.clearIndicator }, paper: { expectedClassName: classes.paper }, diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index e73ef3911c5f09..fba2aefb7d1aba 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -522,11 +522,11 @@ function testComponentsProp(element, getOptions) { const slotComponent = slotOptions.testWithComponent ?? CustomComponent; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components: { [capitalize(slotName)]: slotComponent } }), ); - const renderedElement = getByTestId('custom'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('custom'); + expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); } @@ -561,7 +561,7 @@ function testComponentsProp(element, getOptions) { ); }); - const { getByTestId, queryByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components: { [capitalize(slotName)]: ComponentForComponentsProp, @@ -572,7 +572,7 @@ function testComponentsProp(element, getOptions) { }), ); - expect(getByTestId('from-slots')).toBeVisible(); + expect(queryByTestId('from-slots')).not.to.equal(null); expect(queryByTestId('from-components')).to.equal(null); }); @@ -586,7 +586,7 @@ function testComponentsProp(element, getOptions) { const slotElement = slotOptions.testWithElement ?? 'i'; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { components: { [capitalize(slotName)]: slotElement, @@ -599,8 +599,8 @@ function testComponentsProp(element, getOptions) { }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -615,7 +615,7 @@ function testComponentsProp(element, getOptions) { const slotElement = slotOptions.testWithElement ?? 'i'; - const { getByTestId } = render( + const { queryByTestId } = render( React.cloneElement(element, { slots: { [slotName]: slotElement, @@ -628,8 +628,8 @@ function testComponentsProp(element, getOptions) { }), ); - const renderedElement = getByTestId('customized'); - expect(renderedElement).toBeVisible(); + const renderedElement = queryByTestId('customized'); + expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); if (slotOptions.expectedClassName) { @@ -658,9 +658,9 @@ function testComponentsPropsProp(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { componentsProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -674,9 +674,9 @@ function testComponentsPropsProp(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { slotProps })); - const slotComponent = getByTestId('custom'); - expect(slotComponent).toBeVisible(); + const { queryByTestId } = render(React.cloneElement(element, { slotProps })); + const slotComponent = queryByTestId('custom'); + expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { expect(slotComponent).to.have.class(slotOptions.expectedClassName); @@ -698,8 +698,10 @@ function testComponentsPropsProp(element, getOptions) { }, }; - const { getByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); - const slotComponent = getByTestId('custom'); + const { queryByTestId } = render( + React.cloneElement(element, { componentsProps, slotProps }), + ); + const slotComponent = queryByTestId('custom'); expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); expect(slotComponent).not.to.have.attribute('data-from-components-props'); }); From 3fa5754475bebbcc66d0d4022e87fe401c56aee4 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 20:50:16 +0700 Subject: [PATCH 007/139] fix AvatarGroup --- packages/mui-material/src/AvatarGroup/AvatarGroup.test.js | 3 +++ test/utils/describeConformance.js | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js b/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js index 1f54725e17af09..8b90f0cac78863 100644 --- a/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js +++ b/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js @@ -20,6 +20,9 @@ describe('', () => { muiName: 'MuiAvatarGroup', refInstanceof: window.HTMLDivElement, testVariantProps: { max: 10, spacing: 'small', variant: 'square' }, + components: { + additionalAvatar: { expectedClassName: classes.avatar }, + }, slots: { additionalAvatar: { expectedClassName: classes.avatar }, }, diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index fba2aefb7d1aba..4290f5134d8ecf 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -494,7 +494,7 @@ function testSlotPropsProp(element, getOptions) { * @param {React.ReactElement} element * @param {() => ConformanceOptions} getOptions */ -function testComponentsProp(element, getOptions) { +function testMaterialUIComponentsProp(element, getOptions) { describe('prop components:', () => { const { components, slots, render } = getOptions(); @@ -642,7 +642,7 @@ function testComponentsProp(element, getOptions) { }); } -function testComponentsPropsProp(element, getOptions) { +function testMaterialUIComponentsPropsProp(element, getOptions) { const { render, components } = getOptions(); if (!render) { @@ -1071,8 +1071,8 @@ function testThemeVariants(element, getOptions) { const fullSuite = { componentProp: testComponentProp, - componentsProp: testComponentsProp, - componentsPropsProp: testComponentsPropsProp, + componentsProp: testMaterialUIComponentsProp, + componentsPropsProp: testMaterialUIComponentsPropsProp, mergeClassName: testClassName, propsSpread: testPropsSpread, refForwarding: describeRef, From 3a6b30a7bc658850697c19066aab7c357e5fccf6 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 18 Nov 2022 22:01:41 +0700 Subject: [PATCH 008/139] rename to components --- packages/mui-material/src/Alert/Alert.test.js | 4 ---- packages/mui-material/src/Autocomplete/Autocomplete.test.js | 6 ------ packages/mui-material/src/AvatarGroup/AvatarGroup.test.js | 3 --- packages/mui-material/src/Backdrop/Backdrop.test.js | 2 +- packages/mui-material/src/FilledInput/FilledInput.test.js | 2 +- .../src/FormControlLabel/FormControlLabel.test.js | 2 +- packages/mui-material/src/Input/Input.test.js | 2 +- packages/mui-material/src/InputBase/InputBase.test.js | 2 +- packages/mui-material/src/ListItem/ListItem.test.js | 2 +- packages/mui-material/src/Modal/Modal.test.js | 2 +- .../mui-material/src/OutlinedInput/OutlinedInput.test.js | 2 +- .../mui-material/src/PaginationItem/PaginationItem.test.js | 2 +- packages/mui-material/src/Popper/Popper.test.js | 2 +- packages/mui-material/src/Slider/Slider.test.js | 2 +- packages/mui-material/src/StepLabel/StepLabel.test.js | 2 +- packages/mui-material/src/Tooltip/Tooltip.test.js | 2 +- 16 files changed, 13 insertions(+), 26 deletions(-) diff --git a/packages/mui-material/src/Alert/Alert.test.js b/packages/mui-material/src/Alert/Alert.test.js index 476b0eebdb1659..2d18336a96641e 100644 --- a/packages/mui-material/src/Alert/Alert.test.js +++ b/packages/mui-material/src/Alert/Alert.test.js @@ -22,10 +22,6 @@ describe('', () => { closeButton: {}, closeIcon: {}, }, - slots: { - closeButton: {}, - closeIcon: {}, - }, })); describe('prop: square', () => { diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js index 8cab5bab7632eb..ad7ef6755fab44 100644 --- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js +++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js @@ -61,12 +61,6 @@ describe('', () => { popper: { expectedClassName: classes.popper }, popupIndicator: { expectedClassName: classes.popupIndicator }, }, - slots: { - clearIndicator: { expectedClassName: classes.clearIndicator }, - paper: { expectedClassName: classes.paper }, - popper: { expectedClassName: classes.popper }, - popupIndicator: { expectedClassName: classes.popupIndicator }, - }, skip: ['componentProp', 'componentsProp', 'slotsProp', 'reactTestRenderer'], }), ); diff --git a/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js b/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js index 8b90f0cac78863..c455e6b8cdf70a 100644 --- a/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js +++ b/packages/mui-material/src/AvatarGroup/AvatarGroup.test.js @@ -23,9 +23,6 @@ describe('', () => { components: { additionalAvatar: { expectedClassName: classes.avatar }, }, - slots: { - additionalAvatar: { expectedClassName: classes.avatar }, - }, skip: ['componentsProp', 'slotsProp'], }), ); diff --git a/packages/mui-material/src/Backdrop/Backdrop.test.js b/packages/mui-material/src/Backdrop/Backdrop.test.js index 446e5d42784f6b..df9b603d7cc36e 100644 --- a/packages/mui-material/src/Backdrop/Backdrop.test.js +++ b/packages/mui-material/src/Backdrop/Backdrop.test.js @@ -15,7 +15,7 @@ describe('', () => { refInstanceof: window.HTMLDivElement, muiName: 'MuiBackdrop', testVariantProps: { invisible: true }, - slots: { + components: { root: { expectedClassName: classes.root, }, diff --git a/packages/mui-material/src/FilledInput/FilledInput.test.js b/packages/mui-material/src/FilledInput/FilledInput.test.js index 1afb2e2fd925bc..f5b016bf8a4677 100644 --- a/packages/mui-material/src/FilledInput/FilledInput.test.js +++ b/packages/mui-material/src/FilledInput/FilledInput.test.js @@ -16,7 +16,7 @@ describe('', () => { testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, testVariantProps: { variant: 'contained', fullWidth: true }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, - slots: { + components: { // can't test with DOM element as Input places an ownerState prop on it unconditionally. root: { expectedClassName: classes.root, testWithElement: null }, input: { expectedClassName: classes.input, testWithElement: null }, diff --git a/packages/mui-material/src/FormControlLabel/FormControlLabel.test.js b/packages/mui-material/src/FormControlLabel/FormControlLabel.test.js index b135543e4ce537..136034318b81ec 100644 --- a/packages/mui-material/src/FormControlLabel/FormControlLabel.test.js +++ b/packages/mui-material/src/FormControlLabel/FormControlLabel.test.js @@ -18,7 +18,7 @@ describe('', () => { muiName: 'MuiFormControlLabel', testVariantProps: { disabled: true }, refInstanceof: window.HTMLLabelElement, - slots: { + components: { typography: { expectedClassName: classes.label }, }, skip: ['componentProp', 'componentsProp', 'slotsProp'], diff --git a/packages/mui-material/src/Input/Input.test.js b/packages/mui-material/src/Input/Input.test.js index ca3701a5cb5988..53c597f4615585 100644 --- a/packages/mui-material/src/Input/Input.test.js +++ b/packages/mui-material/src/Input/Input.test.js @@ -16,7 +16,7 @@ describe('', () => { testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, testVariantProps: { variant: 'contained', fullWidth: true }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, - slots: { + components: { // can't test with DOM element as Input places an ownerState prop on it unconditionally. root: { expectedClassName: classes.root, testWithElement: null }, input: { expectedClassName: classes.input, testWithElement: null }, diff --git a/packages/mui-material/src/InputBase/InputBase.test.js b/packages/mui-material/src/InputBase/InputBase.test.js index eb1da1530c47fe..4aa9022f88522e 100644 --- a/packages/mui-material/src/InputBase/InputBase.test.js +++ b/packages/mui-material/src/InputBase/InputBase.test.js @@ -21,7 +21,7 @@ describe('', () => { refInstanceof: window.HTMLDivElement, muiName: 'MuiInputBase', testVariantProps: { size: 'small' }, - slots: { + components: { // can't test with DOM element as InputBase places an ownerState prop on it unconditionally. root: { expectedClassName: classes.root, testWithElement: null }, input: { expectedClassName: classes.input, testWithElement: null }, diff --git a/packages/mui-material/src/ListItem/ListItem.test.js b/packages/mui-material/src/ListItem/ListItem.test.js index c58df6f37daea7..4e60a346d537b9 100644 --- a/packages/mui-material/src/ListItem/ListItem.test.js +++ b/packages/mui-material/src/ListItem/ListItem.test.js @@ -22,7 +22,7 @@ describe('', () => { refInstanceof: window.HTMLLIElement, muiName: 'MuiListItem', testVariantProps: { dense: true }, - slots: { + components: { root: {}, }, skip: ['componentsProp'], diff --git a/packages/mui-material/src/Modal/Modal.test.js b/packages/mui-material/src/Modal/Modal.test.js index 70ea0e748f230b..1ea9daf0b30a8e 100644 --- a/packages/mui-material/src/Modal/Modal.test.js +++ b/packages/mui-material/src/Modal/Modal.test.js @@ -32,7 +32,7 @@ describe('', () => { muiName: 'MuiModal', refInstanceof: window.HTMLDivElement, testVariantProps: { hideBackdrop: true }, - slots: { + components: { root: { expectedClassName: classes.root }, backdrop: {}, }, diff --git a/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js b/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js index 7db2485969817b..509430afd50aaa 100644 --- a/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js +++ b/packages/mui-material/src/OutlinedInput/OutlinedInput.test.js @@ -17,7 +17,7 @@ describe('', () => { testDeepOverrides: { slotName: 'input', slotClassName: classes.input }, testVariantProps: { variant: 'contained', fullWidth: true }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, - slots: { + components: { // can't test with DOM element as InputBase places an ownerState prop on it unconditionally. root: { expectedClassName: classes.root, testWithElement: null }, input: { expectedClassName: classes.input, testWithElement: null }, diff --git a/packages/mui-material/src/PaginationItem/PaginationItem.test.js b/packages/mui-material/src/PaginationItem/PaginationItem.test.js index afc9f71aeac12c..ad7f89d4a0278f 100644 --- a/packages/mui-material/src/PaginationItem/PaginationItem.test.js +++ b/packages/mui-material/src/PaginationItem/PaginationItem.test.js @@ -14,7 +14,7 @@ describe('', () => { refInstanceof: window.HTMLButtonElement, testVariantProps: { variant: 'foo' }, testStateOverrides: { prop: 'variant', value: 'outlined', styleKey: 'outlined' }, - slots: { + components: { first: {}, last: {}, previous: {}, diff --git a/packages/mui-material/src/Popper/Popper.test.js b/packages/mui-material/src/Popper/Popper.test.js index 531c1e94388e61..edf6fbbdf0fc31 100644 --- a/packages/mui-material/src/Popper/Popper.test.js +++ b/packages/mui-material/src/Popper/Popper.test.js @@ -25,7 +25,7 @@ describe('', () => { inheritComponent: 'div', render, refInstanceof: window.HTMLDivElement, - slots: { + components: { root: {}, }, skip: [ diff --git a/packages/mui-material/src/Slider/Slider.test.js b/packages/mui-material/src/Slider/Slider.test.js index b448740edeaa52..29709039aed0e5 100644 --- a/packages/mui-material/src/Slider/Slider.test.js +++ b/packages/mui-material/src/Slider/Slider.test.js @@ -40,7 +40,7 @@ describe('', () => { testDeepOverrides: { slotName: 'thumb', slotClassName: classes.thumb }, testVariantProps: { color: 'primary', orientation: 'vertical', size: 'small' }, testStateOverrides: { prop: 'color', value: 'secondary', styleKey: 'colorSecondary' }, - slots: { + components: { root: { expectedClassName: classes.root, }, diff --git a/packages/mui-material/src/StepLabel/StepLabel.test.js b/packages/mui-material/src/StepLabel/StepLabel.test.js index dcc23e5cc290e3..1eeed002792861 100644 --- a/packages/mui-material/src/StepLabel/StepLabel.test.js +++ b/packages/mui-material/src/StepLabel/StepLabel.test.js @@ -17,7 +17,7 @@ describe('', () => { render, refInstanceof: window.HTMLSpanElement, testVariantProps: { error: true }, - slots: { + components: { label: { expectedClassName: classes.label }, }, skip: ['componentProp', 'componentsProp', 'slotsProp'], diff --git a/packages/mui-material/src/Tooltip/Tooltip.test.js b/packages/mui-material/src/Tooltip/Tooltip.test.js index 9193eeb52c4733..d2766089bac1e6 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.test.js +++ b/packages/mui-material/src/Tooltip/Tooltip.test.js @@ -42,7 +42,7 @@ describe('', () => { refInstanceof: window.HTMLButtonElement, testRootOverrides: { slotName: 'popper', slotClassName: classes.popper }, testDeepOverrides: { slotName: 'tooltip', slotClassName: classes.tooltip }, - slots: { + components: { popper: { expectedClassName: classes.popper, testWithComponent: TestPopper, From e14e4fbb82f445bec69349eacd79fd55052634e6 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 21 Nov 2022 17:05:00 +0700 Subject: [PATCH 009/139] add comment --- test/utils/describeConformance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index 4290f5134d8ecf..b8fda96fa4a06a 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -487,7 +487,7 @@ function testSlotPropsProp(element, getOptions) { */ /** - * >>> This will be DEPRECEATED in v6 <<< + * >>> The `components` and `componentsProps` will be deprecated in v6, and removed in v7 (together with this test suite) <<< * * MUI components have a `components` prop that allows rendering a different * Components from @inheritComponent From bd95b3465be7e3ca2f11172c95014d9e03292f69 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 21 Nov 2022 17:36:34 +0700 Subject: [PATCH 010/139] fix misused variables --- test/utils/describeConformance.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index b8fda96fa4a06a..d304012ccdcbd0 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -496,7 +496,7 @@ function testSlotPropsProp(element, getOptions) { */ function testMaterialUIComponentsProp(element, getOptions) { describe('prop components:', () => { - const { components, slots, render } = getOptions(); + const { components, render } = getOptions(); if (!components) { // the tests below will cover all the components @@ -535,12 +535,6 @@ function testMaterialUIComponentsProp(element, getOptions) { it(`prioritizes the 'slots.${slotName}' over components.${capitalize( slotName, )} if both are defined`, () => { - if (!slots || !slots[slotName]) { - throw new Error( - `missing "slots" in options, unable to test "slots" overrides "components" for this Material UI component`, - ); - } - // eslint-disable-next-line react/prop-types const ComponentForComponentsProp = React.forwardRef(({ children }, ref) => { const SlotComponent = slotOptions.testWithComponent ?? 'div'; @@ -553,7 +547,7 @@ function testMaterialUIComponentsProp(element, getOptions) { // eslint-disable-next-line react/prop-types const ComponentForSlotsProp = React.forwardRef(({ children }, ref) => { - const SlotComponent = slots[slotName].testWithComponent ?? 'div'; + const SlotComponent = components[slotName].testWithComponent ?? 'div'; return ( {children} From 1005da08008a0719227cbf0d2dbd06da7f860735 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 21 Nov 2022 17:46:33 +0700 Subject: [PATCH 011/139] remove outdated tests --- test/utils/describeConformance.js | 188 +++++------------------------- 1 file changed, 27 insertions(+), 161 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index d304012ccdcbd0..e6540220e2cc7b 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -236,41 +236,18 @@ function testSlotsProp(element, getOptions) { )); forEachSlot(slots, (slotName, slotOptions) => { - it(`allows overriding the ${slotName} slot with a component using the components.${capitalize( - slotName, - )} prop`, () => { + it(`allows overriding the ${slotName} slot with a component using the slots.${slotName} prop`, () => { if (!render) { throwMissingPropError('render'); } - const slotComponent = slotOptions.testWithComponent ?? CustomComponent; - - const components = { - [capitalize(slotName)]: slotComponent, - }; - - const { queryByTestId } = render(React.cloneElement(element, { components })); - const renderedElement = queryByTestId('custom'); - expect(renderedElement).not.to.equal(null); - if (slotOptions.expectedClassName) { - expect(renderedElement).to.have.class(slotOptions.expectedClassName); - } - }); - - it(`allows overriding the ${slotName} slot with a component using the slots.${capitalize( - slotName, - )} prop`, () => { - if (!render) { - throwMissingPropError('render'); - } - - const slotComponent = slotOptions.testWithComponent ?? CustomComponent; - - const components = { - [slotName]: slotComponent, - }; - - const { queryByTestId } = render(React.cloneElement(element, { slots: components })); + const { queryByTestId } = render( + React.cloneElement(element, { + slots: { + [slotName]: slotOptions.testWithComponent ?? CustomComponent, + }, + }), + ); const renderedElement = queryByTestId('custom'); expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { @@ -278,82 +255,7 @@ function testSlotsProp(element, getOptions) { } }); - it(`prioritizes the 'slots.${slotName}' over components.${capitalize( - slotName, - )} if both are defined`, () => { - if (!render) { - throwMissingPropError('render'); - } - - // eslint-disable-next-line react/prop-types - const ComponentForComponentsProp = React.forwardRef(({ children }, ref) => { - const SlotComponent = slotOptions.testWithComponent ?? 'div'; - return ( - - {children} - - ); - }); - - // eslint-disable-next-line react/prop-types - const ComponentForSlotsProp = React.forwardRef(({ children }, ref) => { - const SlotComponent = slotOptions.testWithComponent ?? 'div'; - return ( - - {children} - - ); - }); - - const components = { - [capitalize(slotName)]: ComponentForComponentsProp, - }; - - const slotOverrides = { - [slotName]: ComponentForSlotsProp, - }; - - const { queryByTestId } = render( - React.cloneElement(element, { components, slots: slotOverrides }), - ); - - expect(queryByTestId('from-slots')).not.to.equal(null); - expect(queryByTestId('from-components')).to.equal(null); - }); - if (slotOptions.testWithElement !== null) { - it(`allows overriding the ${slotName} slot with an element using the components.${capitalize( - slotName, - )} prop`, () => { - if (!render) { - throwMissingPropError('render'); - } - - const slotElement = slotOptions.testWithElement ?? 'i'; - - const components = { - [capitalize(slotName)]: slotElement, - }; - - const componentsProps = { - [slotName]: { - 'data-testid': 'customized', - }, - }; - - const { queryByTestId } = render( - React.cloneElement(element, { components, componentsProps }), - ); - - const renderedElement = queryByTestId('customized'); - expect(renderedElement).not.to.equal(null); - - expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); - if (slotOptions.expectedClassName) { - expect(renderedElement).to.have.class(slotOptions.expectedClassName); - } - }); - it(`allows overriding the ${slotName} slot with an element using the slots.${slotName} prop`, () => { if (!render) { throwMissingPropError('render'); @@ -361,18 +263,17 @@ function testSlotsProp(element, getOptions) { const slotElement = slotOptions.testWithElement ?? 'i'; - const components = { - [slotName]: slotElement, - }; - - const slotProps = { - [slotName]: { - 'data-testid': 'customized', - }, - }; - const { queryByTestId } = render( - React.cloneElement(element, { slots: components, slotProps }), + React.cloneElement(element, { + slots: { + [slotName]: slotElement, + }, + componentsProps: { + [slotName]: { + 'data-testid': 'customized', + }, + }, + }), ); const renderedElement = queryByTestId('customized'); @@ -395,30 +296,16 @@ function testSlotPropsProp(element, getOptions) { } forEachSlot(slots, (slotName, slotOptions) => { - it(`sets custom properties on the ${slotName} slot's element with the componentsProps.${slotName} prop`, () => { - const componentsProps = { - [slotName]: { - 'data-testid': 'custom', - }, - }; - - const { queryByTestId } = render(React.cloneElement(element, { componentsProps })); - const slotComponent = queryByTestId('custom'); - expect(slotComponent).not.to.equal(null); - - if (slotOptions.expectedClassName) { - expect(slotComponent).to.have.class(slotOptions.expectedClassName); - } - }); - it(`sets custom properties on the ${slotName} slot's element with the slotProps.${slotName} prop`, () => { - const slotProps = { - [slotName]: { - 'data-testid': 'custom', - }, - }; - - const { queryByTestId } = render(React.cloneElement(element, { slotProps })); + const { queryByTestId } = render( + React.cloneElement(element, { + slotProps: { + [slotName]: { + 'data-testid': 'custom', + }, + }, + }), + ); const slotComponent = queryByTestId('custom'); expect(slotComponent).not.to.equal(null); @@ -427,27 +314,6 @@ function testSlotPropsProp(element, getOptions) { } }); - it(`prioritizes the 'slotProps.${slotName}' over componentsProps.${slotName} if both are defined`, () => { - const componentsProps = { - [slotName]: { - 'data-testid': 'custom', - 'data-from-components-props': 'true', - }, - }; - - const slotProps = { - [slotName]: { - 'data-testid': 'custom', - 'data-from-slot-props': 'true', - }, - }; - - const { queryByTestId } = render(React.cloneElement(element, { componentsProps, slotProps })); - const slotComponent = queryByTestId('custom'); - expect(slotComponent).to.have.attribute('data-from-slot-props', 'true'); - expect(slotComponent).not.to.have.attribute('data-from-components-props'); - }); - if (slotOptions.expectedClassName) { it(`merges the class names provided in slotsProps.${slotName} with the built-in ones`, () => { const slotProps = { From 97713b2a9e61cbc2251a42d49859671bad676928 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 21 Nov 2022 17:48:43 +0700 Subject: [PATCH 012/139] add comment --- test/utils/describeConformance.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index e6540220e2cc7b..bbaac49ebb9e19 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -248,7 +248,7 @@ function testSlotsProp(element, getOptions) { }, }), ); - const renderedElement = queryByTestId('custom'); + const renderedElement = queryByTestId('custom'); // use `query*` instead of `get*` to bypass hidden element, we just want to check the overriding functionality. expect(renderedElement).not.to.equal(null); if (slotOptions.expectedClassName) { expect(renderedElement).to.have.class(slotOptions.expectedClassName); @@ -276,7 +276,7 @@ function testSlotsProp(element, getOptions) { }), ); - const renderedElement = queryByTestId('customized'); + const renderedElement = queryByTestId('customized'); // use `query*` instead of `get*` to bypass hidden element, we just want to check the overriding functionality. expect(renderedElement).not.to.equal(null); expect(renderedElement.nodeName.toLowerCase()).to.equal(slotElement); @@ -306,7 +306,7 @@ function testSlotPropsProp(element, getOptions) { }, }), ); - const slotComponent = queryByTestId('custom'); + const slotComponent = queryByTestId('custom'); // use `query*` instead of `get*` to bypass hidden element, we just want to check the overriding functionality. expect(slotComponent).not.to.equal(null); if (slotOptions.expectedClassName) { From 5585d42cccff24dee65091aa3c578b68d2e274db Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 21 Nov 2022 17:59:25 +0700 Subject: [PATCH 013/139] fix pagination tests --- .../mui-material/src/PaginationItem/PaginationItem.test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/mui-material/src/PaginationItem/PaginationItem.test.js b/packages/mui-material/src/PaginationItem/PaginationItem.test.js index ad7f89d4a0278f..b6bad68675738c 100644 --- a/packages/mui-material/src/PaginationItem/PaginationItem.test.js +++ b/packages/mui-material/src/PaginationItem/PaginationItem.test.js @@ -22,10 +22,9 @@ describe('', () => { }, skip: [ 'componentProp', - 'componentsProp', // uses non-standard camel-case fields in `components` - 'slotsProp', - 'slotPropsProp', + 'componentsProp', + 'componentsPropsProp', ], })); From ecbdb3dcb98c5c87b64e9d5aff5e555ed216698f Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 21 Nov 2022 18:01:05 +0700 Subject: [PATCH 014/139] mark components and slots as optional --- test/utils/describeConformance.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index bbaac49ebb9e19..5ff3699f1632d3 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -348,8 +348,8 @@ function testSlotPropsProp(element, getOptions) { * @property {object} [testVariantProps] * @property {(mount: (node: React.ReactNode) => import('enzyme').ReactWrapper) => (node: React.ReactNode) => import('enzyme').ReactWrapper} [wrapMount] - You can use this option to mount the component with enzyme in a WrapperComponent. Make sure the returned node corresponds to the input node and not the wrapper component. * @property {boolean} [testCustomVariant] - The component supports custom variant - * @property {object} components - Material UI's components prop - * @property {object} slots - MUI slots prop + * @property {object} [components] - Material UI's components prop + * @property {object} [slots] - MUI slots prop */ /** From b56ab7449577a39564418f857e33440a92322fe4 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 31 Oct 2022 09:46:13 +0000 Subject: [PATCH 015/139] [CircularProgress] Apply useSlot() --- .../src/CircularProgress/CircularProgress.tsx | 52 +++++++++---------- .../CircularProgress/CircularProgressProps.ts | 24 +++++++-- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx index ec6cb7d6d76de9..2c8164f2a346c0 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx @@ -1,5 +1,4 @@ import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { css, keyframes } from '@mui/system'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -8,6 +7,7 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import { getCircularProgressUtilityClass } from './circularProgressClasses'; import { CircularProgressOwnerState, @@ -204,8 +204,6 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref }); const { - componentsProps = {}, - component = 'span', children, className, color = 'primary', @@ -230,14 +228,13 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), elementType: CircularProgressRoot, - externalSlotProps: componentsProps.root, externalForwardedProps: other, ownerState, additionalProps: { - ref, - as: component, role: 'progressbar', style: { // Setting this CSS varaible via inline-style @@ -245,44 +242,43 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref // `value` prop updates '--CircularProgress-percent': value, }, + ...(value && + determinate && { + 'aria-valuenow': + typeof value === 'number' ? Math.round(value) : Math.round(Number(value || 0)), + }), }, - className: clsx(classes.root, className), - ...(value && - determinate && { - 'aria-valuenow': - typeof value === 'number' ? Math.round(value) : Math.round(Number(value || 0)), - }), }); - const svgProps = useSlotProps({ + const [SlotSvg, svgProps] = useSlot('svg', { + className: classes.svg, elementType: CircularProgressSvg, - externalSlotProps: componentsProps.svg, + externalForwardedProps: other, ownerState, - className: classes.svg, }); - const trackProps = useSlotProps({ + const [SlotTrack, trackProps] = useSlot('track', { + className: classes.track, elementType: CircularProgressTrack, - externalSlotProps: componentsProps.track, + externalForwardedProps: other, ownerState, - className: classes.track, }); - const progressProps = useSlotProps({ + const [SlotProgress, progressProps] = useSlot('progress', { + className: classes.progress, elementType: CircularProgressProgress, - externalSlotProps: componentsProps.progress, + externalForwardedProps: other, ownerState, - className: classes.progress, }); return ( - - - - - + + + + + {children} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts index 968e87ac2113c5..980b1b03fa8e10 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts +++ b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts @@ -10,10 +10,26 @@ export interface CircularProgressPropsSizeOverrides {} export interface CircularProgressPropsVariantOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'span', { sx?: SxProps }, CircularProgressOwnerState>; - svg?: SlotComponentProps<'svg', { sx?: SxProps }, CircularProgressOwnerState>; - track?: SlotComponentProps<'circle', { sx?: SxProps }, CircularProgressOwnerState>; - progress?: SlotComponentProps<'circle', { sx?: SxProps }, CircularProgressOwnerState>; + root?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + CircularProgressOwnerState + >; + svg?: SlotComponentProps< + 'svg', + { component?: React.ElementType; sx?: SxProps }, + CircularProgressOwnerState + >; + track?: SlotComponentProps< + 'circle', + { component?: React.ElementType; sx?: SxProps }, + CircularProgressOwnerState + >; + progress?: SlotComponentProps< + 'circle', + { component?: React.ElementType; sx?: SxProps }, + CircularProgressOwnerState + >; } export interface CircularProgressTypeMap

{ From 063d2e715dd60b19fd493d2ab03c43ee62e12230 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 11:24:16 +0000 Subject: [PATCH 016/139] Apply useSlot to LinearProgress --- .../src/LinearProgress/LinearProgress.tsx | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx index 96487bb358f763..53892bb5a7f1f7 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx @@ -1,5 +1,4 @@ import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { css, keyframes } from '@mui/system'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; @@ -8,6 +7,7 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import { getLinearProgressUtilityClass } from './linearProgressClasses'; import { LinearProgressOwnerState, @@ -145,7 +145,6 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { }); const { - component = 'div', children, className, color = 'primary', @@ -170,14 +169,13 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), elementType: LinearProgressRoot, - externalSlotProps: {}, externalForwardedProps: other, ownerState, additionalProps: { - ref, - as: component, role: 'progressbar', style: { // Setting this CSS varaible via inline-style @@ -185,15 +183,14 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { // `value` prop updates '--LinearProgress-percent': value, }, + ...(typeof value === 'number' && + determinate && { + 'aria-valuenow': Math.round(value), + }), }, - className: clsx(classes.root, className), - ...(typeof value === 'number' && - determinate && { - 'aria-valuenow': Math.round(value), - }), }); - return {children}; + return {children}; }) as OverridableComponent; LinearProgress.propTypes /* remove-proptypes */ = { From a68cac34210d1b8414532e91c20cf9de5b55f1b8 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 11:39:21 +0000 Subject: [PATCH 017/139] Apply useSlot to Chip --- packages/mui-joy/src/Chip/Chip.tsx | 58 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index 93ca70b547a065..353d359bd26d9b 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import clsx from 'clsx'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, useButton } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize, unstable_useId as useId } from '@mui/utils'; import { useThemeProps } from '../styles'; @@ -10,6 +9,7 @@ import styled from '../styles/styled'; import chipClasses, { getChipUtilityClass } from './chipClasses'; import { ChipProps, ChipOwnerState, ChipTypeMap } from './ChipProps'; import ChipContext from './ChipContext'; +import useSlot from '../utils/useSlot'; const useUtilityClasses = (ownerState: ChipOwnerState) => { const { disabled, size, color, clickable, variant, focusVisible } = ownerState; @@ -203,7 +203,6 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { className, componentsProps = {}, color = 'primary', - component, onClick, disabled = false, size = 'md', @@ -216,7 +215,6 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const clickable = !!onClick || !!componentsProps.action; const ownerState: ChipOwnerState = { ...props, - component, disabled, size, color, @@ -240,41 +238,49 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const classes = useUtilityClasses(ownerState); - const labelProps = useSlotProps({ - elementType: ChipLabel, - externalSlotProps: componentsProps.label, + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: ChipRoot, + externalForwardedProps: { ...other, componentsProps }, ownerState, + }); + + const [SlotLabel, labelProps] = useSlot('label', { className: classes.label, + elementType: ChipLabel, + externalForwardedProps: { ...other, componentsProps }, + ownerState, }); // @ts-ignore internal logic. const id = useId(labelProps.id); - const actionProps = useSlotProps({ + const [SlotAction, actionProps] = useSlot('action', { + className: classes.action, elementType: ChipAction, + externalForwardedProps: { ...other, componentsProps }, + ownerState, getSlotProps: getRootProps, - externalSlotProps: componentsProps.action, additionalProps: { 'aria-labelledby': id, as: resolvedActionProps?.component, onClick, }, - ownerState, - className: classes.action, }); - const startDecoratorProps = useSlotProps({ + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, elementType: ChipStartDecorator, - externalSlotProps: componentsProps.startDecorator, + externalForwardedProps: { ...other, componentsProps }, ownerState, - className: classes.startDecorator, }); - const endDecoratorProps = useSlotProps({ + const [SlotEndDecorator, endDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, elementType: ChipEndDecorator, - externalSlotProps: componentsProps.endDecorator, + externalForwardedProps: { ...other, componentsProps }, ownerState, - className: classes.endDecorator, }); const chipContextValue = React.useMemo( @@ -284,25 +290,19 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { return ( - - {clickable && } + + {clickable && } {/* label is always the first element for integrating with other controls, eg. Checkbox, Radio. Use CSS order to rearrange position */} - + {children} - + {startDecorator && ( - {startDecorator} + {startDecorator} )} - {endDecorator && {endDecorator}} - + {endDecorator && {endDecorator}} + ); }) as OverridableComponent; From 74e63a3eddf8ff65b10c6bb68d79b23bb9dba617 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 11:39:54 +0000 Subject: [PATCH 018/139] Apply useSlot to ChipDelete --- packages/mui-joy/src/ChipDelete/ChipDelete.tsx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx index ae77b64ae334c7..9fb90578180ce5 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx +++ b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx @@ -3,13 +3,13 @@ import PropTypes from 'prop-types'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } from '@mui/utils'; import { unstable_composeClasses as composeClasses, useButton } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { useThemeProps } from '../styles'; import styled from '../styles/styled'; import Cancel from '../internal/svg-icons/Cancel'; import chipDeleteClasses, { getChipDeleteUtilityClass } from './chipDeleteClasses'; import { ChipDeleteProps, ChipDeleteOwnerState, ChipDeleteTypeMap } from './ChipDeleteProps'; import ChipContext from '../Chip/ChipContext'; +import useSlot from '../utils/useSlot'; const useUtilityClasses = (ownerState: ChipDeleteOwnerState) => { const { focusVisible, variant, color, disabled } = ownerState; @@ -70,7 +70,6 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) { }); const { - component, children, variant: variantProp, color: colorProp, @@ -101,19 +100,16 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: ChipDeleteRoot, - getSlotProps: getRootProps, - externalSlotProps: {}, externalForwardedProps: other, ownerState, - additionalProps: { - as: component, - }, - className: classes.root, + getSlotProps: getRootProps, }); - return {children ?? }; + return {children ?? }; }) as OverridableComponent; ChipDelete.propTypes /* remove-proptypes */ = { From 4ae13c3baf43523c7144495ba7044b01d28a31fb Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 11:41:56 +0000 Subject: [PATCH 019/139] [Chip] Add optional component prop to each slot in ComponentsProps --- packages/mui-joy/src/Chip/ChipProps.ts | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/mui-joy/src/Chip/ChipProps.ts b/packages/mui-joy/src/Chip/ChipProps.ts index 9ef5020cf985e1..c2842929a6dc43 100644 --- a/packages/mui-joy/src/Chip/ChipProps.ts +++ b/packages/mui-joy/src/Chip/ChipProps.ts @@ -10,15 +10,32 @@ export interface ChipPropsSizeOverrides {} export interface ChipPropsVariantOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, ChipOwnerState>; - label?: SlotComponentProps<'span', { sx?: SxProps }, ChipOwnerState>; + root?: SlotComponentProps<'div', { component?: React.ElementType; sx?: SxProps }, ChipOwnerState>; + label?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + ChipOwnerState + >; action?: SlotComponentProps< 'button', - { sx?: SxProps; component?: React.ElementType; href?: string; to?: string }, + { + component?: React.ElementType; + sx?: SxProps; + href?: string; + to?: string; + }, + ChipOwnerState + >; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + ChipOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, ChipOwnerState >; - startDecorator?: SlotComponentProps<'span', { sx?: SxProps }, ChipOwnerState>; - endDecorator?: SlotComponentProps<'span', { sx?: SxProps }, ChipOwnerState>; } export interface ChipTypeMap

{ From bc5b30ba43f2b8b725439170c5716385ed1c9677 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 15:08:20 +0000 Subject: [PATCH 020/139] [Tooltip] Improve the typing of components and componentsProps --- packages/mui-joy/src/Tooltip/TooltipProps.ts | 41 +++++++++----------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/mui-joy/src/Tooltip/TooltipProps.ts b/packages/mui-joy/src/Tooltip/TooltipProps.ts index 795e678267e36c..968c4570bf4457 100644 --- a/packages/mui-joy/src/Tooltip/TooltipProps.ts +++ b/packages/mui-joy/src/Tooltip/TooltipProps.ts @@ -1,22 +1,27 @@ import * as React from 'react'; import { PopperUnstyledProps } from '@mui/base'; -import { MUIStyledCommonProps } from '@mui/system'; +import { SlotComponentProps } from '@mui/base/utils'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; -export type RootProps = Omit & { - /** - * The system prop that allows defining system overrides as well as additional CSS styles. - */ - sx?: SxProps; -}; - export type TooltipSlot = 'root' | 'arrow'; export interface TooltipPropsVariantOverrides {} export interface TooltipPropsColorOverrides {} export interface TooltipPropsSizeOverrides {} -export interface TooltipComponentsPropsOverrides {} + +interface ComponentsProps { + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps } & Omit, + TooltipOwnerState + >; + arrow?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + TooltipOwnerState + >; +} export interface TooltipTypeMap

{ props: P & { @@ -35,25 +40,17 @@ export interface TooltipTypeMap

{ */ color?: OverridableStringUnion; /** - * The components used for each slot inside the Tooltip. - * Either a string to use a HTML element or a component. - * @default {} + * Replace the default slots. */ components?: { - Root?: React.ElementType; - Arrow?: React.ElementType; + root?: React.ElementType; + arrow?: React.ElementType; }; /** - * The props used for each slot inside the Tooltip. - * Note that `componentsProps.root` prop values win over `RootProps` if both are applied. + * The props used for each slot inside. * @default {} */ - componentsProps?: { - root?: Partial & TooltipComponentsPropsOverrides; - arrow?: React.HTMLProps & - MUIStyledCommonProps & - TooltipComponentsPropsOverrides; - }; + componentsProps?: ComponentsProps; /** * Set to `true` if the `title` acts as an accessible description. * By default the `title` acts as an accessible label for the child. From c8bd681861748aa02d62181f9c8f0a6d1d6fa7b9 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 15:09:54 +0000 Subject: [PATCH 021/139] Apply useSlot to Tooltip --- packages/mui-joy/src/Tooltip/Tooltip.tsx | 62 ++++++++---------------- 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/packages/mui-joy/src/Tooltip/Tooltip.tsx b/packages/mui-joy/src/Tooltip/Tooltip.tsx index 35108acd660196..b7215868526dcf 100644 --- a/packages/mui-joy/src/Tooltip/Tooltip.tsx +++ b/packages/mui-joy/src/Tooltip/Tooltip.tsx @@ -10,19 +10,12 @@ import { unstable_useId as useId, } from '@mui/utils'; import { PopperUnstyled, unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; -import { WithCommonProps } from '@mui/base/utils/mergeSlotProps'; -import { MUIStyledCommonProps } from '@mui/system'; import { OverridableComponent } from '@mui/types'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import { getTooltipUtilityClass } from './tooltipClasses'; -import { - TooltipComponentsPropsOverrides, - TooltipProps, - TooltipOwnerState, - TooltipTypeMap, -} from './TooltipProps'; +import { TooltipProps, TooltipOwnerState, TooltipTypeMap } from './TooltipProps'; const useUtilityClasses = (ownerState: TooltipOwnerState) => { const { arrow, variant, color, size, placement, touch } = ownerState; @@ -214,8 +207,6 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { children, className, arrow = false, - components = {}, - componentsProps = {}, describeChild = false, disableFocusListener = false, disableHoverListener = false, @@ -577,30 +568,26 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) { const classes = useUtilityClasses(ownerState); - const RootComponent = components.Root ?? TooltipRoot; - const ArrowComponent = components.Arrow ?? TooltipArrow; - - const rootProps = useSlotProps({ - elementType: RootComponent, - externalSlotProps: componentsProps.root, + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, + elementType: TooltipRoot, + externalForwardedProps: other, ownerState, - className: clsx(classes.root, componentsProps.root?.className), }); - const tooltipArrowProps = useSlotProps({ - elementType: ArrowComponent, - externalSlotProps: componentsProps.arrow as WithCommonProps< - React.HTMLProps & MUIStyledCommonProps & TooltipComponentsPropsOverrides - >, + const [SlotArrow, arrowProps] = useSlot('arrow', { + className: classes.arrow, + elementType: TooltipArrow, + externalForwardedProps: other, ownerState, - className: clsx(classes.arrow, componentsProps.arrow?.className), }); return ( {React.isValidElement(children) && React.cloneElement(children, childrenProps)} {title} - {arrow ? ( - - ) : null} + {arrow ? : null} ); @@ -666,22 +645,19 @@ Tooltip.propTypes /* remove-proptypes */ = { PropTypes.string, ]), /** - * The components used for each slot inside the Tooltip. - * Either a string to use a HTML element or a component. - * @default {} + * Replace the default slots. */ components: PropTypes.shape({ - Arrow: PropTypes.elementType, - Root: PropTypes.elementType, + arrow: PropTypes.elementType, + root: PropTypes.elementType, }), /** - * The props used for each slot inside the Tooltip. - * Note that `componentsProps.root` prop values win over `RootProps` if both are applied. + * The props used for each slot inside. * @default {} */ componentsProps: PropTypes.shape({ - arrow: PropTypes.object, - root: PropTypes.object, + arrow: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), /** * Set to `true` if the `title` acts as an accessible description. From ddec72d32ef4c136b62ca2383caea9d9c4a31a25 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 15:39:15 +0000 Subject: [PATCH 022/139] [Chip, CircularProgress] Add components prop to API docs --- packages/mui-joy/src/Chip/Chip.tsx | 13 +++++++++---- packages/mui-joy/src/Chip/ChipProps.ts | 12 +++++++++++- .../src/CircularProgress/CircularProgress.tsx | 12 ++++++++---- .../src/CircularProgress/CircularProgressProps.ts | 11 ++++++++++- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index 353d359bd26d9b..f93ebe033a0dbe 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -329,12 +329,17 @@ Chip.propTypes /* remove-proptypes */ = { PropTypes.string, ]), /** - * The component used for the root node. - * Either a string to use a HTML element or a component. + * Replace the default slots. */ - component: PropTypes.elementType, + components: PropTypes.shape({ + action: PropTypes.elementType, + endDecorator: PropTypes.elementType, + label: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), /** - * The props used for each slot inside the component. + * The props used for each slot inside. * @default {} */ componentsProps: PropTypes.shape({ diff --git a/packages/mui-joy/src/Chip/ChipProps.ts b/packages/mui-joy/src/Chip/ChipProps.ts index c2842929a6dc43..62ef1100606375 100644 --- a/packages/mui-joy/src/Chip/ChipProps.ts +++ b/packages/mui-joy/src/Chip/ChipProps.ts @@ -45,7 +45,17 @@ export interface ChipTypeMap

{ */ children?: React.ReactNode; /** - * The props used for each slot inside the component. + * Replace the default slots. + */ + components?: { + root?: React.ElementType; + label?: React.ElementType; + action?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + }; + /** + * The props used for each slot inside. * @default {} */ componentsProps?: ComponentsProps; diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx index 2c8164f2a346c0..1b62d804d1fbba 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx @@ -304,12 +304,16 @@ CircularProgress.propTypes /* remove-proptypes */ = { PropTypes.string, ]), /** - * The component used for the root node. - * Either a string to use a HTML element or a component. + * Replace the default slots. */ - component: PropTypes.elementType, + components: PropTypes.shape({ + progress: PropTypes.elementType, + root: PropTypes.elementType, + svg: PropTypes.elementType, + track: PropTypes.elementType, + }), /** - * The props used for each slot inside the CircularProgress. + * The props used for each slot inside. * @default {} */ componentsProps: PropTypes.shape({ diff --git a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts index 980b1b03fa8e10..934de9a771d15b 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts +++ b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts @@ -40,7 +40,16 @@ export interface CircularProgressTypeMap

Date: Tue, 1 Nov 2022 15:53:00 +0000 Subject: [PATCH 024/139] [Chip, ChipDelete, LinearProgress, CircularProgress] Ensure API doc of component prop is generated + improve code --- packages/mui-joy/src/Chip/Chip.tsx | 18 +++++++++++++----- packages/mui-joy/src/ChipDelete/ChipDelete.tsx | 3 ++- .../src/CircularProgress/CircularProgress.tsx | 16 ++++++++++++---- .../src/LinearProgress/LinearProgress.tsx | 3 ++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index f93ebe033a0dbe..ea154eeaed94d6 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -201,8 +201,9 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const { children, className, - componentsProps = {}, color = 'primary', + component = 'div', + componentsProps = {}, onClick, disabled = false, size = 'md', @@ -218,6 +219,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { disabled, size, color, + component, variant, clickable, focusVisible: false, @@ -237,19 +239,20 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { ownerState.focusVisible = focusVisible; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component, componentsProps }; const [SlotRoot, rootProps] = useSlot('root', { ref, className: clsx(classes.root, className), elementType: ChipRoot, - externalForwardedProps: { ...other, componentsProps }, + externalForwardedProps, ownerState, }); const [SlotLabel, labelProps] = useSlot('label', { className: classes.label, elementType: ChipLabel, - externalForwardedProps: { ...other, componentsProps }, + externalForwardedProps, ownerState, }); @@ -259,7 +262,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const [SlotAction, actionProps] = useSlot('action', { className: classes.action, elementType: ChipAction, - externalForwardedProps: { ...other, componentsProps }, + externalForwardedProps, ownerState, getSlotProps: getRootProps, additionalProps: { @@ -272,7 +275,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { className: classes.startDecorator, elementType: ChipStartDecorator, - externalForwardedProps: { ...other, componentsProps }, + externalForwardedProps, ownerState, }); @@ -328,6 +331,11 @@ Chip.propTypes /* remove-proptypes */ = { 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, /** * Replace the default slots. */ diff --git a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx index 7611cd6ade6a56..89e7fe5ea54a19 100644 --- a/packages/mui-joy/src/ChipDelete/ChipDelete.tsx +++ b/packages/mui-joy/src/ChipDelete/ChipDelete.tsx @@ -70,8 +70,8 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) { }); const { - component, children, + component = 'button', variant: variantProp, color: colorProp, disabled: disabledProp, @@ -93,6 +93,7 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) { const ownerState = { ...props, + component, disabled, variant, color, diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx index 1b62d804d1fbba..2a23b3f864ddd3 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx @@ -207,6 +207,7 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref children, className, color = 'primary', + component = 'span', size = 'md', variant = 'soft', thickness, @@ -218,6 +219,7 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref const ownerState = { ...props, color, + component, size, variant, thickness, @@ -227,12 +229,13 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; const [SlotRoot, rootProps] = useSlot('root', { ref, className: clsx(classes.root, className), elementType: CircularProgressRoot, - externalForwardedProps: other, + externalForwardedProps, ownerState, additionalProps: { role: 'progressbar', @@ -253,21 +256,21 @@ const CircularProgress = React.forwardRef(function CircularProgress(inProps, ref const [SlotSvg, svgProps] = useSlot('svg', { className: classes.svg, elementType: CircularProgressSvg, - externalForwardedProps: other, + externalForwardedProps, ownerState, }); const [SlotTrack, trackProps] = useSlot('track', { className: classes.track, elementType: CircularProgressTrack, - externalForwardedProps: other, + externalForwardedProps, ownerState, }); const [SlotProgress, progressProps] = useSlot('progress', { className: classes.progress, elementType: CircularProgressProgress, - externalForwardedProps: other, + externalForwardedProps, ownerState, }); @@ -303,6 +306,11 @@ CircularProgress.propTypes /* remove-proptypes */ = { 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, /** * Replace the default slots. */ diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx index 0c3b4bf6049808..b6f8569bd870e2 100644 --- a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx @@ -145,10 +145,10 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { }); const { - component, children, className, color = 'primary', + component = 'span', size = 'md', variant = 'soft', thickness, @@ -160,6 +160,7 @@ const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { const ownerState = { ...props, color, + component, size, variant, thickness, From 109fa4aaae922e07c2c42954fd3e105307d73b45 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 15:59:12 +0000 Subject: [PATCH 025/139] Apply useSlot to Avatar and pass className prop to the root --- packages/mui-joy/src/Avatar/Avatar.tsx | 55 +++++++++++++--------- packages/mui-joy/src/Avatar/AvatarProps.ts | 26 ++++++++-- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/packages/mui-joy/src/Avatar/Avatar.tsx b/packages/mui-joy/src/Avatar/Avatar.tsx index 1102657613f33e..ceaa83481ca952 100644 --- a/packages/mui-joy/src/Avatar/Avatar.tsx +++ b/packages/mui-joy/src/Avatar/Avatar.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import Person from '../internal/svg-icons/Person'; import { getAvatarUtilityClass } from './avatarClasses'; @@ -149,6 +150,7 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) { color: colorProp = 'neutral', component = 'div', componentsProps = {}, + className, size: sizeProp = 'md', variant: variantProp = 'soft', imgProps, @@ -186,11 +188,17 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) { const hasImgNotFailing = hasImg && loaded !== 'error'; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component, componentsProps }; - const imageProps = useSlotProps({ - elementType: AvatarImg, - externalSlotProps: componentsProps.img, + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: AvatarRoot, + externalForwardedProps, ownerState, + }); + + const [SlotImg, imageProps] = useSlot('img', { additionalProps: { alt, src, @@ -198,38 +206,29 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) { ...imgProps, }, className: classes.img, + elementType: AvatarImg, + externalForwardedProps, + ownerState, }); - const fallbackProps = useSlotProps({ + const [SlotFallback, fallbackProps] = useSlot('fallback', { + className: classes.fallback, elementType: AvatarFallback, - externalSlotProps: componentsProps.fallback, + externalForwardedProps, ownerState, - className: classes.fallback, }); if (hasImgNotFailing) { - children = ; + children = ; } else if (childrenProp != null) { children = childrenProp; } else if (hasImg && alt) { children = alt[0]; } else { - children = ; + children = ; } - const rootProps = useSlotProps({ - elementType: AvatarRoot, - externalSlotProps: componentsProps.root, - ownerState, - externalForwardedProps: other, - additionalProps: { - ref, - as: component, - }, - className: classes.root, - }); - - return {children}; + return {children}; }) as OverridableComponent; Avatar.propTypes /* remove-proptypes */ = { @@ -247,6 +246,10 @@ Avatar.propTypes /* remove-proptypes */ = { * This can be an element, or just a string. */ 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' @@ -260,6 +263,14 @@ Avatar.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, + /** + * Replace the default slots. + */ + components: PropTypes.shape({ + fallback: PropTypes.elementType, + img: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The props used for each slot inside the component. * @default {} diff --git a/packages/mui-joy/src/Avatar/AvatarProps.ts b/packages/mui-joy/src/Avatar/AvatarProps.ts index cd8eef9623054b..e3ff2ae097da2b 100644 --- a/packages/mui-joy/src/Avatar/AvatarProps.ts +++ b/packages/mui-joy/src/Avatar/AvatarProps.ts @@ -10,9 +10,21 @@ export interface AvatarPropsVariantOverrides {} export interface AvatarPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, AvatarOwnerState>; - img?: SlotComponentProps<'img', { sx?: SxProps }, AvatarOwnerState>; - fallback?: SlotComponentProps<'svg', { sx?: SxProps }, AvatarOwnerState>; + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + AvatarOwnerState + >; + img?: SlotComponentProps< + 'img', + { component?: React.ElementType; sx?: SxProps }, + AvatarOwnerState + >; + fallback?: SlotComponentProps< + 'svg', + { component?: React.ElementType; sx?: SxProps }, + AvatarOwnerState + >; } export interface AvatarTypeMap

{ @@ -27,6 +39,14 @@ export interface AvatarTypeMap

{ * This can be an element, or just a string. */ children?: React.ReactNode; + /** + * Replace the default slots. + */ + components?: { + root?: React.ElementType; + img?: React.ElementType; + fallback?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} From ea89935b4070d29a7b4086fc85563efef957df18 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 16:03:45 +0000 Subject: [PATCH 026/139] [SvgIcon] Remove classes prop and apply useSlot --- packages/mui-joy/src/SvgIcon/SvgIcon.tsx | 35 +++++++++++--------- packages/mui-joy/src/SvgIcon/SvgIconProps.ts | 6 ---- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/mui-joy/src/SvgIcon/SvgIcon.tsx b/packages/mui-joy/src/SvgIcon/SvgIcon.tsx index 91eec650885309..861bc3015f3e90 100644 --- a/packages/mui-joy/src/SvgIcon/SvgIcon.tsx +++ b/packages/mui-joy/src/SvgIcon/SvgIcon.tsx @@ -6,11 +6,12 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import { getSvgIconUtilityClass } from './svgIconClasses'; import { SvgIconProps, SvgIconTypeMap, SvgIconOwnerState } from './SvgIconProps'; const useUtilityClasses = (ownerState: SvgIconOwnerState) => { - const { color, fontSize, classes } = ownerState; + const { color, fontSize } = ownerState; const slots = { root: [ @@ -20,7 +21,7 @@ const useUtilityClasses = (ownerState: SvgIconOwnerState) => { ], }; - return composeClasses(slots, getSvgIconUtilityClass, classes); + return composeClasses(slots, getSvgIconUtilityClass, {}); }; const SvgIconRoot = styled('svg', { @@ -86,22 +87,26 @@ const SvgIcon = React.forwardRef(function SvgIcon(inProps, ref) { const classes = useUtilityClasses(ownerState); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: SvgIconRoot, + externalForwardedProps: { ...other, component }, + ownerState, + additionalProps: { + color: htmlColor, + focusable: 'false', + ...(titleAccess && { role: 'img' }), + ...(!titleAccess && { 'aria-hideen': true }), + ...(!inheritViewBox && { viewBox }), + }, + }); + return ( - + {children} {titleAccess ? {titleAccess} : null} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/SvgIcon/SvgIconProps.ts b/packages/mui-joy/src/SvgIcon/SvgIconProps.ts index 9bf2f219376f94..cfbbd6f81d8172 100644 --- a/packages/mui-joy/src/SvgIcon/SvgIconProps.ts +++ b/packages/mui-joy/src/SvgIcon/SvgIconProps.ts @@ -1,12 +1,10 @@ import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; import { ColorPaletteProp, FontSize, SxProps, ApplyColorInversion } from '../styles/types'; -import { SvgIconClasses } from './svgIconClasses'; export type SvgIconSlot = 'root'; export interface SvgIconPropsSizeOverrides {} - export interface SvgIconPropsColorOverrides {} export interface SvgIconTypeMap

{ @@ -15,10 +13,6 @@ export interface SvgIconTypeMap

{ * Node passed into the SVG element. */ children?: React.ReactNode; - /** - * Override or extend the styles applied to the component. - */ - classes?: Partial; /** * The color of the component. It supports those theme colors that make sense for this component. * You can use the `htmlColor` prop to apply a color attribute to the SVG element. From 9aa71e80d3969468a03ca142ba70c5f83a79ecc4 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 16:22:53 +0000 Subject: [PATCH 027/139] [Slider] Add input class and apply useSlot --- packages/mui-joy/src/Slider/Slider.tsx | 106 +++++++++++++++---------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/packages/mui-joy/src/Slider/Slider.tsx b/packages/mui-joy/src/Slider/Slider.tsx index 8f2186461284c6..0df067f54b32c4 100644 --- a/packages/mui-joy/src/Slider/Slider.tsx +++ b/packages/mui-joy/src/Slider/Slider.tsx @@ -7,8 +7,8 @@ import { } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; import { useSlider } from '@mui/base/SliderUnstyled'; -import { useSlotProps } from '@mui/base/utils'; import { useThemeProps, styled, Theme } from '../styles'; +import useSlot from '../utils/useSlot'; import sliderClasses, { getSliderUtilityClass } from './sliderClasses'; import { SliderProps, SliderTypeMap, SliderOwnerState } from './SliderProps'; @@ -34,13 +34,14 @@ const useUtilityClasses = (ownerState: SliderOwnerState) => { ], rail: ['rail'], track: ['track'], + thumb: ['thumb', disabled && 'disabled'], + input: ['input'], mark: ['mark'], markActive: ['markActive'], markLabel: ['markLabel'], markLabelActive: ['markLabelActive'], valueLabel: ['valueLabel'], valueLabelOpen: ['valueLabelOpen'], - thumb: ['thumb', disabled && 'disabled'], active: ['active'], focusVisible: ['focusVisible'], }; @@ -399,7 +400,8 @@ const Slider = React.forwardRef(function Slider(inProps, ref) { const { 'aria-label': ariaLabel, 'aria-valuetext': ariaValuetext, - component, + className, + component = 'span', componentsProps = {}, classes: classesProp, disableSwap = false, @@ -475,76 +477,75 @@ const Slider = React.forwardRef(function Slider(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component, componentsProps }; - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), elementType: SliderRoot, + externalForwardedProps, getSlotProps: getRootProps, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, - additionalProps: { - as: component, - }, ownerState, - className: classes.root, }); - const railProps = useSlotProps({ + const [SlotRail, railProps] = useSlot('rail', { + className: classes.rail, elementType: SliderRail, - externalSlotProps: componentsProps.rail, + externalForwardedProps, ownerState, - className: classes.rail, }); - const trackProps = useSlotProps({ - elementType: SliderTrack, - externalSlotProps: componentsProps.track, + const [SlotTrack, trackProps] = useSlot('track', { additionalProps: { style: trackStyle, }, - ownerState, className: classes.track, + elementType: SliderTrack, + externalForwardedProps, + ownerState, }); - const markProps = useSlotProps({ + const [SlotMark, markProps] = useSlot('mark', { + className: classes.mark, elementType: SliderMark, - externalSlotProps: componentsProps.mark, + externalForwardedProps, ownerState, - className: classes.mark, }); - const markLabelProps = useSlotProps({ + const [SlotMarkLabel, markLabelProps] = useSlot('markLabel', { + className: classes.markLabel, elementType: SliderMarkLabel, - externalSlotProps: componentsProps.markLabel, + externalForwardedProps, ownerState, - className: classes.markLabel, }); - const thumbProps = useSlotProps({ + const [SlotThumb, thumbProps] = useSlot('thumb', { + className: classes.thumb, elementType: SliderThumb, + externalForwardedProps, getSlotProps: getThumbProps, - externalSlotProps: componentsProps.thumb, ownerState, - className: classes.thumb, }); - const inputProps = useSlotProps({ + const [SlotInput, inputProps] = useSlot('input', { + className: classes.input, elementType: SliderInput, + externalForwardedProps, getSlotProps: getHiddenInputProps, - externalSlotProps: componentsProps.input, ownerState, }); - const valueLabelProps = useSlotProps({ + const [SlotValueLabel, valueLabelProps] = useSlot('valueLabel', { + className: classes.valueLabel, elementType: SliderValueLabel, - externalSlotProps: componentsProps.valueLabel, + externalForwardedProps, ownerState, - className: classes.valueLabel, }); return ( - - - + + + {marks .filter((mark) => mark.value >= min && mark.value <= max) .map((mark, index) => { @@ -568,7 +569,7 @@ const Slider = React.forwardRef(function Slider(inProps, ref) { return ( - {mark.label != null ? ( - {mark.label} - + ) : null} ); @@ -598,7 +599,7 @@ const Slider = React.forwardRef(function Slider(inProps, ref) { const percent = valueToPercent(value, min, max); const style = axisProps[axis].offset(percent); return ( - - {valueLabelDisplay !== 'off' ? ( - + ) : null} - + ); })} - + ); }) as OverridableComponent; @@ -663,6 +664,10 @@ Slider.propTypes /* remove-proptypes */ = { * Override or extend the styles applied to the component. */ classes: PropTypes.object, + /** + * @ignore + */ + className: PropTypes.string, /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'primary' @@ -677,7 +682,20 @@ Slider.propTypes /* remove-proptypes */ = { */ component: PropTypes.elementType, /** - * The props used for each slot inside the Slider. + * Replace the default slots. + */ + components: PropTypes.shape({ + input: PropTypes.elementType, + mark: PropTypes.elementType, + markLabel: PropTypes.elementType, + rail: PropTypes.elementType, + root: PropTypes.elementType, + thumb: PropTypes.elementType, + track: PropTypes.elementType, + valueLabel: PropTypes.elementType, + }), + /** + * The props used for each slot inside the component. * @default {} */ componentsProps: PropTypes.shape({ From 6b457e2912ad2fdc1c7c51b5726b8632af716634 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 16:23:09 +0000 Subject: [PATCH 028/139] [SvgIcon] Remove classes prop from API doc --- packages/mui-joy/src/SvgIcon/SvgIcon.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/mui-joy/src/SvgIcon/SvgIcon.tsx b/packages/mui-joy/src/SvgIcon/SvgIcon.tsx index 861bc3015f3e90..530fb67479d5ce 100644 --- a/packages/mui-joy/src/SvgIcon/SvgIcon.tsx +++ b/packages/mui-joy/src/SvgIcon/SvgIcon.tsx @@ -119,10 +119,6 @@ SvgIcon.propTypes /* remove-proptypes */ = { * Node passed into the SVG element. */ children: PropTypes.node, - /** - * Override or extend the styles applied to the component. - */ - classes: PropTypes.object, /** * @ignore */ From 2e2266f123777eade84a2d7d95d7e58c9dd0ffbb Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 1 Nov 2022 16:23:20 +0000 Subject: [PATCH 029/139] [Slider] Add components prop --- packages/mui-joy/src/Slider/SliderProps.ts | 71 ++++++++++++++++++---- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/packages/mui-joy/src/Slider/SliderProps.ts b/packages/mui-joy/src/Slider/SliderProps.ts index 0a2de173acdb49..4a9b6e8a8e5220 100644 --- a/packages/mui-joy/src/Slider/SliderProps.ts +++ b/packages/mui-joy/src/Slider/SliderProps.ts @@ -15,26 +15,71 @@ export type SliderSlot = | 'input'; export interface SliderPropsVariantOverrides {} - export interface SliderPropsColorOverrides {} - export interface SliderPropsSizeOverrides {} +interface ComponentsProps { + root?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; + track?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; + rail?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; + thumb?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; + mark?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; + markLabel?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; + valueLabel?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; + input?: SlotComponentProps< + 'input', + { component?: React.ElementType; sx?: SxProps }, + SliderOwnerState + >; +} + export interface SliderOwnProps { /** - * The props used for each slot inside the Slider. - * @default {} + * Replace the default slots. */ - componentsProps?: { - root?: SlotComponentProps<'span', { sx?: SxProps }, SliderOwnerState>; - track?: SlotComponentProps<'span', { sx?: SxProps }, SliderOwnerState>; - rail?: SlotComponentProps<'span', { sx?: SxProps }, SliderOwnerState>; - thumb?: SlotComponentProps<'span', { sx?: SxProps }, SliderOwnerState>; - mark?: SlotComponentProps<'span', { sx?: SxProps }, SliderOwnerState>; - markLabel?: SlotComponentProps<'span', { sx?: SxProps }, SliderOwnerState>; - valueLabel?: SlotComponentProps<'span', { sx?: SxProps }, SliderOwnerState>; - input?: SlotComponentProps<'input', { sx?: SxProps }, SliderOwnerState>; + components?: { + root?: React.ElementType; + track?: React.ElementType; + rail?: React.ElementType; + thumb?: React.ElementType; + mark?: React.ElementType; + markLabel?: React.ElementType; + valueLabel?: React.ElementType; + input?: React.ElementType; }; + /** + * The props used for each slot inside the component. + * @default {} + */ + componentsProps?: ComponentsProps; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'primary' From ae7c289442accff805a49269f670bc27acd38479 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Wed, 2 Nov 2022 11:03:20 +0000 Subject: [PATCH 030/139] Rename componentsProps to slotProps and components to slots --- packages/mui-joy/src/utils/useSlot.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/mui-joy/src/utils/useSlot.ts b/packages/mui-joy/src/utils/useSlot.ts index 407e733ea7a711..fdf21c55ffadbc 100644 --- a/packages/mui-joy/src/utils/useSlot.ts +++ b/packages/mui-joy/src/utils/useSlot.ts @@ -35,8 +35,8 @@ export default function useSlot< ExternalSlotProps extends { component?: React.ElementType }, ExternalForwardedProps extends { component?: React.ElementType; - components?: { [k in T]?: React.ElementType }; - componentsProps?: { + slots?: { [k in T]?: React.ElementType }; + slotProps?: { [k in T]?: | WithCommonProps | ((ownerState: OwnerState) => WithCommonProps); @@ -66,7 +66,7 @@ export default function useSlot< */ ownerState: OwnerState; /** - * The `other` props from the consumer. It has to contain `component`, `components`, and `componentsProps`. + * The `other` props from the consumer. It has to contain `component`, `slots`, and `slotProps`. * The function will use those props to calculate the final leaf component and the returned props. * * If the slot is not `root`, the rest of the `externalForwardedProps` are neglected. @@ -80,13 +80,13 @@ export default function useSlot< * For overriding the component's ownerState for the slot. * This is required for some components that need styling via `ownerState`. * - * It is a function because `componentsProps.{slot}` can be a function which has to be resolved first. + * It is a function because `slotProps.{slot}` can be a function which has to be resolved first. */ getSlotOwnerState?: ( mergedProps: SlotProps & ExternalSlotProps & ExtractComponentProps< - Exclude[T], undefined> + Exclude[T], undefined> >, ) => SlotOwnerState; /** @@ -107,14 +107,14 @@ export default function useSlot< } = parameters; const { component: rootComponent, - components = { [name]: undefined }, - componentsProps = { [name]: undefined }, + slots = { [name]: undefined }, + slotProps = { [name]: undefined }, ...other } = externalForwardedProps; - // `componentsProps[name]` can be a callback that receives the component's `ownerState`. + // `slotProps[name]` can be a callback that receives the component's ownerState. // `resolvedComponentsProps` is always a plain object. - const resolvedComponentsProps = resolveComponentProps(componentsProps[name], ownerState); + const resolvedComponentsProps = resolveComponentProps(slotProps[name], ownerState); const { props: { component: slotComponent, ...mergedProps }, @@ -143,8 +143,8 @@ export default function useSlot< const props = appendOwnerState( elementType, { - ...(name === 'root' && !rootComponent && !components[name] && internalForwardedProps), - ...(name !== 'root' && !components[name] && internalForwardedProps), + ...(name === 'root' && !rootComponent && !slots[name] && internalForwardedProps), + ...(name !== 'root' && !slots[name] && internalForwardedProps), ...(mergedProps as { className: string } & SlotProps & ExternalSlotProps & AdditionalProps & @@ -157,5 +157,5 @@ export default function useSlot< finalOwnerState as OwnerState & SlotOwnerState, ); - return [components[name] || elementType, props] as [ElementType, typeof props]; + return [slots[name] || elementType, props] as [ElementType, typeof props]; } From 4387e71e8b846fd4c2957f44ccdba2e26f0cf718 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Wed, 2 Nov 2022 11:03:49 +0000 Subject: [PATCH 031/139] Apply useSlot to more components & Rename components to slots, componentsProps to slotProps --- packages/mui-joy/src/Alert/Alert.tsx | 65 +++++++++---- packages/mui-joy/src/Alert/AlertProps.ts | 34 ++++++- .../mui-joy/src/AspectRatio/AspectRatio.tsx | 56 ++++++------ .../src/AspectRatio/AspectRatioProps.ts | 21 ++++- packages/mui-joy/src/Avatar/Avatar.tsx | 44 ++++----- packages/mui-joy/src/Avatar/AvatarProps.ts | 4 +- .../mui-joy/src/AvatarGroup/AvatarGroup.tsx | 23 ++--- packages/mui-joy/src/Badge/Badge.tsx | 51 ++++++----- packages/mui-joy/src/Badge/BadgeProps.ts | 21 ++++- .../mui-joy/src/Breadcrumbs/Breadcrumbs.tsx | 91 +++++++++---------- .../src/Breadcrumbs/BreadcrumbsProps.ts | 35 ++++++- packages/mui-joy/src/Button/Button.tsx | 89 ++++++++++-------- packages/mui-joy/src/Button/ButtonProps.ts | 39 ++++++-- packages/mui-joy/src/Card/Card.tsx | 21 +++-- .../mui-joy/src/CardContent/CardContent.tsx | 23 +++-- packages/mui-joy/src/CardCover/CardCover.tsx | 21 +++-- .../mui-joy/src/CardOverflow/CardOverflow.tsx | 23 +++-- packages/mui-joy/src/Chip/Chip.tsx | 56 ++++++------ packages/mui-joy/src/Chip/ChipProps.ts | 4 +- .../src/CircularProgress/CircularProgress.tsx | 38 ++++---- .../CircularProgress/CircularProgressProps.ts | 4 +- packages/mui-joy/src/Slider/Slider.tsx | 58 ++++++------ packages/mui-joy/src/Slider/SliderProps.ts | 4 +- packages/mui-joy/src/Tooltip/Tooltip.tsx | 30 +++--- packages/mui-joy/src/Tooltip/TooltipProps.ts | 4 +- 25 files changed, 510 insertions(+), 349 deletions(-) diff --git a/packages/mui-joy/src/Alert/Alert.tsx b/packages/mui-joy/src/Alert/Alert.tsx index e1de8f3f7a7f6a..3fe754fb9364a3 100644 --- a/packages/mui-joy/src/Alert/Alert.tsx +++ b/packages/mui-joy/src/Alert/Alert.tsx @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import { getAlertUtilityClass } from './alertClasses'; import { AlertProps, AlertTypeMap } from './AlertProps'; @@ -121,29 +122,42 @@ const Alert = React.forwardRef(function Alert(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; + + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: AlertRoot, + externalForwardedProps, + ownerState, + additionalProps: { + role, + }, + }); + + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, + elementType: AlertStartDecorator, + externalForwardedProps, + ownerState, + }); + + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { + className: classes.startDecorator, + elementType: AlertEndDecorator, + externalForwardedProps, + ownerState, + }); return ( - + {startDecorator && ( - - {startDecorator} - + {startDecorator} )} {children} - {endDecorator && ( - - {endDecorator} - - )} - + {endDecorator && {endDecorator}} + ); }) as OverridableComponent; @@ -190,6 +204,23 @@ Alert.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + endDecorator: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), /** * Element placed before the children. */ diff --git a/packages/mui-joy/src/Alert/AlertProps.ts b/packages/mui-joy/src/Alert/AlertProps.ts index 8d4b36ba3ebc26..40ff44119f8862 100644 --- a/packages/mui-joy/src/Alert/AlertProps.ts +++ b/packages/mui-joy/src/Alert/AlertProps.ts @@ -1,5 +1,6 @@ -import { OverridableStringUnion, OverrideProps } from '@mui/types'; import * as React from 'react'; +import { OverridableStringUnion, OverrideProps } from '@mui/types'; +import { SlotComponentProps } from '@mui/base/utils'; import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; export type AlertSlot = 'root' | 'startDecorator' | 'endDecorator'; @@ -8,6 +9,24 @@ export interface AlertPropsVariantOverrides {} export interface AlertPropsColorOverrides {} export interface AlertPropsSizeOverrides {} +interface ComponentsProps { + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + AlertOwnerState + >; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + AlertOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + AlertOwnerState + >; +} + export interface AlertTypeMap

{ props: P & { /** @@ -15,6 +34,19 @@ export interface AlertTypeMap

{ * @default 'primary' */ color?: OverridableStringUnion; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + }; + /** + * The props used for each slot inside. + * @default {} + */ + slotProps?: ComponentsProps; /** * Element placed after the children. */ diff --git a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx index f5bf1c50977e31..4e9dfeac06a977 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatio.tsx +++ b/packages/mui-joy/src/AspectRatio/AspectRatio.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; -import { useThemeProps } from '../styles'; +import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getAspectRatioUtilityClass } from './aspectRatioClasses'; import { AspectRatioProps, AspectRatioOwnerState, AspectRatioTypeMap } from './AspectRatioProps'; @@ -91,7 +91,6 @@ const AspectRatio = React.forwardRef(function AspectRatio(inProps, ref) { const { component = 'div', children, - componentsProps = {}, ratio = '16 / 9', minHeight, maxHeight, @@ -114,35 +113,33 @@ const AspectRatio = React.forwardRef(function AspectRatio(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ + const externalForwardedProps = { ...other, component }; + + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: AspectRatioRoot, + externalForwardedProps, ownerState, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, - additionalProps: { - ref, - as: component, - }, - className: classes.root, }); - const contentProps = useSlotProps({ + const [SlotContent, contentProps] = useSlot('content', { + className: classes.content, elementType: AspectRatioContent, + externalForwardedProps, ownerState, - externalSlotProps: componentsProps.content, - className: classes.content, }); return ( - - + + {React.Children.map(children, (child, index) => index === 0 && React.isValidElement(child) ? React.cloneElement(child, { 'data-first-child': '' } as Record) : child, )} - - + + ); }) as OverridableComponent; @@ -166,14 +163,6 @@ AspectRatio.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - content: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * The maximum calculated height of the element (not the CSS height). */ @@ -204,6 +193,21 @@ AspectRatio.propTypes /* remove-proptypes */ = { * @default '16 / 9' */ ratio: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + content: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + content: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts b/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts index a028cf35ea459c..27dc5247ffe4f1 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts +++ b/packages/mui-joy/src/AspectRatio/AspectRatioProps.ts @@ -9,8 +9,16 @@ export interface AspectRatioPropsColorOverrides {} export interface AspectRatioPropsVariantOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, AspectRatioOwnerState>; - content?: SlotComponentProps<'div', { sx?: SxProps }, AspectRatioOwnerState>; + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + AspectRatioOwnerState + >; + content?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + AspectRatioOwnerState + >; } export interface AspectRatioTypeMap

{ @@ -25,11 +33,18 @@ export interface AspectRatioTypeMap

* This can be an element, or just a string. */ children?: React.ReactNode; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + content?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The minimum calculated height of the element (not the CSS height). */ diff --git a/packages/mui-joy/src/Avatar/Avatar.tsx b/packages/mui-joy/src/Avatar/Avatar.tsx index ceaa83481ca952..ec86b7673b73f2 100644 --- a/packages/mui-joy/src/Avatar/Avatar.tsx +++ b/packages/mui-joy/src/Avatar/Avatar.tsx @@ -149,7 +149,7 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) { alt, color: colorProp = 'neutral', component = 'div', - componentsProps = {}, + slotProps = {}, className, size: sizeProp = 'md', variant: variantProp = 'soft', @@ -177,9 +177,9 @@ 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, - ...(typeof componentsProps.img === 'function' - ? componentsProps.img(ownerState) - : componentsProps.img), + ...(typeof slotProps.img === 'function' + ? slotProps.img(ownerState) + : slotProps.img), src, srcSet, }); @@ -188,7 +188,7 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) { const hasImgNotFailing = hasImg && loaded !== 'error'; const classes = useUtilityClasses(ownerState); - const externalForwardedProps = { ...other, component, componentsProps }; + const externalForwardedProps = { ...other, component, slotProps }; const [SlotRoot, rootProps] = useSlot('root', { ref, @@ -263,23 +263,6 @@ Avatar.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * Replace the default slots. - */ - components: PropTypes.shape({ - fallback: PropTypes.elementType, - img: PropTypes.elementType, - root: PropTypes.elementType, - }), - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - fallback: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - img: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * [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. @@ -294,6 +277,23 @@ Avatar.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['lg', 'md', 'sm']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + fallback: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + img: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + fallback: PropTypes.elementType, + img: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The `src` attribute for the `img` element. */ diff --git a/packages/mui-joy/src/Avatar/AvatarProps.ts b/packages/mui-joy/src/Avatar/AvatarProps.ts index e3ff2ae097da2b..bab66b6043d9dc 100644 --- a/packages/mui-joy/src/Avatar/AvatarProps.ts +++ b/packages/mui-joy/src/Avatar/AvatarProps.ts @@ -42,7 +42,7 @@ export interface AvatarTypeMap

{ /** * Replace the default slots. */ - components?: { + slots?: { root?: React.ElementType; img?: React.ElementType; fallback?: React.ElementType; @@ -51,7 +51,7 @@ export interface AvatarTypeMap

{ * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' diff --git a/packages/mui-joy/src/AvatarGroup/AvatarGroup.tsx b/packages/mui-joy/src/AvatarGroup/AvatarGroup.tsx index 15905d00e943c1..db72ee1f52e9ca 100644 --- a/packages/mui-joy/src/AvatarGroup/AvatarGroup.tsx +++ b/packages/mui-joy/src/AvatarGroup/AvatarGroup.tsx @@ -3,7 +3,8 @@ import clsx from 'clsx'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; -import { useThemeProps } from '../styles'; +import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getAvatarGroupUtilityClass } from './avatarGroupClasses'; import { AvatarGroupProps, AvatarGroupOwnerState, AvatarGroupTypeMap } from './AvatarGroupProps'; @@ -18,7 +19,7 @@ const useUtilityClasses = () => { return composeClasses(slots, getAvatarGroupUtilityClass, {}); }; -const AvatarGroupGroupRoot = styled('div', { +const AvatarGroupRoot = styled('div', { name: 'JoyAvatarGroup', slot: 'Root', overridesResolver: (props, styles) => styles.root, @@ -62,17 +63,17 @@ const AvatarGroup = React.forwardRef(function AvatarGroup(inProps, ref) { const classes = useUtilityClasses(); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: AvatarGroupRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + return ( - - {children} - + {children} ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/Badge/Badge.tsx b/packages/mui-joy/src/Badge/Badge.tsx index 76312db102b56b..326830c9c43d24 100644 --- a/packages/mui-joy/src/Badge/Badge.tsx +++ b/packages/mui-joy/src/Badge/Badge.tsx @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize, usePreviousProps } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import badgeClasses, { getBadgeUtilityClass } from './badgeClasses'; import { BadgeProps, BadgeOwnerState, BadgeTypeMap } from './BadgeProps'; @@ -147,7 +147,6 @@ const Badge = React.forwardRef(function Badge(inProps, ref) { badgeInset: badgeInsetProp = 0, children, component = 'span', - componentsProps = {}, size: sizeProp = 'md', color: colorProp = 'primary', invisible: invisibleProp = false, @@ -191,31 +190,28 @@ const Badge = React.forwardRef(function Badge(inProps, ref) { if (invisible && badgeContentProp === 0) { displayValue = ''; } + const externalForwardedProps = { ...other, component }; - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: BadgeRoot, + externalForwardedProps, ownerState, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, - additionalProps: { - ref, - as: component, - }, - className: classes.root, }); - const badgeProps = useSlotProps({ + const [SlotBadge, badgeProps] = useSlot('badge', { + className: classes.badge, elementType: BadgeBadge, + externalForwardedProps, ownerState, - externalSlotProps: componentsProps.badge, - className: classes.badge, }); return ( - + {children} - {displayValue} - + {displayValue} + ); }) as OverridableComponent; @@ -261,14 +257,6 @@ Badge.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - badge: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * If `true`, the badge is invisible. * @default false @@ -292,6 +280,21 @@ Badge.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + badge: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + badge: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-joy/src/Badge/BadgeProps.ts b/packages/mui-joy/src/Badge/BadgeProps.ts index 177e64f8b25825..93bc902cfd7546 100644 --- a/packages/mui-joy/src/Badge/BadgeProps.ts +++ b/packages/mui-joy/src/Badge/BadgeProps.ts @@ -17,8 +17,16 @@ export interface BadgeOrigin { } interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, BadgeOwnerState>; - badge?: SlotComponentProps<'div', { sx?: SxProps }, BadgeOwnerState>; + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + BadgeOwnerState + >; + badge?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + BadgeOwnerState + >; } export interface BadgeTypeMap { @@ -44,11 +52,18 @@ export interface BadgeTypeMap { * The badge will be added relative to this node. */ children?: React.ReactNode; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + badge?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'primary' diff --git a/packages/mui-joy/src/Breadcrumbs/Breadcrumbs.tsx b/packages/mui-joy/src/Breadcrumbs/Breadcrumbs.tsx index 886001804675f8..613d28c1112c83 100644 --- a/packages/mui-joy/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/mui-joy/src/Breadcrumbs/Breadcrumbs.tsx @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import clsx from 'clsx'; import { useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getBreadcrumbsUtilityClass } from './breadcrumbsClasses'; import { BreadcrumbsProps, BreadcrumbsOwnerState, BreadcrumbsTypeMap } from './BreadcrumbsProps'; @@ -82,15 +82,7 @@ const Breadcrumbs = React.forwardRef(function Breadcrumbs(inProps, ref) { name: 'JoyBreadcrumbs', }); - const { - children, - className, - component = 'nav', - componentsProps = {}, - size = 'md', - separator = '/', - ...other - } = props; + const { children, className, component = 'nav', size = 'md', separator = '/', ...other } = props; const ownerState = { ...props, @@ -101,40 +93,38 @@ const Breadcrumbs = React.forwardRef(function Breadcrumbs(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ + const externalForwardedProps = { ...other, component }; + + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), elementType: BreadcrumbsRoot, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, + externalForwardedProps, ownerState, - additionalProps: { - ref, - as: component, - }, - className: clsx(classes.root, className), }); - const olProps = useSlotProps({ + const [SlotOl, olProps] = useSlot('ol', { + className: classes.ol, elementType: BreadcrumbsOl, - externalSlotProps: componentsProps.ol, + externalForwardedProps, ownerState, - className: classes.ol, }); - const liProps = useSlotProps({ + const [SlotLi, liProps] = useSlot('li', { + className: classes.li, elementType: BreadcrumbsLi, - externalSlotProps: componentsProps.li, + externalForwardedProps, ownerState, - className: classes.li, }); - const separatorProps = useSlotProps({ - elementType: BreadcrumbsSeparator, - externalSlotProps: componentsProps.separator, - ownerState, + const [SlotSeparator, separatorProps] = useSlot('separator', { additionalProps: { 'aria-hidden': true, }, className: classes.separator, + elementType: BreadcrumbsSeparator, + externalForwardedProps, + ownerState, }); const allItems = React.Children.toArray(children) @@ -142,29 +132,29 @@ const Breadcrumbs = React.forwardRef(function Breadcrumbs(inProps, ref) { return React.isValidElement(child); }) .map((child, index) => ( - + {child} - + )); return ( - - + + {allItems.reduce((acc: React.ReactNode[], current: React.ReactNode, index: number) => { if (index < allItems.length - 1) { acc = acc.concat( current, - + {separator} - , + , ); } else { acc.push(current); } return acc; }, [])} - - + + ); }) as OverridableComponent; @@ -186,16 +176,6 @@ Breadcrumbs.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - li: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - ol: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - separator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * Custom separator node. * @default '/' @@ -210,6 +190,25 @@ Breadcrumbs.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + li: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + ol: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + separator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + li: PropTypes.elementType, + ol: PropTypes.elementType, + root: PropTypes.elementType, + separator: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-joy/src/Breadcrumbs/BreadcrumbsProps.ts b/packages/mui-joy/src/Breadcrumbs/BreadcrumbsProps.ts index a1ac7e05081521..415bcfffc54ace 100644 --- a/packages/mui-joy/src/Breadcrumbs/BreadcrumbsProps.ts +++ b/packages/mui-joy/src/Breadcrumbs/BreadcrumbsProps.ts @@ -8,10 +8,26 @@ export type BreadcrumbsSlot = 'root' | 'ol' | 'li' | 'separator'; export interface BreadcrumbsPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'nav', { sx?: SxProps }, BreadcrumbsOwnerState>; - ol?: SlotComponentProps<'ol', { sx?: SxProps }, BreadcrumbsOwnerState>; - li?: SlotComponentProps<'li', { sx?: SxProps }, BreadcrumbsOwnerState>; - separator?: SlotComponentProps<'li', { sx?: SxProps }, BreadcrumbsOwnerState>; + root?: SlotComponentProps< + 'nav', + { component?: React.ElementType; sx?: SxProps }, + BreadcrumbsOwnerState + >; + ol?: SlotComponentProps< + 'ol', + { component?: React.ElementType; sx?: SxProps }, + BreadcrumbsOwnerState + >; + li?: SlotComponentProps< + 'li', + { component?: React.ElementType; sx?: SxProps }, + BreadcrumbsOwnerState + >; + separator?: SlotComponentProps< + 'li', + { component?: React.ElementType; sx?: SxProps }, + BreadcrumbsOwnerState + >; } export interface BreadcrumbsTypeMap

{ @@ -20,11 +36,20 @@ export interface BreadcrumbsTypeMap

* The content of the component. */ children?: React.ReactNode; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + ol?: React.ElementType; + li?: React.ElementType; + separator?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * Custom separator node. * @default '/' diff --git a/packages/mui-joy/src/Button/Button.tsx b/packages/mui-joy/src/Button/Button.tsx index 362c4ab86195e4..9b641beb1957ba 100644 --- a/packages/mui-joy/src/Button/Button.tsx +++ b/packages/mui-joy/src/Button/Button.tsx @@ -2,10 +2,10 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useButton } from '@mui/base/ButtonUnstyled'; import composeClasses from '@mui/base/composeClasses'; -import { useSlotProps } from '@mui/base/utils'; import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } from '@mui/utils'; import { styled, useThemeProps } from '../styles'; import { useColorInversion } from '../styles/ColorInversion'; +import useSlot from '../utils/useSlot'; import CircularProgress from '../CircularProgress'; import buttonClasses, { getButtonUtilityClass } from './buttonClasses'; import { ButtonOwnerState, ButtonTypeMap, ExtendButton } from './ButtonProps'; @@ -166,7 +166,6 @@ const Button = React.forwardRef(function Button(inProps, ref) { children, action, component = 'button', - componentsProps = {}, color: colorProp = 'primary', variant = 'solid', size = 'md', @@ -220,61 +219,62 @@ const Button = React.forwardRef(function Button(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: ButtonRoot, - externalSlotProps: componentsProps.root, - ownerState, + externalForwardedProps, getSlotProps: getRootProps, - externalForwardedProps: other, - additionalProps: { - as: component, - }, - className: classes.root, + ownerState, }); - const startDecoratorProps = useSlotProps({ + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, elementType: ButtonStartDecorator, - externalSlotProps: componentsProps.startDecorator, + externalForwardedProps, ownerState, - className: classes.startDecorator, }); - const endDecoratorProps = useSlotProps({ + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { + className: classes.endDecorator, elementType: ButtonEndDecorator, - externalSlotProps: componentsProps.endDecorator, + externalForwardedProps, ownerState, - className: classes.endDecorator, }); - const loadingIndicatorCenterProps = useSlotProps({ - elementType: ButtonLoadingCenter, - externalSlotProps: componentsProps.loadingIndicatorCenter, - ownerState, - className: classes.loadingIndicatorCenter, - }); + const [SlotLoadingIndicatorCenter, loadingIndicatorCenterProps] = useSlot( + 'loadingIndicatorCenter', + { + className: classes.loadingIndicatorCenter, + elementType: ButtonLoadingCenter, + externalForwardedProps, + ownerState, + }, + ); return ( - + {(startDecorator || (loading && loadingPosition === 'start')) && ( - + {loading && loadingPosition === 'start' ? loadingIndicator : startDecorator} - + )} {children} {loading && loadingPosition === 'center' && ( - + {loadingIndicator} - + )} {(endDecorator || (loading && loadingPosition === 'end')) && ( - + {loading && loadingPosition === 'end' ? loadingIndicator : endDecorator} - + )} - + ); }) as ExtendButton; @@ -311,16 +311,6 @@ Button.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - loadingIndicatorCenter: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * If `true`, the component is disabled. * @default false @@ -362,6 +352,25 @@ Button.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + loadingIndicatorCenter: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + endDecorator: PropTypes.elementType, + loadingIndicatorCenter: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), /** * Element placed before the children. */ diff --git a/packages/mui-joy/src/Button/ButtonProps.ts b/packages/mui-joy/src/Button/ButtonProps.ts index c3f34429820c10..1e61890619f276 100644 --- a/packages/mui-joy/src/Button/ButtonProps.ts +++ b/packages/mui-joy/src/Button/ButtonProps.ts @@ -8,19 +8,33 @@ import { import { SlotComponentProps } from '@mui/base/utils'; import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; -export type ButtonSlot = 'root' | 'startDecorator' | 'endDecorator'; +export type ButtonSlot = 'root' | 'startDecorator' | 'endDecorator' | 'loadingIndicatorCenter'; export interface ButtonPropsVariantOverrides {} - export interface ButtonPropsColorOverrides {} - export interface ButtonPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'button', { sx?: SxProps }, ButtonOwnerState>; - startDecorator?: SlotComponentProps<'span', { sx?: SxProps }, ButtonOwnerState>; - endDecorator?: SlotComponentProps<'span', { sx?: SxProps }, ButtonOwnerState>; - loadingIndicatorCenter?: SlotComponentProps<'span', { sx?: SxProps }, ButtonOwnerState>; + root?: SlotComponentProps< + 'button', + { component?: React.ElementType; sx?: SxProps }, + ButtonOwnerState + >; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + ButtonOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + ButtonOwnerState + >; + loadingIndicatorCenter?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + ButtonOwnerState + >; } export interface ButtonTypeMap

{ @@ -36,11 +50,20 @@ export interface ButtonTypeMap

{ * @default 'primary' */ color?: OverridableStringUnion; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + loadingIndicatorCenter?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * If `true`, the component is disabled. * @default false diff --git a/packages/mui-joy/src/Card/Card.tsx b/packages/mui-joy/src/Card/Card.tsx index be43a502024ab0..1fa245d6df8606 100644 --- a/packages/mui-joy/src/Card/Card.tsx +++ b/packages/mui-joy/src/Card/Card.tsx @@ -7,7 +7,8 @@ import { unstable_capitalize as capitalize, unstable_isMuiElement as isMuiElement, } from '@mui/utils'; -import { useThemeProps } from '../styles'; +import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getCardUtilityClass } from './cardClasses'; import { CardProps, CardTypeMap } from './CardProps'; @@ -112,15 +113,17 @@ const Card = React.forwardRef(function Card(inProps, ref) { const classes = useUtilityClasses(ownerState); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: CardRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + return ( - + {React.Children.map(children, (child, index) => { if (!React.isValidElement(child)) { return child; @@ -141,7 +144,7 @@ const Card = React.forwardRef(function Card(inProps, ref) { } return React.cloneElement(child, extraProps); })} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/CardContent/CardContent.tsx b/packages/mui-joy/src/CardContent/CardContent.tsx index 42ad54bbeb186d..7c67f3bf584111 100644 --- a/packages/mui-joy/src/CardContent/CardContent.tsx +++ b/packages/mui-joy/src/CardContent/CardContent.tsx @@ -3,7 +3,8 @@ import clsx from 'clsx'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; -import { useThemeProps } from '../styles'; +import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getCardContentUtilityClass } from './cardContentClasses'; import { CardContentProps, CardContentTypeMap } from './CardContentProps'; @@ -42,17 +43,15 @@ const CardContent = React.forwardRef(function CardContent(inProps, ref) { const classes = useUtilityClasses(); - return ( - - {children} - - ); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: CardContentRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return {children}; }) as OverridableComponent; CardContent.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/CardCover/CardCover.tsx b/packages/mui-joy/src/CardCover/CardCover.tsx index 71f1e2be14e877..eba7c6b8b0f6f8 100644 --- a/packages/mui-joy/src/CardCover/CardCover.tsx +++ b/packages/mui-joy/src/CardCover/CardCover.tsx @@ -3,7 +3,8 @@ import clsx from 'clsx'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; -import { useThemeProps } from '../styles'; +import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getCardCoverUtilityClass } from './cardCoverClasses'; import { CardCoverProps, CardCoverTypeMap } from './CardCoverProps'; @@ -65,20 +66,22 @@ const CardCover = React.forwardRef(function CardCover(inProps, ref) { const classes = useUtilityClasses(); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: CardCoverRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + return ( - + {React.Children.map(children, (child, index) => index === 0 && React.isValidElement(child) ? React.cloneElement(child, { 'data-first-child': '' } as Record) : child, )} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx index a60fced418e944..bac843720588b6 100644 --- a/packages/mui-joy/src/CardOverflow/CardOverflow.tsx +++ b/packages/mui-joy/src/CardOverflow/CardOverflow.tsx @@ -4,7 +4,8 @@ import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; -import { useThemeProps } from '../styles'; +import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getCardOverflowUtilityClass } from './cardOverflowClasses'; import { CardOverflowProps, CardOverflowTypeMap } from './CardOverflowProps'; @@ -110,17 +111,15 @@ const CardOverflow = React.forwardRef(function CardOverflow(inProps, ref) { const classes = useUtilityClasses(ownerState); - return ( - - {children} - - ); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: CardOverflowRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return {children}; }) as OverridableComponent; CardOverflow.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index ea154eeaed94d6..2ae55d727866b6 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -203,7 +203,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { className, color = 'primary', component = 'div', - componentsProps = {}, + slotProps = {}, onClick, disabled = false, size = 'md', @@ -213,7 +213,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { ...other } = props; - const clickable = !!onClick || !!componentsProps.action; + const clickable = !!onClick || !!slotProps.action; const ownerState: ChipOwnerState = { ...props, disabled, @@ -226,9 +226,9 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { }; const resolvedActionProps = - typeof componentsProps.action === 'function' - ? componentsProps.action(ownerState) - : componentsProps.action; + typeof slotProps.action === 'function' + ? slotProps.action(ownerState) + : slotProps.action; const actionRef = React.useRef(null); const { focusVisible, getRootProps } = useButton({ ...resolvedActionProps, @@ -239,7 +239,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { ownerState.focusVisible = focusVisible; const classes = useUtilityClasses(ownerState); - const externalForwardedProps = { ...other, component, componentsProps }; + const externalForwardedProps = { ...other, component, slotProps }; const [SlotRoot, rootProps] = useSlot('root', { ref, @@ -282,7 +282,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { const [SlotEndDecorator, endDecoratorProps] = useSlot('startDecorator', { className: classes.startDecorator, elementType: ChipEndDecorator, - externalForwardedProps: { ...other, componentsProps }, + externalForwardedProps: { ...other, slotProps }, ownerState, }); @@ -336,27 +336,6 @@ Chip.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * Replace the default slots. - */ - components: PropTypes.shape({ - action: PropTypes.elementType, - endDecorator: PropTypes.elementType, - label: PropTypes.elementType, - root: PropTypes.elementType, - startDecorator: PropTypes.elementType, - }), - /** - * The props used for each slot inside. - * @default {} - */ - componentsProps: PropTypes.shape({ - action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * If `true`, the component is disabled. * @default false @@ -379,6 +358,27 @@ Chip.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['lg', 'md', 'sm']), PropTypes.string, ]), + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + action: PropTypes.elementType, + endDecorator: PropTypes.elementType, + label: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), /** * Element placed before the children. */ diff --git a/packages/mui-joy/src/Chip/ChipProps.ts b/packages/mui-joy/src/Chip/ChipProps.ts index 62ef1100606375..9d43f45187a7d0 100644 --- a/packages/mui-joy/src/Chip/ChipProps.ts +++ b/packages/mui-joy/src/Chip/ChipProps.ts @@ -47,7 +47,7 @@ export interface ChipTypeMap

{ /** * Replace the default slots. */ - components?: { + slots?: { root?: React.ElementType; label?: React.ElementType; action?: React.ElementType; @@ -58,7 +58,7 @@ export interface ChipTypeMap

{ * The props used for each slot inside. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'primary' diff --git a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx index 2a23b3f864ddd3..2ace2741e2c001 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgress.tsx +++ b/packages/mui-joy/src/CircularProgress/CircularProgress.tsx @@ -311,25 +311,6 @@ CircularProgress.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * Replace the default slots. - */ - components: PropTypes.shape({ - progress: PropTypes.elementType, - root: PropTypes.elementType, - svg: PropTypes.elementType, - track: PropTypes.elementType, - }), - /** - * The props used for each slot inside. - * @default {} - */ - componentsProps: PropTypes.shape({ - progress: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - svg: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - track: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * The boolean to select a variant. * Use indeterminate when there is no progress value. @@ -345,6 +326,25 @@ CircularProgress.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + progress: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + svg: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + track: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + progress: PropTypes.elementType, + root: PropTypes.elementType, + svg: PropTypes.elementType, + track: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts index 934de9a771d15b..9d4d7c85b92b88 100644 --- a/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts +++ b/packages/mui-joy/src/CircularProgress/CircularProgressProps.ts @@ -42,7 +42,7 @@ export interface CircularProgressTypeMap

{ /** * Replace the default slots. */ - components?: { + slots?: { root?: React.ElementType; arrow?: React.ElementType; }; @@ -50,7 +50,7 @@ export interface TooltipTypeMap

{ * The props used for each slot inside. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * Set to `true` if the `title` acts as an accessible description. * By default the `title` acts as an accessible label for the child. From 0005956166af833a5dbd59b5ce22b185c1b9c646 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Wed, 2 Nov 2022 11:21:24 +0000 Subject: [PATCH 032/139] Fix Joy useSlot() test --- packages/mui-joy/src/utils/useSlot.test.tsx | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/mui-joy/src/utils/useSlot.test.tsx b/packages/mui-joy/src/utils/useSlot.test.tsx index e65cd1a9c599a3..055bc7d8c18fe6 100644 --- a/packages/mui-joy/src/utils/useSlot.test.tsx +++ b/packages/mui-joy/src/utils/useSlot.test.tsx @@ -45,8 +45,8 @@ describe('useSlot', () => { className?: string; component?: React.ElementType; href?: string; - components?: { root?: React.ElementType; decorator?: React.ElementType }; - componentsProps?: { + slots?: { root?: React.ElementType; decorator?: React.ElementType }; + slotProps?: { root?: SlotComponentProps<'button', Record, {}>; decorator?: SlotComponentProps<'span', { size?: 'sm' | 'md' } & Record, {}>; }; @@ -92,7 +92,7 @@ describe('useSlot', () => { it('should append classes', () => { const { getByRole } = render( - , + , ); expect(getByRole('button')).to.have.class('root'); expect(getByRole('button')).to.have.class('foo-bar'); @@ -106,13 +106,13 @@ describe('useSlot', () => { }); it('slot ownerstate should be overriable', () => { - const { getByRole } = render(); + const { getByRole } = render(); expect(getByRole('button').firstChild).to.have.class('size-sm'); }); - it('componentsProps has higher priority', () => { + it('slotProps has higher priority', () => { const { getByRole } = render( - , + , ); expect(getByRole('button')).to.have.attribute('data-item', 'bar'); }); @@ -122,15 +122,15 @@ describe('useSlot', () => { expect(getByRole('link')).toBeVisible(); }); - it('use componentsProps `component` over `component` prop', () => { + it('use slotProps `component` over `component` prop', () => { const { getByRole } = render( - , + , ); expect(getByRole('link')).toBeVisible(); }); it('can change decorator leaf component', () => { - const { getByRole } = render(); + const { getByRole } = render(); expect(getByRole('button').firstChild).to.have.tagName('div'); }); }); @@ -144,12 +144,12 @@ describe('useSlot', () => { function Item(props: { component?: React.ElementType; - components?: { + slots?: { root?: React.ElementType; listbox?: React.ElementType; option?: React.ElementType; }; - componentsProps?: { + slotProps?: { root?: SlotComponentProps<'button', Record, {}>; listbox?: SlotComponentProps<'span', Record, {}>; option?: SlotComponentProps<'div', Record, {}>; @@ -208,19 +208,19 @@ describe('useSlot', () => { function Listbox({ component }: { component?: React.ElementType }) { return

    ; } - const { getByRole } = render(); + const { getByRole } = render(); expect(getByRole('list')).toBeVisible(); expect(getByRole('list')).not.to.have.attribute('class'); expect(getByRole('list')).not.to.have.attribute('data-component'); }); it('the listbox leaf component can be changed', () => { - const { getByRole } = render(); + const { getByRole } = render(); expect(getByRole('menu')).to.have.tagName('div'); }); it('the option leaf component can be changed', () => { - const { getByRole } = render(); + const { getByRole } = render(); expect(getByRole('menuitem')).to.have.tagName('div'); }); }); From 9343bce1d7f6231818133559fbc3aee153bc70c4 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Thu, 3 Nov 2022 10:24:50 +0000 Subject: [PATCH 033/139] Apple useSlot to more components --- packages/mui-joy/src/Checkbox/Checkbox.tsx | 66 +++++++++---------- .../mui-joy/src/Checkbox/CheckboxProps.ts | 44 ++++++++++--- packages/mui-joy/src/Divider/Divider.tsx | 29 ++++---- .../mui-joy/src/FormControl/FormControl.tsx | 17 +++-- .../src/FormHelperText/FormHelperText.tsx | 18 +++-- packages/mui-joy/src/FormLabel/FormLabel.tsx | 35 +++++----- .../mui-joy/src/FormLabel/FormLabelProps.ts | 21 +++++- .../mui-joy/src/IconButton/IconButton.tsx | 15 ++--- .../mui-joy/src/IconButton/IconButtonProps.ts | 2 - packages/mui-joy/src/Input/Input.tsx | 61 ++++++++--------- packages/mui-joy/src/Input/InputProps.ts | 37 +++++++++-- 11 files changed, 199 insertions(+), 146 deletions(-) diff --git a/packages/mui-joy/src/Checkbox/Checkbox.tsx b/packages/mui-joy/src/Checkbox/Checkbox.tsx index adcb195fc33299..ce146abcfa1239 100644 --- a/packages/mui-joy/src/Checkbox/Checkbox.tsx +++ b/packages/mui-joy/src/Checkbox/Checkbox.tsx @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import { OverridableComponent } from '@mui/types'; import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { useSwitch } from '@mui/base/SwitchUnstyled'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import checkboxClasses, { getCheckboxUtilityClass } from './checkboxClasses'; import { CheckboxOwnerState, CheckboxTypeMap } from './CheckboxProps'; import CheckIcon from '../internal/svg-icons/Check'; @@ -188,8 +188,7 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { uncheckedIcon, checkedIcon = defaultCheckedIcon, label, - component, - componentsProps = {}, + component = 'span', defaultChecked, disabled: disabledExternalProp, disableIcon = false, @@ -261,38 +260,31 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: CheckboxRoot, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, + externalForwardedProps, ownerState, - additionalProps: { - ref, - as: component, - }, - className: classes.root, }); - const checkboxProps = useSlotProps({ + const [SlotCheckbox, checkboxProps] = useSlot('checkbox', { + className: classes.checkbox, elementType: CheckboxCheckbox, - externalSlotProps: componentsProps.checkbox, + externalForwardedProps, ownerState, - className: classes.checkbox, }); - const actionProps = useSlotProps({ + const [SlotAction, actionProps] = useSlot('action', { + className: classes.action, elementType: CheckboxAction, - externalSlotProps: componentsProps.action, + externalForwardedProps, ownerState, - className: classes.action, }); - const inputProps = useSlotProps({ - elementType: CheckboxInput, - getSlotProps: getInputProps, - externalSlotProps: componentsProps.input, - ownerState, + const [SlotInput, inputProps] = useSlot('input', { additionalProps: { id, name, @@ -306,34 +298,38 @@ const Checkbox = React.forwardRef(function Checkbox(inProps, ref) { }), }, className: classes.input, + elementType: CheckboxInput, + externalForwardedProps, + getSlotProps: getInputProps, + ownerState, }); - const labelProps = useSlotProps({ - elementType: CheckboxLabel, - externalSlotProps: componentsProps.label, - ownerState, + const [SlotLabel, labelProps] = useSlot('label', { additionalProps: { htmlFor: id, }, className: classes.label, + elementType: CheckboxLabel, + externalForwardedProps, + ownerState, }); return ( - - - - - + + + + + {indeterminate && !checked && !disableIcon && indeterminateIcon} {checked && !disableIcon && checkedIcon} {!checked && !disableIcon && !indeterminate && uncheckedIcon} - + {label && ( - {label} + {label} )} - + ); }) as OverridableComponent; @@ -376,7 +372,7 @@ Checkbox.propTypes /* remove-proptypes */ = { * The props used for each slot inside the component. * @default {} */ - componentsProps: PropTypes.shape({ + slotProps: PropTypes.shape({ action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), checkbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), diff --git a/packages/mui-joy/src/Checkbox/CheckboxProps.ts b/packages/mui-joy/src/Checkbox/CheckboxProps.ts index b43a79a6fbccdd..dccf5ed626182d 100644 --- a/packages/mui-joy/src/Checkbox/CheckboxProps.ts +++ b/packages/mui-joy/src/Checkbox/CheckboxProps.ts @@ -7,17 +7,35 @@ import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; export type CheckboxSlot = 'root' | 'checkbox' | 'action' | 'input' | 'label'; export interface CheckboxPropsVariantOverrides {} - export interface CheckboxPropsColorOverrides {} - export interface CheckboxPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'span', { sx?: SxProps }, CheckboxOwnerState>; - checkbox?: SlotComponentProps<'span', { sx?: SxProps }, CheckboxOwnerState>; - action?: SlotComponentProps<'span', { sx?: SxProps }, CheckboxOwnerState>; - input?: SlotComponentProps<'input', { sx?: SxProps }, CheckboxOwnerState>; - label?: SlotComponentProps<'label', { sx?: SxProps }, CheckboxOwnerState>; + root?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + CheckboxOwnerState + >; + checkbox?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + CheckboxOwnerState + >; + action?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + CheckboxOwnerState + >; + input?: SlotComponentProps< + 'input', + { component?: React.ElementType; sx?: SxProps }, + CheckboxOwnerState + >; + label?: SlotComponentProps< + 'label', + { component?: React.ElementType; sx?: SxProps }, + CheckboxOwnerState + >; } export interface CheckboxTypeMap

    { @@ -32,11 +50,21 @@ export interface CheckboxTypeMap

    { * Class name applied to the root element. */ className?: string; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + checkbox?: React.ElementType; + action?: React.ElementType; + input?: React.ElementType; + label?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' diff --git a/packages/mui-joy/src/Divider/Divider.tsx b/packages/mui-joy/src/Divider/Divider.tsx index 85842211b06350..cd9df47e51147a 100644 --- a/packages/mui-joy/src/Divider/Divider.tsx +++ b/packages/mui-joy/src/Divider/Divider.tsx @@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { DividerOwnerState, DividerTypeMap } from './DividerProps'; import { getDividerUtilityClass } from './dividerClasses'; @@ -117,24 +118,24 @@ const Divider = React.forwardRef(function Divider(inProps, ref) { const classes = useUtilityClasses(ownerState); - return ( - - {children} - - ); + }), + }, + ref, + className: clsx(classes.root, className), + elementType: DividerRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return {children}; }) as OverridableComponent; Divider.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/FormControl/FormControl.tsx b/packages/mui-joy/src/FormControl/FormControl.tsx index a0b2cf6077deec..02f4febea6aa35 100644 --- a/packages/mui-joy/src/FormControl/FormControl.tsx +++ b/packages/mui-joy/src/FormControl/FormControl.tsx @@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types'; import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils'; import composeClasses from '@mui/base/composeClasses'; import { useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import FormControlContext from './FormControlContext'; import formControlClasses, { getFormControlUtilityClass } from './formControlClasses'; @@ -131,15 +132,17 @@ const FormControl = React.forwardRef(function FormControl(inProps, ref) { [color, disabled, error, helperText, id, registerEffect, required, size], ); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: FormControlRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + return ( - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/FormHelperText/FormHelperText.tsx b/packages/mui-joy/src/FormHelperText/FormHelperText.tsx index a46f2aecb0e2ce..de43aa02484414 100644 --- a/packages/mui-joy/src/FormHelperText/FormHelperText.tsx +++ b/packages/mui-joy/src/FormHelperText/FormHelperText.tsx @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import { OverridableComponent } from '@mui/types'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import composeClasses from '@mui/base/composeClasses'; -import { useSlotProps } from '@mui/base/utils'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { FormHelperTextProps, FormHelperTextTypeMap } from './FormHelperTextProps'; import { getFormHelperTextUtilityClass } from './formHelperTextClasses'; import FormControlContext from '../FormControl/FormControlContext'; @@ -41,7 +41,7 @@ const FormHelperText = React.forwardRef(function FormHelperText(inProps, ref) { name: 'JoyFormHelperText', }); - const { children, component, ...other } = props; + const { children, component = 'p', ...other } = props; const rootRef = React.useRef(null); const handleRef = useForkRef(rootRef, ref); const formControl = React.useContext(FormControlContext); @@ -60,20 +60,18 @@ const FormHelperText = React.forwardRef(function FormHelperText(inProps, ref) { const classes = useUtilityClasses(); - const rootProps = useSlotProps({ - elementType: FormHelperTextRoot, - externalSlotProps: {}, - externalForwardedProps: other, - ownerState, + const [SlotRoot, rootProps] = useSlot('root', { additionalProps: { - ref: handleRef, - as: component, id: formControl?.['aria-describedby'], }, + ref: handleRef, className: classes.root, + elementType: FormHelperTextRoot, + externalForwardedProps: { ...other, component }, + ownerState, }); - return {children}; + return {children}; }) as OverridableComponent; FormHelperText.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/FormLabel/FormLabel.tsx b/packages/mui-joy/src/FormLabel/FormLabel.tsx index 1d0fb2b86f08f1..59ee5e52de4f70 100644 --- a/packages/mui-joy/src/FormLabel/FormLabel.tsx +++ b/packages/mui-joy/src/FormLabel/FormLabel.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; -import { useSlotProps } from '@mui/base/utils'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { FormLabelProps, FormLabelTypeMap } from './FormLabelProps'; import { getFormLabelUtilityClass } from './formLabelClasses'; import FormControlContext from '../FormControl/FormControlContext'; @@ -50,7 +50,7 @@ const FormLabel = React.forwardRef(function FormLabel(inProps, ref) { name: 'JoyFormLabel', }); - const { children, component = 'label', componentsProps = {}, ...other } = props; + const { children, component = 'label', ...other } = props; const formControl = React.useContext(FormControlContext); const required = inProps.required ?? formControl?.required ?? false; @@ -60,36 +60,33 @@ const FormLabel = React.forwardRef(function FormLabel(inProps, ref) { }; const classes = useUtilityClasses(); + const externalForwardedProps = { ...other, component }; - const rootProps = useSlotProps({ - elementType: FormLabelRoot, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, - ownerState, + const [SlotRoot, rootProps] = useSlot('root', { additionalProps: { - ref, - as: component, htmlFor: formControl?.htmlFor, id: formControl?.labelId, }, + ref, className: classes.root, + elementType: FormLabelRoot, + externalForwardedProps, + ownerState, }); - const asteriskProps = useSlotProps({ + const [SlotAsterisk, asteriskProps] = useSlot('asterisk', { + additionalProps: { 'aria-hidden': true }, + className: classes.asterisk, elementType: AsteriskComponent, - externalSlotProps: componentsProps.asterisk, + externalForwardedProps, ownerState, - additionalProps: { - 'aria-hidden': true, - }, - className: classes.asterisk, }); return ( - + {children} - {required &&  {'*'}} - + {required &&  {'*'}} + ); }) as OverridableComponent; @@ -111,7 +108,7 @@ FormLabel.propTypes /* remove-proptypes */ = { * The props used for each slot inside the component. * @default {} */ - componentsProps: PropTypes.shape({ + slotProps: PropTypes.shape({ asterisk: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), diff --git a/packages/mui-joy/src/FormLabel/FormLabelProps.ts b/packages/mui-joy/src/FormLabel/FormLabelProps.ts index 78a3581336d91f..463b77ad7ede77 100644 --- a/packages/mui-joy/src/FormLabel/FormLabelProps.ts +++ b/packages/mui-joy/src/FormLabel/FormLabelProps.ts @@ -6,8 +6,16 @@ import { SxProps } from '../styles/types'; export type FormLabelSlot = 'root' | 'asterisk'; interface ComponentsProps { - root?: SlotComponentProps<'label', { sx?: SxProps }, FormLabelOwnerState>; - asterisk?: SlotComponentProps<'span', { sx?: SxProps }, FormLabelOwnerState>; + root?: SlotComponentProps< + 'label', + { component?: React.ElementType; sx?: SxProps }, + FormLabelOwnerState + >; + asterisk?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + FormLabelOwnerState + >; } export interface FormLabelTypeMap

    { @@ -16,11 +24,18 @@ export interface FormLabelTypeMap

    * The content of the component. */ children?: React.ReactNode; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + asterisk?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The asterisk is added if required=`true` */ diff --git a/packages/mui-joy/src/IconButton/IconButton.tsx b/packages/mui-joy/src/IconButton/IconButton.tsx index f008725acebb9a..695253022424ce 100644 --- a/packages/mui-joy/src/IconButton/IconButton.tsx +++ b/packages/mui-joy/src/IconButton/IconButton.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } from '@mui/utils'; import { useButton } from '@mui/base/ButtonUnstyled'; -import { useSlotProps } from '@mui/base/utils'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import iconButtonClasses, { getIconButtonUtilityClass } from './iconButtonClasses'; import { IconButtonOwnerState, IconButtonTypeMap, ExtendIconButton } from './IconButtonProps'; @@ -137,19 +137,16 @@ const IconButton = React.forwardRef(function IconButton(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: IconButtonRoot, getSlotProps: getRootProps, - externalSlotProps: {}, - externalForwardedProps: other, + externalForwardedProps: { ...other, component }, ownerState, - additionalProps: { - as: component, - }, - className: classes.root, }); - return {children}; + return {children}; }) as ExtendIconButton; IconButton.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/IconButton/IconButtonProps.ts b/packages/mui-joy/src/IconButton/IconButtonProps.ts index 29cb334cc0340f..3aab381f8f04f8 100644 --- a/packages/mui-joy/src/IconButton/IconButtonProps.ts +++ b/packages/mui-joy/src/IconButton/IconButtonProps.ts @@ -10,9 +10,7 @@ import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; export type IconButtonSlot = 'root'; export interface IconButtonPropsVariantOverrides {} - export interface IconButtonPropsColorOverrides {} - export interface IconButtonPropsSizeOverrides {} export interface IconButtonTypeMap

    { diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx index 4896090d31b2e5..6d52fdc0e26853 100644 --- a/packages/mui-joy/src/Input/Input.tsx +++ b/packages/mui-joy/src/Input/Input.tsx @@ -3,8 +3,9 @@ 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 { useSlotProps, EventHandlers } from '@mui/base/utils'; +import { EventHandlers } from '@mui/base/utils'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { InputTypeMap, InputProps, InputOwnerState } from './InputProps'; import inputClasses, { getInputUtilityClass } from './inputClasses'; import useForwardedInput from './useForwardedInput'; @@ -248,8 +249,7 @@ const Input = React.forwardRef(function Input(inProps, ref) { inputStateClasses, getRootProps, getInputProps, - component, - componentsProps = {}, + component = 'div', formControl, focused, error: errorProp = false, @@ -291,58 +291,55 @@ const Input = React.forwardRef(function Input(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: [classes.root, rootStateClasses], elementType: InputRoot, getSlotProps: getRootProps, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, - additionalProps: { - ref, - as: component, - }, + externalForwardedProps, ownerState, - className: [classes.root, rootStateClasses], }); - const inputProps = useSlotProps({ + const [SlotInput, inputProps] = useSlot('input', { + ...(formControl && { + additionalProps: { + id: formControl.htmlFor, + 'aria-describedby': formControl['aria-describedby'], + }, + }), + className: [classes.input, inputStateClasses], elementType: InputInput, getSlotProps: (otherHandlers: EventHandlers) => getInputProps({ ...otherHandlers, ...propsToForward }), - externalSlotProps: componentsProps.input, + externalForwardedProps, ownerState, - additionalProps: formControl - ? { - id: formControl.htmlFor, - 'aria-describedby': formControl['aria-describedby'], - } - : {}, - className: [classes.input, inputStateClasses], }); - const startDecoratorProps = useSlotProps({ + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, elementType: InputStartDecorator, - externalSlotProps: componentsProps.startDecorator, + externalForwardedProps, ownerState, - className: classes.startDecorator, }); - const endDecoratorProps = useSlotProps({ + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { + className: classes.startDecorator, elementType: InputEndDecorator, - externalSlotProps: componentsProps.endDecorator, + externalForwardedProps, ownerState, - className: classes.endDecorator, }); return ( - + {startDecorator && ( - {startDecorator} + {startDecorator} )} - - {endDecorator && {endDecorator}} - + + {endDecorator && {endDecorator}} + ); }) as OverridableComponent; @@ -379,7 +376,7 @@ Input.propTypes /* remove-proptypes */ = { * The props used for each slot inside the component. * @default {} */ - componentsProps: PropTypes.shape({ + slotProps: PropTypes.shape({ endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), diff --git a/packages/mui-joy/src/Input/InputProps.ts b/packages/mui-joy/src/Input/InputProps.ts index b3b9f01dbce28c..4e046a499946f4 100644 --- a/packages/mui-joy/src/Input/InputProps.ts +++ b/packages/mui-joy/src/Input/InputProps.ts @@ -6,16 +6,30 @@ import { ColorPaletteProp, VariantProp, SxProps, ApplyColorInversion } from '../ export type InputSlot = 'root' | 'input' | 'startDecorator' | 'endDecorator'; export interface InputPropsVariantOverrides {} - export interface InputPropsColorOverrides {} - export interface InputPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, InputOwnerState>; - input?: SlotComponentProps<'input', { sx?: SxProps }, InputOwnerState>; - startDecorator?: SlotComponentProps<'span', { sx?: SxProps }, InputOwnerState>; - endDecorator?: SlotComponentProps<'span', { sx?: SxProps }, InputOwnerState>; + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + InputOwnerState + >; + input?: SlotComponentProps< + 'input', + { component?: React.ElementType; sx?: SxProps }, + InputOwnerState + >; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + InputOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + InputOwnerState + >; } export interface InputTypeMap

    { @@ -49,11 +63,20 @@ export interface InputTypeMap

    { * @default 'neutral' */ color?: OverridableStringUnion; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + input?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * Trailing adornment for this input. */ From dbe766d782e21c77c8aaece1962bcdb9686fedda Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Sat, 5 Nov 2022 11:02:17 -0400 Subject: [PATCH 034/139] Apply useSlot, add slots prop, rename componentsProps to slotProps for all other Joy components --- .../TabPanelUnstyled.types.ts | 1 - .../src/AspectRatio/AspectRatio.test.js | 2 +- .../src/Autocomplete/Autocomplete.test.tsx | 8 +- .../mui-joy/src/Autocomplete/Autocomplete.tsx | 70 +++++----- .../src/Autocomplete/AutocompleteProps.ts | 4 +- packages/mui-joy/src/Avatar/Avatar.test.js | 4 +- packages/mui-joy/src/Avatar/Avatar.tsx | 4 +- packages/mui-joy/src/Checkbox/Checkbox.tsx | 32 +++-- packages/mui-joy/src/Chip/Chip.test.js | 6 +- packages/mui-joy/src/Chip/Chip.tsx | 6 +- packages/mui-joy/src/FormLabel/FormLabel.tsx | 11 +- packages/mui-joy/src/Input/Input.tsx | 29 ++-- packages/mui-joy/src/Link/Link.tsx | 64 +++++---- packages/mui-joy/src/Link/LinkProps.ts | 24 +++- packages/mui-joy/src/List/List.tsx | 25 ++-- packages/mui-joy/src/List/ListProps.ts | 2 - .../mui-joy/src/ListDivider/ListDivider.tsx | 28 ++-- packages/mui-joy/src/ListItem/ListItem.tsx | 68 +++++++--- .../mui-joy/src/ListItem/ListItemProps.ts | 33 ++++- .../src/ListItemButton/ListItemButton.tsx | 24 ++-- .../src/ListItemContent/ListItemContent.tsx | 21 ++- .../ListItemDecorator/ListItemDecorator.tsx | 21 ++- .../src/ListSubheader/ListSubheader.tsx | 25 ++-- packages/mui-joy/src/Menu/Menu.tsx | 22 ++-- packages/mui-joy/src/MenuItem/MenuItem.tsx | 15 +-- packages/mui-joy/src/MenuList/MenuList.tsx | 17 +-- packages/mui-joy/src/Modal/Modal.test.tsx | 4 +- packages/mui-joy/src/Modal/Modal.tsx | 100 +++++++------- packages/mui-joy/src/Modal/ModalProps.ts | 21 ++- .../mui-joy/src/ModalClose/ModalClose.tsx | 23 ++-- .../mui-joy/src/ModalDialog/ModalDialog.tsx | 25 ++-- packages/mui-joy/src/Option/Option.tsx | 18 +-- packages/mui-joy/src/Radio/Radio.tsx | 109 ++++++++------- packages/mui-joy/src/Radio/RadioProps.ts | 46 ++++++- .../mui-joy/src/RadioGroup/RadioGroup.tsx | 35 ++--- .../ScopedCssBaseline/ScopedCssBaseline.tsx | 19 +-- packages/mui-joy/src/Select/Select.spec.tsx | 2 +- packages/mui-joy/src/Select/Select.test.tsx | 4 +- packages/mui-joy/src/Select/Select.tsx | 116 ++++++++-------- packages/mui-joy/src/Select/SelectProps.ts | 45 +++++-- packages/mui-joy/src/Sheet/Sheet.tsx | 19 +-- packages/mui-joy/src/Switch/Switch.test.js | 4 +- packages/mui-joy/src/Switch/Switch.tsx | 124 ++++++++++-------- packages/mui-joy/src/Switch/SwitchProps.ts | 67 ++++++++-- packages/mui-joy/src/Tab/Tab.tsx | 16 +-- packages/mui-joy/src/Tab/TabProps.ts | 1 - packages/mui-joy/src/TabList/TabList.tsx | 17 +-- packages/mui-joy/src/TabPanel/TabPanel.tsx | 20 ++- .../mui-joy/src/TabPanel/TabPanelProps.ts | 2 +- packages/mui-joy/src/Tabs/Tabs.tsx | 18 +-- packages/mui-joy/src/Tabs/TabsProps.ts | 2 +- .../mui-joy/src/TextField/TextField.test.js | 2 +- packages/mui-joy/src/TextField/TextField.tsx | 45 ++++--- .../mui-joy/src/TextField/TextFieldProps.ts | 2 +- .../mui-joy/src/Textarea/Textarea.test.tsx | 2 +- packages/mui-joy/src/Textarea/Textarea.tsx | 96 ++++++++------ .../mui-joy/src/Textarea/TextareaProps.ts | 37 +++++- .../mui-joy/src/Typography/Typography.tsx | 62 +++++---- .../mui-joy/src/Typography/TypographyProps.ts | 29 +++- 59 files changed, 994 insertions(+), 704 deletions(-) diff --git a/packages/mui-base/src/TabPanelUnstyled/TabPanelUnstyled.types.ts b/packages/mui-base/src/TabPanelUnstyled/TabPanelUnstyled.types.ts index dda1d467114cad..53286dadfe6f4d 100644 --- a/packages/mui-base/src/TabPanelUnstyled/TabPanelUnstyled.types.ts +++ b/packages/mui-base/src/TabPanelUnstyled/TabPanelUnstyled.types.ts @@ -23,7 +23,6 @@ export interface TabPanelUnstyledOwnProps { slots?: { root?: React.ElementType; }; - /** * The props used for each slot inside the TabPanel. * @default {} diff --git a/packages/mui-joy/src/AspectRatio/AspectRatio.test.js b/packages/mui-joy/src/AspectRatio/AspectRatio.test.js index 57e0cc1f978943..7c5436df0c3ec3 100644 --- a/packages/mui-joy/src/AspectRatio/AspectRatio.test.js +++ b/packages/mui-joy/src/AspectRatio/AspectRatio.test.js @@ -76,7 +76,7 @@ describe('', () => { it('able to pass the props to content slot', () => { const { getByTestId } = render( - , + , ); expect(getByTestId('content')).toBeVisible(); }); diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx index 80be88419afb23..ad05a2d2f88889 100644 --- a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx @@ -2064,14 +2064,14 @@ describe('Joy ', () => { expect(handleSubmit.callCount).to.equal(1); }); - describe('prop: componentsProps', () => { + describe('prop: slotProps', () => { it('should apply the props on the AutocompleteClearIndicator component', () => { render( ', () => { ', () => { render( ', () => { it('should be able to add more props to the image', () => { const onError = spy(); const { container } = render( - , + , ); const img = container.querySelector('img'); fireEvent.error(img); @@ -117,7 +117,7 @@ describe('', () => { it('should be able to add more props to the image', () => { const onError = spy(); const { container } = render( - , + , ); const img = container.querySelector('img'); fireEvent.error(img); diff --git a/packages/mui-joy/src/Avatar/Avatar.tsx b/packages/mui-joy/src/Avatar/Avatar.tsx index ec86b7673b73f2..48f6273db06d34 100644 --- a/packages/mui-joy/src/Avatar/Avatar.tsx +++ b/packages/mui-joy/src/Avatar/Avatar.tsx @@ -177,9 +177,7 @@ 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, - ...(typeof slotProps.img === 'function' - ? slotProps.img(ownerState) - : slotProps.img), + ...(typeof slotProps.img === 'function' ? slotProps.img(ownerState) : slotProps.img), src, srcSet, }); diff --git a/packages/mui-joy/src/Checkbox/Checkbox.tsx b/packages/mui-joy/src/Checkbox/Checkbox.tsx index ce146abcfa1239..472f7f079109c0 100644 --- a/packages/mui-joy/src/Checkbox/Checkbox.tsx +++ b/packages/mui-joy/src/Checkbox/Checkbox.tsx @@ -368,17 +368,6 @@ Checkbox.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - slotProps: PropTypes.shape({ - action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - checkbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * The default checked state. Use when the component is not controlled. */ @@ -459,6 +448,27 @@ Checkbox.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + checkbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + action: PropTypes.elementType, + checkbox: PropTypes.elementType, + input: PropTypes.elementType, + label: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-joy/src/Chip/Chip.test.js b/packages/mui-joy/src/Chip/Chip.test.js index 990f37c0377f11..08aebf175c5b63 100644 --- a/packages/mui-joy/src/Chip/Chip.test.js +++ b/packages/mui-joy/src/Chip/Chip.test.js @@ -110,15 +110,15 @@ describe('', () => { expect(handleClick.callCount).to.equal(1); }); - it('renders action element when `componentsProps.action` is provided', () => { - const { getByRole } = render(); + it('renders action element when `slotProps.action` is provided', () => { + const { getByRole } = render(); expect(getByRole('button')).toBeVisible(); }); it('renders custom action element', () => { const { getByRole } = render( - , + , ); expect(getByRole('link')).to.have.attr('href', '#'); diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index 2ae55d727866b6..45c98a0ad83181 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -226,9 +226,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { }; const resolvedActionProps = - typeof slotProps.action === 'function' - ? slotProps.action(ownerState) - : slotProps.action; + typeof slotProps.action === 'function' ? slotProps.action(ownerState) : slotProps.action; const actionRef = React.useRef(null); const { focusVisible, getRootProps } = useButton({ ...resolvedActionProps, @@ -279,7 +277,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) { ownerState, }); - const [SlotEndDecorator, endDecoratorProps] = useSlot('startDecorator', { + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { className: classes.startDecorator, elementType: ChipEndDecorator, externalForwardedProps: { ...other, slotProps }, diff --git a/packages/mui-joy/src/FormLabel/FormLabel.tsx b/packages/mui-joy/src/FormLabel/FormLabel.tsx index 59ee5e52de4f70..e3401e7bbde04e 100644 --- a/packages/mui-joy/src/FormLabel/FormLabel.tsx +++ b/packages/mui-joy/src/FormLabel/FormLabel.tsx @@ -104,6 +104,10 @@ FormLabel.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, + /** + * The asterisk is added if required=`true` + */ + required: PropTypes.bool, /** * The props used for each slot inside the component. * @default {} @@ -113,9 +117,12 @@ FormLabel.propTypes /* remove-proptypes */ = { root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), /** - * The asterisk is added if required=`true` + * Replace the default slots. */ - required: PropTypes.bool, + slots: PropTypes.shape({ + asterisk: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx index 6d52fdc0e26853..81e40ffc7406d5 100644 --- a/packages/mui-joy/src/Input/Input.tsx +++ b/packages/mui-joy/src/Input/Input.tsx @@ -372,16 +372,6 @@ Input.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']), PropTypes.string, ]), - /** - * The props used for each slot inside the component. - * @default {} - */ - slotProps: PropTypes.shape({ - endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * @ignore */ @@ -440,6 +430,25 @@ Input.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + endDecorator: PropTypes.elementType, + input: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), /** * Leading adornment for this input. */ diff --git a/packages/mui-joy/src/Link/Link.tsx b/packages/mui-joy/src/Link/Link.tsx index 7eceb43ca025da..ead6c4e2322116 100644 --- a/packages/mui-joy/src/Link/Link.tsx +++ b/packages/mui-joy/src/Link/Link.tsx @@ -7,10 +7,10 @@ import { unstable_useForkRef as useForkRef, unstable_useIsFocusVisible as useIsFocusVisible, } from '@mui/utils'; -import { useSlotProps } from '@mui/base/utils'; import { unstable_extendSxProp as extendSxProp } from '@mui/system'; import styled from '../styles/styled'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import linkClasses, { getLinkUtilityClass } from './linkClasses'; import { LinkProps, LinkOwnerState, LinkTypeMap } from './LinkProps'; import { TypographyContext } from '../Typography/Typography'; @@ -167,7 +167,6 @@ const Link = React.forwardRef(function Link(inProps, ref) { const { component = 'a', - componentsProps = {}, children, disabled = false, onBlur, @@ -224,42 +223,43 @@ const Link = React.forwardRef(function Link(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; - const rootProps = useSlotProps({ - elementType: LinkRoot, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, + const [SlotRoot, rootProps] = useSlot('root', { additionalProps: { - ref: handleRef, - as: component, onBlur: handleBlur, onFocus: handleFocus, }, - ownerState, + ref: handleRef, className: classes.root, + elementType: LinkRoot, + externalForwardedProps, + ownerState, }); - const startDecoratorProps = useSlotProps({ + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, elementType: StartDecorator, - externalSlotProps: componentsProps.startDecorator, + externalForwardedProps, ownerState, - className: classes.startDecorator, }); - const endDecoratorProps = useSlotProps({ + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { + className: classes.startDecorator, elementType: EndDecorator, - externalSlotProps: componentsProps.endDecorator, + externalForwardedProps, ownerState, - className: classes.endDecorator, }); return ( - - {startDecorator && {startDecorator}} + + {startDecorator && ( + {startDecorator} + )} {children} - {endDecorator && {endDecorator}} - + {endDecorator && {endDecorator}} + ); }) as OverridableComponent; @@ -285,15 +285,6 @@ Link.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * If `true`, the component is disabled. * @default false @@ -325,6 +316,23 @@ Link.propTypes /* remove-proptypes */ = { * @default false */ overlay: PropTypes.bool, + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + endDecorator: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), /** * Element placed before the children. */ diff --git a/packages/mui-joy/src/Link/LinkProps.ts b/packages/mui-joy/src/Link/LinkProps.ts index 6f28525a71638d..dc489d23abfddc 100644 --- a/packages/mui-joy/src/Link/LinkProps.ts +++ b/packages/mui-joy/src/Link/LinkProps.ts @@ -16,9 +16,17 @@ export interface LinkPropsVariantOverrides {} export interface LinkPropsColorOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'a', { sx?: SxProps }, LinkOwnerState>; - startDecorator?: SlotComponentProps<'span', { sx?: SxProps }, LinkOwnerState>; - endDecorator?: SlotComponentProps<'span', { sx?: SxProps }, LinkOwnerState>; + root?: SlotComponentProps<'a', { component?: React.ElementType; sx?: SxProps }, LinkOwnerState>; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + LinkOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + LinkOwnerState + >; } export interface LinkTypeMap

    { @@ -33,11 +41,19 @@ export interface LinkTypeMap

    { * @default 'primary' */ color?: OverridableStringUnion; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * If `true`, the component is disabled. * @default false diff --git a/packages/mui-joy/src/List/List.tsx b/packages/mui-joy/src/List/List.tsx index b4af0a8cde9116..41fe219d047180 100644 --- a/packages/mui-joy/src/List/List.tsx +++ b/packages/mui-joy/src/List/List.tsx @@ -7,6 +7,7 @@ import composeClasses from '@mui/base/composeClasses'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import { SelectUnstyledContext } from '@mui/base/SelectUnstyled'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { ListProps, ListOwnerState, ListTypeMap } from './ListProps'; import { getListUtilityClass } from './listClasses'; import NestedListContext from './NestedListContext'; @@ -190,16 +191,20 @@ const List = React.forwardRef(function List(inProps, ref) { const classes = useUtilityClasses(ownerState); + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { + 'aria-labelledby': typeof nesting === 'string' ? nesting : undefined, + role, + }, + ref, + className: clsx(classes.root, className), + elementType: ListRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + return ( - + @@ -207,7 +212,7 @@ const List = React.forwardRef(function List(inProps, ref) { {children} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/List/ListProps.ts b/packages/mui-joy/src/List/ListProps.ts index 6de57cf9b48b5b..4eb6123163ee63 100644 --- a/packages/mui-joy/src/List/ListProps.ts +++ b/packages/mui-joy/src/List/ListProps.ts @@ -5,9 +5,7 @@ import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; export type ListSlot = 'root'; export interface ListPropsSizeOverrides {} - export interface ListPropsVariantOverrides {} - export interface ListPropsColorOverrides {} export interface ListTypeMap

    { diff --git a/packages/mui-joy/src/ListDivider/ListDivider.tsx b/packages/mui-joy/src/ListDivider/ListDivider.tsx index 5a73ad4e810b8c..8fde0187961d96 100644 --- a/packages/mui-joy/src/ListDivider/ListDivider.tsx +++ b/packages/mui-joy/src/ListDivider/ListDivider.tsx @@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import Divider from '../Divider/Divider'; import { ListDividerOwnerState, ListDividerTypeMap } from './ListDividerProps'; import { getListDividerUtilityClass } from './listDividerClasses'; @@ -87,20 +88,19 @@ const ListDivider = React.forwardRef(function ListDivider(inProps, ref) { const classes = useUtilityClasses(ownerState); - return ( - - {children} - - ); + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { + orientation, + ...(inset === 'context' && { inset }), + }, + ref, + className: clsx(classes.root, className), + elementType: ListDividerRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return {children}; }) as OverridableComponent; ListDivider.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/ListItem/ListItem.tsx b/packages/mui-joy/src/ListItem/ListItem.tsx index 5ede1556990d6d..2c609e4363a076 100644 --- a/packages/mui-joy/src/ListItem/ListItem.tsx +++ b/packages/mui-joy/src/ListItem/ListItem.tsx @@ -9,6 +9,7 @@ import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { MenuUnstyledContext } from '@mui/base/MenuUnstyled'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { ListItemProps, ListItemOwnerState, ListItemTypeMap } from './ListItemProps'; import { getListItemUtilityClass } from './listItemClasses'; import NestedListContext from '../List/NestedListContext'; @@ -192,22 +193,38 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; + + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { + role, + }, + ref, + className: clsx(classes.root, className), + elementType: ListItemRoot, + externalForwardedProps, + ownerState, + }); + + const [SlotStartAction, startActionProps] = useSlot('startAction', { + className: classes.startAction, + elementType: ListItemStartAction, + externalForwardedProps, + ownerState, + }); + + const [SlotEndAction, endActionProps] = useSlot('endAction', { + className: classes.endAction, + elementType: ListItemEndAction, + externalForwardedProps, + ownerState, + }); + return ( - - {startAction && ( - - {startAction} - - )} + + {startAction && {startAction}} {React.Children.map(children, (child, index) => React.isValidElement(child) @@ -222,12 +239,8 @@ const ListItem = React.forwardRef(function ListItem(inProps, ref) { : child, )} - {endAction && ( - - {endAction} - - )} - + {endAction && {endAction}} + ); @@ -272,6 +285,23 @@ ListItem.propTypes /* remove-proptypes */ = { * @ignore */ role: PropTypes /* @typescript-to-proptypes-ignore */.string, + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + endAction: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startAction: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + endAction: PropTypes.elementType, + root: PropTypes.elementType, + startAction: PropTypes.elementType, + }), /** * The element to display at the start of ListItem. */ diff --git a/packages/mui-joy/src/ListItem/ListItemProps.ts b/packages/mui-joy/src/ListItem/ListItemProps.ts index 277ab842827ea8..f6fe4a81907710 100644 --- a/packages/mui-joy/src/ListItem/ListItemProps.ts +++ b/packages/mui-joy/src/ListItem/ListItemProps.ts @@ -1,13 +1,31 @@ import * as React from 'react'; import { OverridableStringUnion, OverrideProps } from '@mui/types'; +import { SlotComponentProps } from '@mui/base/utils'; import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; export type ListItemSlot = 'root' | 'startAction' | 'endAction'; export interface ListItemPropsVariantOverrides {} - export interface ListItemPropsColorOverrides {} +interface ComponentsProps { + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + ListItemOwnerState + >; + startAction?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + ListItemOwnerState + >; + endAction?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + ListItemOwnerState + >; +} + export interface ListItemTypeMap

    { props: P & { /** @@ -19,6 +37,19 @@ export interface ListItemTypeMap

    { * The content of the component. */ children?: React.ReactNode; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + startAction?: React.ElementType; + endAction?: React.ElementType; + }; + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps?: ComponentsProps; /** * The element to display at the start of ListItem. */ diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx index c2565af8fb3ecd..4c10f716819b2f 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx @@ -5,6 +5,7 @@ import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } import composeClasses from '@mui/base/composeClasses'; import { useButton } from '@mui/base/ButtonUnstyled'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { ListItemButtonOwnerState, ExtendListItemButton, @@ -156,20 +157,21 @@ const ListItemButton = React.forwardRef(function ListItemButton(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = getRootProps(); + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { + role: role ?? getRootProps().role, + }, + ref, + className: clsx(classes.root, className), + elementType: ListItemButtonRoot, + getSlotProps: getRootProps, + externalForwardedProps: { ...other, component }, + ownerState, + }); return ( - - {children} - + {children} ); }) as ExtendListItemButton; diff --git a/packages/mui-joy/src/ListItemContent/ListItemContent.tsx b/packages/mui-joy/src/ListItemContent/ListItemContent.tsx index fb0395262d83de..7e5568c71815f3 100644 --- a/packages/mui-joy/src/ListItemContent/ListItemContent.tsx +++ b/packages/mui-joy/src/ListItemContent/ListItemContent.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { ListItemContentOwnerState, ListItemContentTypeMap } from './ListItemContentProps'; import { getListItemContentUtilityClass } from './listItemContentClasses'; @@ -38,17 +39,15 @@ const ListItemContent = React.forwardRef(function ListItemContent(inProps, ref) const classes = useUtilityClasses(); - return ( - - {children} - - ); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: ListItemContentRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return {children}; }) as OverridableComponent; ListItemContent.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/ListItemDecorator/ListItemDecorator.tsx b/packages/mui-joy/src/ListItemDecorator/ListItemDecorator.tsx index 6abad47e6f96a3..623b224516f8f4 100644 --- a/packages/mui-joy/src/ListItemDecorator/ListItemDecorator.tsx +++ b/packages/mui-joy/src/ListItemDecorator/ListItemDecorator.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { ListItemDecoratorOwnerState, ListItemDecoratorTypeMap } from './ListItemDecoratorProps'; import { getListItemDecoratorUtilityClass } from './listItemDecoratorClasses'; import ListItemButtonOrientationContext from '../ListItemButton/ListItemButtonOrientationContext'; @@ -50,17 +51,15 @@ const ListItemDecorator = React.forwardRef(function ListItemDecorator(inProps, r const classes = useUtilityClasses(); - return ( - - {children} - - ); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: ListItemDecoratorRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return {children}; }) as OverridableComponent; ListItemDecorator.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx index 0ac1ce43fe88e0..93009a14435a6c 100644 --- a/packages/mui-joy/src/ListSubheader/ListSubheader.tsx +++ b/packages/mui-joy/src/ListSubheader/ListSubheader.tsx @@ -5,6 +5,7 @@ import { OverridableComponent } from '@mui/types'; import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils'; import composeClasses from '@mui/base/composeClasses'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { ListSubheaderOwnerState, ListSubheaderTypeMap } from './ListSubheaderProps'; import { getListSubheaderUtilityClass } from './listSubheaderClasses'; import ListSubheaderDispatch from './ListSubheaderContext'; @@ -88,18 +89,18 @@ const ListSubheader = React.forwardRef(function ListSubheader(inProps, ref) { const classes = useUtilityClasses(ownerState); - return ( - - {children} - - ); + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { + id, + }, + ref, + className: clsx(classes.root, className), + elementType: ListSubheaderRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return {children}; }) as OverridableComponent; ListSubheader.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/Menu/Menu.tsx b/packages/mui-joy/src/Menu/Menu.tsx index ac6f32b32b89f9..336700b3ac658e 100644 --- a/packages/mui-joy/src/Menu/Menu.tsx +++ b/packages/mui-joy/src/Menu/Menu.tsx @@ -3,12 +3,12 @@ import PropTypes from 'prop-types'; import { unstable_capitalize as capitalize, HTMLElementType, refType } from '@mui/utils'; import { OverridableComponent } from '@mui/types'; import composeClasses from '@mui/base/composeClasses'; -import { useSlotProps } from '@mui/base/utils'; import { useMenu, MenuUnstyledContext, MenuUnstyledContextType } from '@mui/base/MenuUnstyled'; import PopperUnstyled from '@mui/base/PopperUnstyled'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { MenuTypeMap, MenuProps, MenuOwnerState } from './MenuProps'; import { getMenuUtilityClass } from './menuClasses'; @@ -125,22 +125,20 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ - elementType: MenuRoot, - externalForwardedProps: other, - getSlotProps: getListboxProps, - externalSlotProps: {}, + const [SlotRoot, rootProps] = useSlot('root', { additionalProps: { anchorEl, - open, + component: MenuRoot, disablePortal, keepMounted, - ref, - component: MenuRoot, - as: component, // use `as` to insert the component inside of the MenuRoot modifiers: cachedModifiers, + open, }, + ref, className: classes.root, + elementType: PopperUnstyled, + getSlotProps: getListboxProps, + externalForwardedProps: { ...other, component }, ownerState, }); @@ -156,11 +154,11 @@ const Menu = React.forwardRef(function Menu(inProps, ref) { ); return ( - + {children} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/MenuItem/MenuItem.tsx b/packages/mui-joy/src/MenuItem/MenuItem.tsx index 5d7598cbf394f5..14bffdf8d9b5eb 100644 --- a/packages/mui-joy/src/MenuItem/MenuItem.tsx +++ b/packages/mui-joy/src/MenuItem/MenuItem.tsx @@ -2,10 +2,10 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_capitalize as capitalize } from '@mui/utils'; import composeClasses from '@mui/base/composeClasses'; -import { useSlotProps } from '@mui/base/utils'; import { useMenuItem } from '@mui/base/MenuItemUnstyled'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { getMenuItemUtilityClass } from './menuItemClasses'; import { MenuItemProps, @@ -75,19 +75,16 @@ const MenuItem = React.forwardRef(function MenuItem(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: MenuItemRoot, getSlotProps: getRootProps, - externalSlotProps: {}, - additionalProps: { - as: component, - }, - externalForwardedProps: other, - className: classes.root, + externalForwardedProps: { ...other, component }, ownerState, }); - return {children}; + return {children}; }) as ExtendMenuItem; MenuItem.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/MenuList/MenuList.tsx b/packages/mui-joy/src/MenuList/MenuList.tsx index d36f7f48b22846..dd139a1d7bb015 100644 --- a/packages/mui-joy/src/MenuList/MenuList.tsx +++ b/packages/mui-joy/src/MenuList/MenuList.tsx @@ -3,10 +3,10 @@ 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 { useSlotProps } from '@mui/base/utils'; import { useMenu, MenuUnstyledContext, MenuUnstyledContextType } from '@mui/base/MenuUnstyled'; import { styled, useThemeProps } from '../styles'; import { StyledList } from '../List/List'; +import useSlot from '../utils/useSlot'; import ListProvider, { scopedVariables } from '../List/ListProvider'; import { MenuListProps, MenuListOwnerState, MenuListTypeMap } from './MenuListProps'; import { getMenuListUtilityClass } from './menuListClasses'; @@ -97,16 +97,13 @@ const MenuList = React.forwardRef(function MenuList(inProps, ref) { const classes = useUtilityClasses(ownerState); - const listboxProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: MenuListRoot, getSlotProps: getListboxProps, - externalSlotProps: {}, - externalForwardedProps: other, - additionalProps: { - as: component, - }, + externalForwardedProps: { ...other, component }, ownerState, - className: classes.root, }); const contextValue = React.useMemo( @@ -123,11 +120,11 @@ const MenuList = React.forwardRef(function MenuList(inProps, ref) { ); return ( - + {children} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/Modal/Modal.test.tsx b/packages/mui-joy/src/Modal/Modal.test.tsx index 0a5a9b4d889655..3c564b6fe40358 100644 --- a/packages/mui-joy/src/Modal/Modal.test.tsx +++ b/packages/mui-joy/src/Modal/Modal.test.tsx @@ -113,7 +113,7 @@ describe('', () => { @@ -146,7 +146,7 @@ describe('', () => {

    , diff --git a/packages/mui-joy/src/Modal/Modal.tsx b/packages/mui-joy/src/Modal/Modal.tsx index 51f89208bd7336..dbcc325608c0ac 100644 --- a/packages/mui-joy/src/Modal/Modal.tsx +++ b/packages/mui-joy/src/Modal/Modal.tsx @@ -8,12 +8,12 @@ import { unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, } from '@mui/utils'; -import { useSlotProps } from '@mui/base/utils'; import composeClasses from '@mui/base/composeClasses'; import Portal from '@mui/base/Portal'; import FocusTrap from '@mui/base/FocusTrap'; import { ModalManager } from '@mui/base/ModalUnstyled'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { getModalUtilityClass } from './modalClasses'; import { ModalOwnerState, ModalTypeMap } from './ModalProps'; import CloseModalContext from './CloseModalContext'; @@ -31,6 +31,7 @@ const useUtilityClasses = (ownerState: ModalOwnerState) => { const slots = { root: ['root', !open && 'hidden'], + backdrop: ['backdrop'], }; return composeClasses(slots, getModalUtilityClass, {}); @@ -78,7 +79,7 @@ const ModalBackdrop = styled('div', { }), })); -const ModalUnstyled = React.forwardRef(function ModalUnstyled(inProps, ref) { +const Modal = React.forwardRef(function ModalUnstyled(inProps, ref) { const props = useThemeProps({ props: inProps, name: 'JoyModal', @@ -87,7 +88,6 @@ const ModalUnstyled = React.forwardRef(function ModalUnstyled(inProps, ref) { const { children, component = 'div', - componentsProps = {}, container, disableAutoFocus = false, disableEnforceFocus = false, @@ -227,28 +227,26 @@ const ModalUnstyled = React.forwardRef(function ModalUnstyled(inProps, ref) { } }; - const rootProps = useSlotProps({ - elementType: ModalRoot, - externalSlotProps: componentsProps.root, - externalForwardedProps: other, - additionalProps: { - as: component, - ref: handleRef, - role: 'presentation', - onKeyDown: handleKeyDown, - }, + const externalForwardedProps = { ...other, component }; + + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { role: 'presentation', onKeyDown: handleKeyDown }, + ref: handleRef, className: classes.root, + elementType: ModalRoot, + externalForwardedProps, ownerState, }); - const backdropProps = useSlotProps({ - elementType: ModalBackdrop, - externalSlotProps: componentsProps.backdrop, + const [SlotBackdrop, backdropProps] = useSlot('backdrop', { additionalProps: { 'aria-hidden': true, onClick: handleBackdropClick, open, }, + className: classes.backdrop, + elementType: ModalBackdrop, + externalForwardedProps, ownerState, }); @@ -266,8 +264,8 @@ const ModalUnstyled = React.forwardRef(function ModalUnstyled(inProps, ref) { * is not meant for humans to interact with directly. * https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md */} - - {!hideBackdrop ? : null} + + {!hideBackdrop ? : null} - + ); }) as OverridableComponent; -ModalUnstyled.propTypes /* remove-proptypes */ = { +Modal.propTypes /* remove-proptypes */ = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the d.ts file and run "yarn proptypes" | + // | To update them edit TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- /** * A single child content element. */ children: elementAcceptingRef.isRequired, - /** - * Override or extend the styles applied to the component. - */ - classes: PropTypes.object, - /** - * When set to true the Modal waits until a nested Transition is completed before closing. - * @default false - */ - closeAfterTransition: PropTypes.bool, /** * The component used for the root node. * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The components used for each slot inside the Modal. - * Either a string to use a HTML element or a component. - * @default {} - */ - components: PropTypes.shape({ - Backdrop: PropTypes.elementType, - Root: PropTypes.elementType, - }), - /** - * The props used for each slot inside the Modal. - * @default {} - */ - componentsProps: PropTypes.shape({ - backdrop: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * An HTML element or function that returns one. * The `container` will have the portal children appended to it. @@ -390,17 +362,12 @@ ModalUnstyled.propTypes /* remove-proptypes */ = { * @default false */ keepMounted: PropTypes.bool, - /** - * Callback fired when the backdrop is clicked. - * @deprecated Use the `onClose` prop with the `reason` argument to handle the `backdropClick` events. - */ - onBackdropClick: PropTypes.func, /** * Callback fired when the component requests to be closed. * The `reason` parameter can optionally be used to control the response to `onClose`. * * @param {object} event The event source of the callback. - * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`. + * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`, `"closeClick"`. */ onClose: PropTypes.func, /** @@ -411,6 +378,29 @@ ModalUnstyled.propTypes /* remove-proptypes */ = { * If `true`, the component is shown. */ open: PropTypes.bool.isRequired, -}; + /** + * The props used for each slot inside the Modal. + * @default {} + */ + slotProps: PropTypes.shape({ + backdrop: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + backdrop: PropTypes.elementType, + root: PropTypes.elementType, + }), + /** + * 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, + ]), +} as any; -export default ModalUnstyled; +export default Modal; diff --git a/packages/mui-joy/src/Modal/ModalProps.ts b/packages/mui-joy/src/Modal/ModalProps.ts index 99b0e5639cd4e3..3504d5dff1788b 100644 --- a/packages/mui-joy/src/Modal/ModalProps.ts +++ b/packages/mui-joy/src/Modal/ModalProps.ts @@ -7,8 +7,16 @@ import { SxProps } from '../styles/types'; export type ModalSlot = 'root' | 'backdrop'; interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, ModalOwnerState>; - backdrop?: SlotComponentProps<'div', { sx?: SxProps }, ModalOwnerState>; + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + ModalOwnerState + >; + backdrop?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + ModalOwnerState + >; } export interface ModalTypeMap

    { @@ -27,11 +35,18 @@ export interface ModalTypeMap

    { | 'keepMounted' | 'open' > & { + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + backdrop?: React.ElementType; + }; /** * The props used for each slot inside the Modal. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * Callback fired when the component requests to be closed. * The `reason` parameter can optionally be used to control the response to `onClose`. diff --git a/packages/mui-joy/src/ModalClose/ModalClose.tsx b/packages/mui-joy/src/ModalClose/ModalClose.tsx index 48dd83236fca04..40a1392b4bd107 100644 --- a/packages/mui-joy/src/ModalClose/ModalClose.tsx +++ b/packages/mui-joy/src/ModalClose/ModalClose.tsx @@ -3,10 +3,10 @@ import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize } from '@mui/utils'; -import { useSlotProps } from '@mui/base/utils'; import { useButton } from '@mui/base/ButtonUnstyled'; import { useThemeProps, styled } from '../styles'; import { StyledIconButton } from '../IconButton/IconButton'; +import useSlot from '../utils/useSlot'; import { getModalCloseUtilityClass } from './modalCloseClasses'; import { ModalCloseProps, ModalCloseTypeMap } from './ModalCloseProps'; import CloseIcon from '../internal/svg-icons/Close'; @@ -102,27 +102,28 @@ const ModalClose = React.forwardRef(function ModalClose(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ - elementType: ModalCloseRoot, - getSlotProps: getRootProps, - externalSlotProps: { + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { onClick: (event: React.MouseEvent) => { closeModalContext?.(event, 'closeClick'); onClick?.(event); }, - ...other, - }, - additionalProps: { - as: component, }, + ref, className: classes.root, + elementType: ModalCloseRoot, + externalForwardedProps: { + ...other, + component, + }, + getSlotProps: getRootProps, ownerState, }); return ( - + - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx index 7542cc2b4c3021..67abd4e101562e 100644 --- a/packages/mui-joy/src/ModalDialog/ModalDialog.tsx +++ b/packages/mui-joy/src/ModalDialog/ModalDialog.tsx @@ -8,6 +8,7 @@ import { unstable_isMuiElement as isMuiElement, } from '@mui/utils'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { SheetRoot } from '../Sheet/Sheet'; import { getModalDialogUtilityClass } from './modalDialogClasses'; import { ModalDialogProps, ModalDialogTypeMap } from './ModalDialogProps'; @@ -111,18 +112,22 @@ const ModalDialog = React.forwardRef(function ModalDialog(inProps, ref) { const contextValue = React.useMemo(() => ({ variant, color }), [color, variant]); + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { + role: 'dialog', + 'aria-modal': 'true', + }, + ref, + className: clsx(classes.root, className), + elementType: ModalDialogRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + return ( - + {React.Children.map(children, (child) => { if (!React.isValidElement(child)) { return child; @@ -134,7 +139,7 @@ const ModalDialog = React.forwardRef(function ModalDialog(inProps, ref) { } return child; })} - + ); diff --git a/packages/mui-joy/src/Option/Option.tsx b/packages/mui-joy/src/Option/Option.tsx index c7a8ab4683f327..2dcae01bac2b19 100644 --- a/packages/mui-joy/src/Option/Option.tsx +++ b/packages/mui-joy/src/Option/Option.tsx @@ -2,10 +2,10 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import composeClasses from '@mui/base/composeClasses'; -import { useSlotProps } from '@mui/base/utils'; import { SelectUnstyledContext } from '@mui/base/SelectUnstyled'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { OptionOwnerState, ExtendOption, OptionTypeMap } from './OptionProps'; import optionClasses, { getOptionUtilityClass } from './optionClasses'; import RowListContext from '../List/RowListContext'; @@ -100,20 +100,16 @@ const Option = React.forwardRef(function Option(inProps, ref) { const classes = useUtilityClasses(ownerState); - const rootProps = useSlotProps({ - elementType: OptionRoot, - externalSlotProps: {}, - externalForwardedProps: other, - additionalProps: { - ...optionProps, - ref: handleRef, - as: component, - }, + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: optionProps, + ref: handleRef, className: classes.root, + elementType: OptionRoot, + externalForwardedProps: { ...other, component }, ownerState, }); - return {children}; + return {children}; }) as ExtendOption; Option.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/Radio/Radio.tsx b/packages/mui-joy/src/Radio/Radio.tsx index 9ac7a0e92104e0..bde3fce05660f0 100644 --- a/packages/mui-joy/src/Radio/Radio.tsx +++ b/packages/mui-joy/src/Radio/Radio.tsx @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize, unstable_useId as useId } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; -import { useSlotProps } from '@mui/base/utils'; import { useSwitch } from '@mui/base/SwitchUnstyled'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import radioClasses, { getRadioUtilityClass } from './radioClasses'; import { RadioOwnerState, RadioTypeMap } from './RadioProps'; import RadioGroupContext from '../RadioGroup/RadioGroupContext'; @@ -224,7 +224,6 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { checked: checkedProp, checkedIcon, component, - componentsProps = {}, defaultChecked, disabled: disabledProp, disableIcon: disableIconProp = false, @@ -303,45 +302,38 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { }; const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { ...other, component }; - const rootProps = useSlotProps({ - elementType: RadioRoot, - externalForwardedProps: other, - externalSlotProps: componentsProps.root, - additionalProps: { - as: component, - ref, - }, + const [SlotRoot, rootProps] = useSlot('root', { + ref, className: classes.root, + elementType: RadioRoot, + externalForwardedProps, ownerState, }); - const radioProps = useSlotProps({ - elementType: RadioRadio, - externalSlotProps: componentsProps.radio, + const [SlotRadio, radioProps] = useSlot('radio', { className: classes.radio, + elementType: RadioRadio, + externalForwardedProps, ownerState, }); - const radioIconProps = useSlotProps({ - elementType: RadioIcon, - externalSlotProps: componentsProps.icon, + const [SlotIcon, iconProps] = useSlot('icon', { className: classes.icon, + elementType: RadioIcon, + externalForwardedProps, ownerState, }); - const radioActionProps = useSlotProps({ - elementType: RadioAction, - externalSlotProps: componentsProps.action, + const [SlotAction, actionProps] = useSlot('action', { className: classes.action, + elementType: RadioAction, + externalForwardedProps, ownerState, }); - const radioInputProps = useSlotProps({ - elementType: RadioInput, - getSlotProps: () => getInputProps({ onChange: radioGroup?.onChange }), - externalSlotProps: componentsProps.input, - className: classes.input, + const [SlotInput, inputProps] = useSlot('input', { additionalProps: { type: 'radio', id, @@ -351,36 +343,40 @@ const Radio = React.forwardRef(function Radio(inProps, ref) { value: String(value), 'aria-describedby': formControl?.['aria-describedby'], }, + className: classes.input, + elementType: RadioInput, + externalForwardedProps, + getSlotProps: () => getInputProps({ onChange: radioGroup?.onChange }), ownerState, }); - const radioLabelProps = useSlotProps({ - elementType: RadioLabel, - externalSlotProps: componentsProps.label, - className: classes.label, - ownerState, + const [SlotLabel, labelProps] = useSlot('label', { additionalProps: { htmlFor: id, }, + className: classes.label, + elementType: RadioLabel, + externalForwardedProps, + ownerState, }); return ( - - + + {checked && !disableIcon && checkedIcon} {!checked && !disableIcon && uncheckedIcon} - {!checkedIcon && !uncheckedIcon && !disableIcon && } - - - - + {!checkedIcon && !uncheckedIcon && !disableIcon && } + + + + {label && ( - + {/* Automatically adjust the Typography to render `span` */} {label} - + )} - + ); }) as OverridableComponent; @@ -418,18 +414,6 @@ Radio.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - radio: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * The default checked state. Use when the component is not controlled. */ @@ -497,6 +481,29 @@ Radio.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + radio: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * @ignore + */ + slots: PropTypes.shape({ + action: PropTypes.elementType, + icon: PropTypes.elementType, + input: PropTypes.elementType, + label: PropTypes.elementType, + radio: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-joy/src/Radio/RadioProps.ts b/packages/mui-joy/src/Radio/RadioProps.ts index 23a6ff1cb870fa..10aa9f867f9234 100644 --- a/packages/mui-joy/src/Radio/RadioProps.ts +++ b/packages/mui-joy/src/Radio/RadioProps.ts @@ -13,12 +13,36 @@ export interface RadioPropsColorOverrides {} export interface RadioPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'span', { sx?: SxProps }, RadioOwnerState>; - radio?: SlotComponentProps<'span', { sx?: SxProps }, RadioOwnerState>; - icon?: SlotComponentProps<'span', { sx?: SxProps }, RadioOwnerState>; - action?: SlotComponentProps<'span', { sx?: SxProps }, RadioOwnerState>; - input?: SlotComponentProps<'input', { sx?: SxProps }, RadioOwnerState>; - label?: SlotComponentProps<'label', { sx?: SxProps }, RadioOwnerState>; + root?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + RadioOwnerState + >; + radio?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + RadioOwnerState + >; + icon?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + RadioOwnerState + >; + action?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + RadioOwnerState + >; + input?: SlotComponentProps< + 'input', + { component?: React.ElementType; sx?: SxProps }, + RadioOwnerState + >; + label?: SlotComponentProps< + 'label', + { component?: React.ElementType; sx?: SxProps }, + RadioOwnerState + >; } export interface RadioTypeMap

    { @@ -37,11 +61,19 @@ export interface RadioTypeMap

    { * Either a string to use a HTML element or a component. */ component?: React.ElementType; + slots?: { + root?: React.ElementType; + radio?: React.ElementType; + icon?: React.ElementType; + action?: React.ElementType; + input?: React.ElementType; + label?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' diff --git a/packages/mui-joy/src/RadioGroup/RadioGroup.tsx b/packages/mui-joy/src/RadioGroup/RadioGroup.tsx index e4c65cb3bcb7d6..24ab2b32302aa0 100644 --- a/packages/mui-joy/src/RadioGroup/RadioGroup.tsx +++ b/packages/mui-joy/src/RadioGroup/RadioGroup.tsx @@ -9,6 +9,7 @@ import { } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { getRadioGroupUtilityClass } from './radioGroupClasses'; import { RadioGroupOwnerState, RadioGroupTypeMap } from './RadioGroupProps'; import RadioGroupContext from './RadioGroupContext'; @@ -125,22 +126,26 @@ const RadioGroup = React.forwardRef(function RadioGroup(inProps, ref) { [disableIcon, name, onChange, overlay, row, setValueState, size, value], ); + const [SlotRoot, rootProps] = useSlot('root', { + additionalProps: { + role, + // The `id` is just for the completeness, it does not have any effect because RadioGroup (div) is non-labellable element + // MDN: "If it is not a labelable element, then the for attribute has no effect" + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label#attr-for + id: formControl?.htmlFor, + 'aria-labelledby': formControl?.labelId, + 'aria-describedby': formControl?.['aria-describedby'], + }, + ref, + className: clsx(classes.root, className), + elementType: RadioGroupRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + return ( - + {React.Children.map(children, (child, index) => React.isValidElement(child) @@ -153,7 +158,7 @@ const RadioGroup = React.forwardRef(function RadioGroup(inProps, ref) { : child, )} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx index e1b1b8d6978a04..2cb9d0b6e3c9f5 100644 --- a/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx +++ b/packages/mui-joy/src/ScopedCssBaseline/ScopedCssBaseline.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import { OverridableComponent } from '@mui/types'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import useThemeProps from '../styles/useThemeProps'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { DefaultColorScheme, ColorSystem } from '../styles/types'; import { @@ -77,15 +78,15 @@ const ScopedCssBaseline = React.forwardRef(function ScopedCssBaseline(inProps, r const classes = useUtilityClasses(); - return ( - - ); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: ScopedCssBaselineRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + return ; }) as OverridableComponent; ScopedCssBaseline.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/Select/Select.spec.tsx b/packages/mui-joy/src/Select/Select.spec.tsx index c027524b988fb2..ec8656b05f6f08 100644 --- a/packages/mui-joy/src/Select/Select.spec.tsx +++ b/packages/mui-joy/src/Select/Select.spec.tsx @@ -39,7 +39,7 @@ interface Value { />; ', () => { const { getByRole } = render( Category 1 diff --git a/packages/mui-joy/src/Select/Select.tsx b/packages/mui-joy/src/Select/Select.tsx index e51f9f4006ff5c..971f463a28ba81 100644 --- a/packages/mui-joy/src/Select/Select.tsx +++ b/packages/mui-joy/src/Select/Select.tsx @@ -14,12 +14,12 @@ import { getOptionsFromChildren, } from '@mui/base/SelectUnstyled'; import type { SelectChild, SelectOption } from '@mui/base/SelectUnstyled'; -import { useSlotProps } from '@mui/base/utils'; import composeClasses from '@mui/base/composeClasses'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; import Unfold from '../internal/svg-icons/Unfold'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import { SelectOwnProps, SelectStaticProps, SelectOwnerState, SelectTypeMap } from './SelectProps'; import selectClasses, { getSelectUtilityClass } from './selectClasses'; import { ListOwnerState } from '../List'; @@ -292,7 +292,7 @@ const Select = React.forwardRef(function Select( action, autoFocus, children, - componentsProps = {}, + slotProps = {}, defaultValue, defaultListboxOpen = false, disabled: disabledExternalProp, @@ -311,7 +311,7 @@ const Select = React.forwardRef(function Select( startDecorator, endDecorator, indicator = , - // props to forward to the button (all handlers should go through componentsProps.button) + // props to forward to the button (all handlers should go through slotProps.button) 'aria-describedby': ariaDescribedby, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, @@ -437,8 +437,11 @@ const Select = React.forwardRef(function Select( return options.find((o) => value === o.value) ?? null; }, [options, value]); - const rootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref: handleRef, + className: classes.root, elementType: SelectRoot, + externalForwardedProps: other, getSlotProps: (handlers) => ({ onMouseDown: (event: React.MouseEvent) => { if ( @@ -453,19 +456,10 @@ const Select = React.forwardRef(function Select( handlers.onMouseDown?.(event); }, }), - externalSlotProps: componentsProps.root, - externalForwardedProps: other, - additionalProps: { - ref: handleRef, - }, ownerState, - className: classes.root, }); - const buttonProps = useSlotProps({ - elementType: SelectButton, - getSlotProps: getButtonProps, - externalSlotProps: componentsProps.button, + const [SlotButton, buttonProps] = useSlot('button', { additionalProps: { 'aria-describedby': ariaDescribedby ?? formControl?.['aria-describedby'], 'aria-label': ariaLabel, @@ -473,14 +467,15 @@ const Select = React.forwardRef(function Select( id: id ?? formControl?.htmlFor, name, }, - ownerState, className: classes.button, + elementType: SelectButton, + externalForwardedProps: other, + getSlotProps: getButtonProps, + ownerState, }); const resolveListboxProps = - typeof componentsProps.listbox === 'function' - ? componentsProps.listbox(ownerState) - : componentsProps.listbox; + typeof slotProps.listbox === 'function' ? slotProps.listbox(ownerState) : slotProps.listbox; // cache the modifiers to prevent Popper from being recreated when React rerenders menu. const cachedModifiers = React.useMemo( () => [ @@ -505,46 +500,46 @@ const Select = React.forwardRef(function Select( [resolveListboxProps?.modifiers], ); - const { component: listboxComponent, ...listboxProps } = useSlotProps({ - elementType: SelectListbox, - getSlotProps: getListboxProps, - externalSlotProps: componentsProps.listbox, + const [SlotListbox, listboxProps] = useSlot('listbox', { additionalProps: { - ref: listboxRef, anchorEl, disablePortal: true, open: listboxOpen, placement: 'bottom' as const, modifiers: cachedModifiers, + component: SelectListbox, }, + className: classes.listbox, + elementType: PopperUnstyled, + externalForwardedProps: other, + getSlotProps: getListboxProps, ownerState: { ...ownerState, nesting: false, row: false, wrap: false, } as SelectOwnerState & ListOwnerState, - className: classes.listbox, }); - const startDecoratorProps = useSlotProps({ + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, elementType: SelectStartDecorator, - externalSlotProps: componentsProps.startDecorator, + externalForwardedProps: other, ownerState, - className: classes.startDecorator, }); - const endDecoratorProps = useSlotProps({ + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { + className: classes.startDecorator, elementType: SelectEndDecorator, - externalSlotProps: componentsProps.endDecorator, + externalForwardedProps: other, ownerState, - className: classes.endDecorator, }); - const indicatorProps = useSlotProps({ + const [SlotIndicator, indicatorProps] = useSlot('indicator', { + className: classes.indicator, elementType: SelectIndicator, - externalSlotProps: componentsProps.indicator, + externalForwardedProps: other, ownerState, - className: classes.indicator, }); const context = React.useMemo( @@ -559,28 +554,26 @@ const Select = React.forwardRef(function Select( return ( - + {startDecorator && ( - {startDecorator} + {startDecorator} )} - + {selectedOption ? renderValue(selectedOption) : placeholder} - - {endDecorator && ( - {endDecorator} - )} + + {endDecorator && {endDecorator}} - {indicator && {indicator}} - + {indicator && {indicator}} + {anchorEl && ( // @ts-ignore internal logic: `listboxComponent` should not replace `SelectListbox`. - + {/* for building grouped options */} {children} - + )} {name && } @@ -635,18 +628,6 @@ Select.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - button: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - indicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - listbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * The default selected value. Use when the component is not controlled. */ @@ -696,6 +677,29 @@ Select.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + button: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + indicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + listbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + button: PropTypes.elementType, + endDecorator: PropTypes.elementType, + indicator: PropTypes.elementType, + listbox: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + }), /** * Leading adornment for the select. */ diff --git a/packages/mui-joy/src/Select/SelectProps.ts b/packages/mui-joy/src/Select/SelectProps.ts index 37b66df1c4005d..d035379c5d7f5d 100644 --- a/packages/mui-joy/src/Select/SelectProps.ts +++ b/packages/mui-joy/src/Select/SelectProps.ts @@ -16,17 +16,35 @@ export type SelectSlot = | 'listbox'; export interface SelectPropsVariantOverrides {} - export interface SelectPropsColorOverrides {} - export interface SelectPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, SelectOwnerState>; - button?: SlotComponentProps<'button', { sx?: SxProps }, SelectOwnerState>; - startDecorator?: SlotComponentProps<'span', { sx?: SxProps }, SelectOwnerState>; - endDecorator?: SlotComponentProps<'span', { sx?: SxProps }, SelectOwnerState>; - indicator?: SlotComponentProps<'span', { sx?: SxProps }, SelectOwnerState>; + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + SelectOwnerState + >; + button?: SlotComponentProps< + 'button', + { component?: React.ElementType; sx?: SxProps }, + SelectOwnerState + >; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SelectOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SelectOwnerState + >; + indicator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SelectOwnerState + >; listbox?: SlotComponentProps< 'ul', Omit & { @@ -49,11 +67,22 @@ export interface SelectStaticProps extends SelectUnstyledCommonProps { * @default 'primary' */ color?: OverridableStringUnion; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + button?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + indicator?: React.ElementType; + listbox?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * If `true`, the component is disabled. * @default false diff --git a/packages/mui-joy/src/Sheet/Sheet.tsx b/packages/mui-joy/src/Sheet/Sheet.tsx index 7cdc3737c3692e..d3d8203a041570 100644 --- a/packages/mui-joy/src/Sheet/Sheet.tsx +++ b/packages/mui-joy/src/Sheet/Sheet.tsx @@ -5,6 +5,7 @@ import clsx from 'clsx'; import PropTypes from 'prop-types'; import * as React from 'react'; import { useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { resolveSxValue } from '../styles/styleUtils'; import { getSheetUtilityClass } from './sheetClasses'; @@ -81,15 +82,15 @@ const Sheet = React.forwardRef(function Sheet(inProps, ref) { const classes = useUtilityClasses(ownerState); - const result = ( - - ); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: clsx(classes.root, className), + elementType: SheetRoot, + externalForwardedProps: { ...other, component }, + ownerState, + }); + + const result = ; if (invertedColors) { return {result}; diff --git a/packages/mui-joy/src/Switch/Switch.test.js b/packages/mui-joy/src/Switch/Switch.test.js index 03871f77c08cc3..fe52fd80d37875 100644 --- a/packages/mui-joy/src/Switch/Switch.test.js +++ b/packages/mui-joy/src/Switch/Switch.test.js @@ -24,11 +24,11 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'classesRoot'], })); - it('should pass `componentsProps` down to slots', () => { + it('should pass `slotProps` down to slots', () => { const { container } = render( + {startDecorator && ( - + {typeof startDecorator === 'function' ? startDecorator(ownerState) : startDecorator} - + )} - + {/* @ts-ignore */} {trackProps?.children} - - - - - + + + + + {endDecorator && ( - + {typeof endDecorator === 'function' ? endDecorator(ownerState) : endDecorator} - + )} - + ); }) as OverridableComponent; @@ -403,19 +399,6 @@ Switch.propTypes /* remove-proptypes */ = { * Either a string to use a HTML element or a component. */ component: PropTypes.elementType, - /** - * The props used for each slot inside the component. - * @default {} - */ - componentsProps: PropTypes.shape({ - action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - thumb: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - track: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - }), /** * The default checked state. Use when the component is not controlled. */ @@ -471,6 +454,31 @@ Switch.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * The props used for each slot inside the component. + * @default {} + */ + slotProps: PropTypes.shape({ + action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + thumb: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + track: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * Replace the default slots. + */ + slots: PropTypes.shape({ + action: PropTypes.elementType, + endDecorator: PropTypes.elementType, + input: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + thumb: PropTypes.elementType, + track: PropTypes.elementType, + }), /** * The element that appears at the end of the switch. */ diff --git a/packages/mui-joy/src/Switch/SwitchProps.ts b/packages/mui-joy/src/Switch/SwitchProps.ts index a01eec6d904ddc..517a31035a69f3 100644 --- a/packages/mui-joy/src/Switch/SwitchProps.ts +++ b/packages/mui-joy/src/Switch/SwitchProps.ts @@ -4,22 +4,55 @@ import { SlotComponentProps } from '@mui/base/utils'; import { UseSwitchParameters } from '@mui/base/SwitchUnstyled'; import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; -export type SwitchSlot = 'root' | 'action' | 'input' | 'track' | 'thumb'; +export type SwitchSlot = + | 'root' + | 'action' + | 'input' + | 'track' + | 'thumb' + | 'startDecorator' + | 'endDecorator'; export interface SwitchPropsVariantOverrides {} - export interface SwitchPropsColorOverrides {} - export interface SwitchPropsSizeOverrides {} interface ComponentsProps { - root?: SlotComponentProps<'div', { sx?: SxProps }, SwitchOwnerState>; - thumb?: SlotComponentProps<'span', { sx?: SxProps }, SwitchOwnerState>; - action?: SlotComponentProps<'div', { sx?: SxProps }, SwitchOwnerState>; - input?: SlotComponentProps<'button', { sx?: SxProps }, SwitchOwnerState>; - track?: SlotComponentProps<'span', { sx?: SxProps }, SwitchOwnerState>; - startDecorator?: SlotComponentProps<'span', { sx?: SxProps }, SwitchOwnerState>; - endDecorator?: SlotComponentProps<'span', { sx?: SxProps }, SwitchOwnerState>; + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + SwitchOwnerState + >; + thumb?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SwitchOwnerState + >; + action?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + SwitchOwnerState + >; + input?: SlotComponentProps< + 'button', + { component?: React.ElementType; sx?: SxProps }, + SwitchOwnerState + >; + track?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SwitchOwnerState + >; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SwitchOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + SwitchOwnerState + >; } export interface SwitchTypeMap

    { @@ -30,11 +63,23 @@ export interface SwitchTypeMap

    { * @default 'neutral' */ color?: OverridableStringUnion; + /** + * Replace the default slots. + */ + slots?: { + root?: React.ElementType; + action?: React.ElementType; + thumb?: React.ElementType; + track?: React.ElementType; + input?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + }; /** * The props used for each slot inside the component. * @default {} */ - componentsProps?: ComponentsProps; + slotProps?: ComponentsProps; /** * The element that appears at the end of the switch. */ diff --git a/packages/mui-joy/src/Tab/Tab.tsx b/packages/mui-joy/src/Tab/Tab.tsx index a5c30a0e8f7d24..240a2e9d2f2b99 100644 --- a/packages/mui-joy/src/Tab/Tab.tsx +++ b/packages/mui-joy/src/Tab/Tab.tsx @@ -4,9 +4,9 @@ import { OverridableComponent } from '@mui/types'; import { unstable_capitalize as capitalize, unstable_useForkRef as useForkRef } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { useTab } from '@mui/base/TabUnstyled'; -import { useSlotProps } from '@mui/base/utils'; import { StyledListItemButton } from '../ListItemButton/ListItemButton'; import { useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { getTabUtilityClass } from './tabClasses'; import { TabOwnerState, TabTypeMap } from './TabProps'; @@ -109,23 +109,19 @@ const Tab = React.forwardRef(function Tab(inProps, ref) { const classes = useUtilityClasses(ownerState); - const tabRootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: TabRoot, + externalForwardedProps: { ...other, component }, getSlotProps: getRootProps, - externalSlotProps: {}, - externalForwardedProps: other, - additionalProps: { - ref, - as: component, - }, ownerState, - className: classes.root, }); return ( {/* @ts-ignore ListItemButton base is div which conflict with TabProps 'button' */} - {children} + {children} ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/Tab/TabProps.ts b/packages/mui-joy/src/Tab/TabProps.ts index 5c61bec704e315..f8baf034183b6a 100644 --- a/packages/mui-joy/src/Tab/TabProps.ts +++ b/packages/mui-joy/src/Tab/TabProps.ts @@ -5,7 +5,6 @@ import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; export type TabSlot = 'root'; export interface TabPropsColorOverrides {} - export interface TabPropsVariantOverrides {} export interface TabTypeMap

    { diff --git a/packages/mui-joy/src/TabList/TabList.tsx b/packages/mui-joy/src/TabList/TabList.tsx index 810c97ac88260a..9456a31c4d7714 100644 --- a/packages/mui-joy/src/TabList/TabList.tsx +++ b/packages/mui-joy/src/TabList/TabList.tsx @@ -4,8 +4,8 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; import { useTabsList } from '@mui/base/TabsListUnstyled'; -import { useSlotProps } from '@mui/base/utils'; import { useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import styled from '../styles/styled'; import { StyledList } from '../List/List'; import ListProvider, { scopedVariables } from '../List/ListProvider'; @@ -77,27 +77,24 @@ const TabList = React.forwardRef(function TabList(inProps, ref) { const classes = useUtilityClasses(ownerState); - const tabsListRootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: TabListRoot, + externalForwardedProps: { ...other, component }, getSlotProps: getRootProps, - externalSlotProps: {}, - externalForwardedProps: other, - additionalProps: { - as: component, - }, ownerState, - className: classes.root, }); const processedChildren = processChildren(); return ( // @ts-ignore conflicted ref types - + {processedChildren} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/TabPanel/TabPanel.tsx b/packages/mui-joy/src/TabPanel/TabPanel.tsx index ea89caece2a300..fa6e199f5e58dc 100644 --- a/packages/mui-joy/src/TabPanel/TabPanel.tsx +++ b/packages/mui-joy/src/TabPanel/TabPanel.tsx @@ -5,8 +5,8 @@ import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; import { useTabContext } from '@mui/base/TabsUnstyled'; import { useTabPanel } from '@mui/base/TabPanelUnstyled'; -import { useSlotProps } from '@mui/base/utils'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import SizeTabsContext from '../Tabs/SizeTabsContext'; import { getTabPanelUtilityClass } from './tabPanelClasses'; import { TabPanelOwnerState, TabPanelTypeMap } from './TabPanelProps'; @@ -61,7 +61,7 @@ const TabPanel = React.forwardRef(function TabPanel(inProps, ref) { const size = sizeProp ?? tabsSize; - const ownerState = { + const ownerState: TabPanelOwnerState = { ...props, orientation, hidden, @@ -70,21 +70,19 @@ const TabPanel = React.forwardRef(function TabPanel(inProps, ref) { const classes = useUtilityClasses(ownerState); - const tabPanelRootProps = useSlotProps({ - elementType: TabPanelRoot, - getSlotProps: getRootProps, - externalSlotProps: {}, - externalForwardedProps: other, + const [SlotRoot, rootProps] = useSlot('root', { additionalProps: { role: 'tabpanel', - ref, - as: component, }, - ownerState, + ref, className: classes.root, + elementType: TabPanelRoot, + externalForwardedProps: { ...other, component }, + getSlotProps: getRootProps, + ownerState, }); - return {!hidden && children}; + return {!hidden && children}; }) as OverridableComponent; TabPanel.propTypes /* remove-proptypes */ = { diff --git a/packages/mui-joy/src/TabPanel/TabPanelProps.ts b/packages/mui-joy/src/TabPanel/TabPanelProps.ts index 9c553c227689df..a7694f0848d91c 100644 --- a/packages/mui-joy/src/TabPanel/TabPanelProps.ts +++ b/packages/mui-joy/src/TabPanel/TabPanelProps.ts @@ -9,7 +9,7 @@ export interface TabPanelPropsSizeOverrides {} export interface TabPanelTypeMap

    { props: P & - Omit & { + Omit & { /** * The size of the component. */ diff --git a/packages/mui-joy/src/Tabs/Tabs.tsx b/packages/mui-joy/src/Tabs/Tabs.tsx index 11b15e8d22e18d..4e2884d669c7c5 100644 --- a/packages/mui-joy/src/Tabs/Tabs.tsx +++ b/packages/mui-joy/src/Tabs/Tabs.tsx @@ -4,9 +4,9 @@ import { unstable_capitalize as capitalize } from '@mui/utils'; import { unstable_composeClasses as composeClasses } from '@mui/base'; import { OverridableComponent } from '@mui/types'; import { useTabs, TabsContext } from '@mui/base/TabsUnstyled'; -import { useSlotProps } from '@mui/base/utils'; import { SheetRoot } from '../Sheet/Sheet'; import { styled, useThemeProps } from '../styles'; +import useSlot from '../utils/useSlot'; import SizeTabsContext from './SizeTabsContext'; import { getTabsUtilityClass } from './tabsClasses'; import { TabsOwnerState, TabsTypeMap } from './TabsProps'; @@ -82,25 +82,21 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) { const classes = useUtilityClasses(ownerState); - const tabsRootProps = useSlotProps({ + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: classes.root, elementType: TabsRoot, - externalSlotProps: {}, - externalForwardedProps: other, - additionalProps: { - ref, - as: component, - }, + externalForwardedProps: { ...other, component }, ownerState, - className: classes.root, }); return ( // @ts-ignore `defaultValue` between HTMLDiv and TabsProps is conflicted. - + {children} - + ); }) as OverridableComponent; diff --git a/packages/mui-joy/src/Tabs/TabsProps.ts b/packages/mui-joy/src/Tabs/TabsProps.ts index 62ed104549b5ad..5ff21ba0a7f391 100644 --- a/packages/mui-joy/src/Tabs/TabsProps.ts +++ b/packages/mui-joy/src/Tabs/TabsProps.ts @@ -13,7 +13,7 @@ export interface TabsPropsSizeOverrides {} export interface TabsTypeMap

    { props: P & - Omit & { + Omit & { /** * The color of the component. It supports those theme colors that make sense for this component. * @default 'neutral' diff --git a/packages/mui-joy/src/TextField/TextField.test.js b/packages/mui-joy/src/TextField/TextField.test.js index 072fe0a62a6508..c31933beecd46d 100644 --- a/packages/mui-joy/src/TextField/TextField.test.js +++ b/packages/mui-joy/src/TextField/TextField.test.js @@ -126,7 +126,7 @@ describe('Joy ', () => { const handleKeyDown = spy(); const { getByRole } = render( + {label && ( )} - + ); }) as OverridableComponent; @@ -219,15 +222,6 @@ TextField.propTypes /* remove-proptypes */ = { Label: PropTypes.elementType, Root: PropTypes.elementType, }), - /** - * @ignore - */ - componentsProps: PropTypes.shape({ - helperText: PropTypes.object, - input: PropTypes.object, - label: PropTypes.object, - root: PropTypes.object, - }), /** * @ignore */ @@ -299,6 +293,15 @@ TextField.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['sm', 'md', 'lg']), PropTypes.string, ]), + /** + * @ignore + */ + slotProps: PropTypes.shape({ + helperText: PropTypes.object, + input: PropTypes.object, + label: PropTypes.object, + root: PropTypes.object, + }), /** * Leading adornment for this input. */ diff --git a/packages/mui-joy/src/TextField/TextFieldProps.ts b/packages/mui-joy/src/TextField/TextFieldProps.ts index 3f7c5dccc0e551..aa8da7f7145163 100644 --- a/packages/mui-joy/src/TextField/TextFieldProps.ts +++ b/packages/mui-joy/src/TextField/TextFieldProps.ts @@ -36,7 +36,7 @@ export interface TextFieldTypeMap

    { Input?: React.ElementType; HelperText?: React.ElementType; }; - componentsProps?: { + slotProps?: { root?: React.ComponentPropsWithRef<'div'>; label?: FormLabelProps; input?: Omit; diff --git a/packages/mui-joy/src/Textarea/Textarea.test.tsx b/packages/mui-joy/src/Textarea/Textarea.test.tsx index 391ed9f4b8a583..08c8e30c2b7799 100644 --- a/packages/mui-joy/src/Textarea/Textarea.test.tsx +++ b/packages/mui-joy/src/Textarea/Textarea.test.tsx @@ -38,7 +38,7 @@ describe('Joy