From a6f1c94df6dffbaf1a4200bc7fb889e1afba1a7a Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Mon, 27 Jan 2025 13:08:57 +0700 Subject: [PATCH] [material-ui][SwitchBase] Deprecate `inputProps` and complete slots, slotProps (#45076) --- .../mui-material/src/internal/SwitchBase.d.ts | 34 ++++- .../mui-material/src/internal/SwitchBase.js | 123 +++++++++++++----- .../src/internal/SwitchBase.test.js | 14 ++ 3 files changed, 135 insertions(+), 36 deletions(-) diff --git a/packages/mui-material/src/internal/SwitchBase.d.ts b/packages/mui-material/src/internal/SwitchBase.d.ts index b175d4f629933b..a66a7792fa1b4a 100644 --- a/packages/mui-material/src/internal/SwitchBase.d.ts +++ b/packages/mui-material/src/internal/SwitchBase.d.ts @@ -2,9 +2,39 @@ import * as React from 'react'; import { InternalStandardProps as StandardProps } from '..'; import { ButtonBaseProps } from '../ButtonBase'; import { SwitchBaseClasses } from './switchBaseClasses'; +import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; + +interface SwitchBaseSlots { + /** + * The component that renders the root slot. + * @default ButtonBase + */ + root: React.ElementType; + /** + * The component that renders the input slot. + * @default 'input' + */ + input: React.ElementType; +} + +type SwitchBaseSlotsAndSlotProps = CreateSlotsAndSlotProps< + SwitchBaseSlots, + { + /** + * Props forwarded to the root slot. + * By default, the avaible props are based on the [ButtonBase](https://mui.com/material-ui/api/button-base/#props) component. + */ + root: SlotProps, {}, SwitchBaseOwnerState>; + /** + * Props forwarded to the input slot. + */ + input: SlotProps<'input', {}, SwitchBaseOwnerState>; + } +>; export interface SwitchBaseProps - extends StandardProps { + extends StandardProps, + SwitchBaseSlotsAndSlotProps { autoFocus?: boolean; /** * If `true`, the component is checked. @@ -80,6 +110,8 @@ export interface SwitchBaseProps value?: unknown; } +export interface SwitchBaseOwnerState extends Omit {} + declare const SwitchBase: React.JSXElementConstructor; export default SwitchBase; diff --git a/packages/mui-material/src/internal/SwitchBase.js b/packages/mui-material/src/internal/SwitchBase.js index fe962172099870..d9468ded76f850 100644 --- a/packages/mui-material/src/internal/SwitchBase.js +++ b/packages/mui-material/src/internal/SwitchBase.js @@ -1,7 +1,6 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import clsx from 'clsx'; import refType from '@mui/utils/refType'; import composeClasses from '@mui/utils/composeClasses'; import capitalize from '../utils/capitalize'; @@ -11,6 +10,7 @@ import useControlled from '../utils/useControlled'; import useFormControl from '../FormControl/useFormControl'; import ButtonBase from '../ButtonBase'; import { getSwitchBaseUtilityClass } from './switchBaseClasses'; +import useSlot from '../utils/useSlot'; const useUtilityClasses = (ownerState) => { const { classes, checked, disabled, edge } = ownerState; @@ -81,7 +81,6 @@ const SwitchBase = React.forwardRef(function SwitchBase(props, ref) { autoFocus, checked: checkedProp, checkedIcon, - className, defaultChecked, disabled: disabledProp, disableFocusRipple = false, @@ -99,6 +98,8 @@ const SwitchBase = React.forwardRef(function SwitchBase(props, ref) { tabIndex, type, value, + slots = {}, + slotProps = {}, ...other } = props; const [checked, setCheckedState] = useControlled({ @@ -166,41 +167,77 @@ const SwitchBase = React.forwardRef(function SwitchBase(props, ref) { const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { + slots, + slotProps: { + input: inputProps, + ...slotProps, + }, + }; + + const [RootSlot, rootSlotProps] = useSlot('root', { + ref, + elementType: SwitchBaseRoot, + className: classes.root, + shouldForwardComponentProp: true, + externalForwardedProps: { + ...externalForwardedProps, + component: 'span', + ...other, + }, + getSlotProps: (handlers) => ({ + ...handlers, + onFocus: (event) => { + handlers.onFocus?.(event); + handleFocus(event); + }, + onBlur: (event) => { + handlers.onBlur?.(event); + handleBlur(event); + }, + }), + ownerState, + additionalProps: { + centerRipple: true, + focusRipple: !disableFocusRipple, + disabled, + role: undefined, + tabIndex: null, + }, + }); + + const [InputSlot, inputSlotProps] = useSlot('input', { + ref: inputRef, + elementType: SwitchBaseInput, + className: classes.input, + externalForwardedProps, + getSlotProps: (handlers) => ({ + onChange: (event) => { + handlers.onChange?.(event); + handleInputChange(event); + }, + }), + ownerState, + additionalProps: { + autoFocus, + checked: checkedProp, + defaultChecked, + disabled, + id: hasLabelFor ? id : undefined, + name, + readOnly, + required, + tabIndex, + type, + ...(type === 'checkbox' && value === undefined ? {} : { value }), + }, + }); + return ( - - + + {checked ? checkedIcon : icon} - + ); }); @@ -292,6 +329,22 @@ SwitchBase.propTypes = { * If `true`, the `input` element is required. */ required: PropTypes.bool, + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + input: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/internal/SwitchBase.test.js b/packages/mui-material/src/internal/SwitchBase.test.js index 7e177979efa1ba..25d7e1a0bd8482 100644 --- a/packages/mui-material/src/internal/SwitchBase.test.js +++ b/packages/mui-material/src/internal/SwitchBase.test.js @@ -12,6 +12,10 @@ import * as ripple from '../../test/ripple'; describe('', () => { const { render } = createRenderer(); + function CustomRoot({ centerRipple, focusRipple, ownerState, ...props }) { + return
; + } + describeConformance( , () => ({ @@ -21,6 +25,15 @@ describe('', () => { refInstanceof: window.HTMLSpanElement, testComponentPropWith: 'div', testVariantProps: { disabled: true }, + slots: { + root: { + expectedClassName: classes.root, + testWithElement: CustomRoot, + }, + input: { + expectedClassName: classes.input, + }, + }, skip: ['componentsProp', 'themeDefaultProps', 'themeStyleOverrides', 'themeVariants'], }), ); @@ -260,6 +273,7 @@ describe('', () => { describe('prop: inputProps', () => { it('should be able to add aria', () => { + // TODO: remove this test in v7 because `inputProps` is deprecated const { getByRole } = render(