Skip to content

Commit

Permalink
[material-ui][Radio] Add slots and slotProps (mui#44972)
Browse files Browse the repository at this point in the history
Co-authored-by: siriwatknp <siriwatkunaporn@gmail.com>
  • Loading branch information
sai6855 and siriwatknp authored Feb 5, 2025
1 parent 14a8ed2 commit 39b1f38
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 26 deletions.
31 changes: 25 additions & 6 deletions docs/pages/material-ui/api/radio.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@
},
"default": "'medium'"
},
"slotProps": {
"type": {
"name": "shape",
"description": "{ input?: func<br>&#124;&nbsp;object, root?: func<br>&#124;&nbsp;object }"
},
"default": "{}"
},
"slots": {
"type": { "name": "shape", "description": "{ input?: elementType, root?: elementType }" },
"default": "{}"
},
"sx": {
"type": {
"name": "union",
Expand All @@ -51,6 +62,20 @@
},
"name": "Radio",
"imports": ["import Radio from '@mui/material/Radio';", "import { Radio } from '@mui/material';"],
"slots": [
{
"name": "root",
"description": "The component that renders the root slot.",
"default": "SwitchBase",
"class": "MuiRadio-root"
},
{
"name": "input",
"description": "The component that renders the input slot.",
"default": "SwitchBase's input",
"class": null
}
],
"classes": [
{
"key": "checked",
Expand All @@ -76,12 +101,6 @@
"description": "State class applied to the root element if `disabled={true}`.",
"isGlobal": true
},
{
"key": "root",
"className": "MuiRadio-root",
"description": "Styles applied to the root element.",
"isGlobal": false
},
{
"key": "sizeSmall",
"className": "MuiRadio-sizeSmall",
Expand Down
7 changes: 6 additions & 1 deletion docs/translations/api-docs/radio/radio.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"size": {
"description": "The size of the component. <code>small</code> is equivalent to the dense radio styling."
},
"slotProps": { "description": "The props used for each slot inside." },
"slots": { "description": "The components used for each slot inside." },
"sx": {
"description": "The system prop that allows defining system overrides as well as additional CSS styles."
},
Expand All @@ -54,11 +56,14 @@
"nodeName": "the root element",
"conditions": "<code>disabled={true}</code>"
},
"root": { "description": "Styles applied to the root element." },
"sizeSmall": {
"description": "Styles applied to {{nodeName}} if {{conditions}}.",
"nodeName": "the root element",
"conditions": "<code>size=\"small\"</code>"
}
},
"slotDescriptions": {
"input": "The component that renders the input slot.",
"root": "The component that renders the root slot."
}
}
46 changes: 45 additions & 1 deletion packages/mui-material/src/Radio/Radio.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,57 @@ import * as React from 'react';
import { SxProps } from '@mui/system';
import { OverridableStringUnion } from '@mui/types';
import { InternalStandardProps as StandardProps, Theme } from '..';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
import { SwitchBaseProps } from '../internal/SwitchBase';
import { RadioClasses } from './radioClasses';

export interface RadioPropsSizeOverrides {}

export interface RadioPropsColorOverrides {}

export interface RadioRootSlotPropsOverrides {}

export interface RadioInputSlotPropsOverrides {}

export interface RadioSlots {
/**
* The component that renders the root slot.
* @default SwitchBase
*/
root: React.ElementType;
/**
* The component that renders the input slot.
* @default SwitchBase's input
*/
input: React.ElementType;
}

export type RadioSlotsAndSlotProps = CreateSlotsAndSlotProps<
RadioSlots,
{
/**
* Props forwarded to the root slot.
* By default, the avaible props are based on the span element.
*/
root: SlotProps<
React.ElementType<SwitchBaseProps>,
RadioRootSlotPropsOverrides,
RadioOwnerState
>;
/**
* Props forwarded to the input slot.
* By default, the avaible props are based on the input element.
*/
input: SlotProps<'input', RadioInputSlotPropsOverrides, RadioOwnerState>;
}
>;

export interface RadioProps
extends StandardProps<SwitchBaseProps, 'checkedIcon' | 'color' | 'icon' | 'type'> {
extends StandardProps<
SwitchBaseProps,
'checkedIcon' | 'color' | 'icon' | 'type' | 'slots' | 'slotProps'
>,
RadioSlotsAndSlotProps {
/**
* The icon to display when the component is checked.
* @default <RadioButtonIcon checked />
Expand Down Expand Up @@ -51,6 +93,8 @@ export interface RadioProps
sx?: SxProps<Theme>;
}

export interface RadioOwnerState extends Omit<RadioProps, 'slots' | 'slotProps'> {}

/**
*
* Demos:
Expand Down
74 changes: 56 additions & 18 deletions packages/mui-material/src/Radio/Radio.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import rootShouldForwardProp from '../styles/rootShouldForwardProp';
import { styled } from '../zero-styled';
import memoTheme from '../utils/memoTheme';
import createSimplePaletteValueFilter from '../utils/createSimplePaletteValueFilter';
import useSlot from '../utils/useSlot';
import { useDefaultProps } from '../DefaultPropsProvider';

const useUtilityClasses = (ownerState) => {
Expand Down Expand Up @@ -124,6 +125,8 @@ const Radio = React.forwardRef(function Radio(inProps, ref) {
className,
disabled: disabledProp,
disableRipple = false,
slots = {},
slotProps = {},
...other
} = props;

Expand Down Expand Up @@ -163,24 +166,43 @@ const Radio = React.forwardRef(function Radio(inProps, ref) {
}
}

return (
<RadioRoot
type="radio"
icon={React.cloneElement(icon, { fontSize: defaultIcon.props.fontSize ?? size })}
checkedIcon={React.cloneElement(checkedIcon, {
fontSize: defaultCheckedIcon.props.fontSize ?? size,
})}
disabled={disabled}
ownerState={ownerState}
classes={classes}
name={name}
checked={checked}
onChange={onChange}
ref={ref}
className={clsx(classes.root, className)}
{...other}
/>
);
const [RootSlot, rootSlotProps] = useSlot('root', {
ref,
elementType: RadioRoot,
className: clsx(classes.root, className),
shouldForwardComponentProp: true,
externalForwardedProps: {
slots,
slotProps,
...other,
},
getSlotProps: (handlers) => ({
...handlers,
onChange: (event, ...args) => {
handlers.onChange?.(event, ...args);
onChange(event, ...args);
},
}),
ownerState,
additionalProps: {
type: 'radio',
icon: React.cloneElement(icon, { fontSize: icon.props.fontSize ?? size }),
checkedIcon: React.cloneElement(checkedIcon, {
fontSize: checkedIcon.props.fontSize ?? size,
}),
disabled,
name,
checked,
slots,
slotProps: {
// Do not forward `slotProps.root` again because it's already handled by the `RootSlot` in this file.
input:
typeof slotProps.input === 'function' ? slotProps.input(ownerState) : slotProps.input,
},
},
});

return <RootSlot {...rootSlotProps} classes={classes} />;
});

Radio.propTypes /* remove-proptypes */ = {
Expand Down Expand Up @@ -269,6 +291,22 @@ Radio.propTypes /* remove-proptypes */ = {
PropTypes.oneOf(['medium', 'small']),
PropTypes.string,
]),
/**
* 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.
*/
Expand Down
33 changes: 33 additions & 0 deletions packages/mui-material/src/Radio/Radio.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import Radio from '@mui/material/Radio';
import { expectType } from '@mui/types';

// deprecated props
<Radio
inputProps={{
'aria-label': 'Radio',
onChange: () => {},
}}
inputRef={null}
/>;

<Radio
slots={{
root: 'div',
input: 'input',
}}
slotProps={{
root: {
className: 'root',
disableRipple: true,
hidden: true,
},
input: {
ref: (elm) => {
expectType<HTMLInputElement | null, typeof elm>(elm);
},
'aria-label': 'Radio',
className: 'input',
},
}}
/>;
14 changes: 14 additions & 0 deletions packages/mui-material/src/Radio/Radio.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,32 @@ import Radio, { radioClasses as classes } from '@mui/material/Radio';
import FormControl from '@mui/material/FormControl';
import ButtonBase from '@mui/material/ButtonBase';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import switchBaseClasses from '../internal/switchBaseClasses';
import describeConformance from '../../test/describeConformance';

describe('<Radio />', () => {
const { render } = createRenderer();

function CustomRoot({ checkedIcon, ownerState, disableRipple, slots, slotProps, ...props }) {
return <div {...props} />;
}

describeConformance(<Radio />, () => ({
classes,
inheritComponent: ButtonBase,
render,
muiName: 'MuiRadio',
testVariantProps: { color: 'secondary' },
refInstanceof: window.HTMLSpanElement,
slots: {
root: {
expectedClassName: classes.root,
testWithElement: CustomRoot,
},
input: {
expectedClassName: switchBaseClasses.input,
},
},
skip: ['componentProp', 'componentsProp'],
}));

Expand Down

0 comments on commit 39b1f38

Please sign in to comment.