diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index f5adc03f59f9..60dcf5d7b2a5 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -6433,6 +6433,9 @@ Map { "className": Object { "type": "string", }, + "decorator": Object { + "type": "node", + }, "defaultChecked": Object { "type": "bool", }, @@ -6470,9 +6473,7 @@ Map { "required": Object { "type": "bool", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "value": Object { "args": Array [ Array [ @@ -6498,6 +6499,9 @@ Map { "className": Object { "type": "string", }, + "decorator": Object { + "type": "node", + }, "defaultSelected": Object { "args": Array [ Array [ @@ -6557,9 +6561,7 @@ Map { "required": Object { "type": "bool", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "valueSelected": Object { "args": Array [ Array [ diff --git a/packages/react/src/components/RadioButton/RadioButton.stories.js b/packages/react/src/components/RadioButton/RadioButton.stories.js index 052ee3f3544f..664b14843297 100644 --- a/packages/react/src/components/RadioButton/RadioButton.stories.js +++ b/packages/react/src/components/RadioButton/RadioButton.stories.js @@ -132,7 +132,7 @@ export const withAILabel = { render: () => (
` should be checked by default */ @@ -83,6 +89,7 @@ export interface RadioButtonProps onClick?: (evt: React.MouseEvent) => void; /** + * @deprecated please use decorator instead. * **Experimental**: Provide a `Slug` component to be rendered inside the `RadioButton` component */ slug?: ReactNode; @@ -102,6 +109,7 @@ const RadioButton = React.forwardRef( (props, ref) => { const { className, + decorator, disabled, hideLabel, id, @@ -137,17 +145,29 @@ const RadioButton = React.forwardRef( [`${prefix}--radio-button-wrapper--label-${labelPosition}`]: labelPosition !== 'right', [`${prefix}--radio-button-wrapper--slug`]: slug, + [`${prefix}--radio-button-wrapper--decorator`]: decorator, } ); const inputRef = useRef(null); - let normalizedSlug: React.ReactElement | undefined; - if (slug && React.isValidElement(slug)) { - const size = slug.props?.['kind'] === 'inline' ? 'md' : 'mini'; - normalizedSlug = React.cloneElement(slug as React.ReactElement, { - size, - }); + let normalizedDecorator = React.isValidElement(slug ?? decorator) + ? (slug ?? decorator) + : null; + if ( + normalizedDecorator && + normalizedDecorator['type']?.displayName === 'AILabel' + ) { + const size = + (normalizedDecorator as ReactElement).props?.['kind'] === 'inline' + ? 'md' + : 'mini'; + normalizedDecorator = React.cloneElement( + normalizedDecorator as React.ReactElement, + { + size, + } + ); } return ( @@ -169,7 +189,16 @@ const RadioButton = React.forwardRef( {labelText && ( {labelText} - {normalizedSlug} + {slug ? ( + normalizedDecorator + ) : decorator ? ( +
+ {normalizedDecorator} +
+ ) : ( + '' + )}
)} @@ -191,6 +220,11 @@ RadioButton.propTypes = { */ className: PropTypes.string, + /** + * **Experimental**: Provide a decorator component to be rendered inside the `RadioButton` component + */ + decorator: PropTypes.node, + /** * Specify whether the `` should be checked by default */ @@ -247,7 +281,10 @@ RadioButton.propTypes = { /** * **Experimental**: Provide a `Slug` component to be rendered inside the `RadioButton` component */ - slug: PropTypes.node, + slug: deprecate( + PropTypes.node, + 'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.' + ), /** * Specify the value of the `` diff --git a/packages/react/src/components/RadioButton/__tests__/RadioButton-test.js b/packages/react/src/components/RadioButton/__tests__/RadioButton-test.js index 0f228936ef62..489f60ccd718 100644 --- a/packages/react/src/components/RadioButton/__tests__/RadioButton-test.js +++ b/packages/react/src/components/RadioButton/__tests__/RadioButton-test.js @@ -136,7 +136,38 @@ describe('RadioButton', () => { expect(ref).toHaveBeenCalledWith(screen.getByRole('radio')); }); - it('should respect slug prop', () => { + it('should respect decorator prop', () => { + const { container } = render( + } + /> + ); + + expect(container.firstChild).toHaveClass( + `${prefix}--radio-button-wrapper--decorator` + ); + }); + + it('should update AILabel size', () => { + const { container } = render( + } + /> + ); + + expect(container.querySelector(`.${prefix}--ai-label__button`)).toHaveClass( + `${prefix}--ai-label__button--md` + ); + }); + + it('should respect the deprecated slug prop', () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); const { container } = render( { expect(container.firstChild).toHaveClass( `${prefix}--radio-button-wrapper--slug` ); + spy.mockRestore(); }); it('should set the "required" attribute on the by default', () => { diff --git a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js index 12330a76eccf..7c08617bbe0f 100644 --- a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js +++ b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js @@ -233,7 +233,21 @@ describe('RadioButtonGroup', () => { ); }); - it('should respect slug prop', () => { + it('should respect decorator prop', () => { + const { container } = render( + } name="test" legendText="test"> + + + + ); + + expect(container.firstChild.firstChild).toHaveClass( + `${prefix}--radio-button-group--decorator` + ); + }); + + it('should respect deprecated slug prop', () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); const { container } = render( } name="test" legendText="test"> @@ -244,6 +258,7 @@ describe('RadioButtonGroup', () => { expect(container.firstChild.firstChild).toHaveClass( `${prefix}--radio-button-group--slug` ); + spy.mockRestore(); }); it('should call `onChange` when the value of the group changes', async () => { diff --git a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.tsx b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.tsx index 3f5d0a65ab2b..8f85542c4bf3 100644 --- a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.tsx @@ -18,6 +18,7 @@ import type { RadioButtonProps } from '../RadioButton'; import { Legend } from '../Text'; import { usePrefix } from '../../internal/usePrefix'; import { WarningFilled, WarningAltFilled } from '@carbon/icons-react'; +import deprecate from '../../prop-types/deprecate'; import mergeRefs from '../../tools/mergeRefs'; import { useId } from '../../internal/useId'; @@ -40,6 +41,11 @@ export interface RadioButtonGroupProps */ className?: string; + /** + * **Experimental**: Provide a decorator component to be rendered inside the `RadioButtonGroup` component + */ + decorator?: ReactNode; + /** * Specify the `` to be selected by default */ @@ -102,6 +108,7 @@ export interface RadioButtonGroupProps readOnly?: boolean; /** + * @deprecated please use decorator instead. * **Experimental**: Provide a `Slug` component to be rendered inside the `RadioButtonGroup` component */ slug?: ReactNode; @@ -132,6 +139,7 @@ const RadioButtonGroup = React.forwardRef( const { children, className, + decorator, defaultSelected, disabled, helperText, @@ -220,6 +228,7 @@ const RadioButtonGroup = React.forwardRef( [`${prefix}--radio-button-group--invalid`]: !readOnly && invalid, [`${prefix}--radio-button-group--warning`]: showWarning, [`${prefix}--radio-button-group--slug`]: slug, + [`${prefix}--radio-button-group--decorator`]: decorator, }); const helperClasses = classNames(`${prefix}--form__helper-text`, { @@ -238,13 +247,21 @@ const RadioButtonGroup = React.forwardRef( const divRef = useRef(null); - // Slug is always size `mini` - let normalizedSlug: ReactElement | undefined; - if (slug && slug['type']?.displayName === 'AILabel') { - normalizedSlug = React.cloneElement(slug as React.ReactElement, { - size: 'mini', - kind: 'default', - }); + // AILabel is always size `mini` + let normalizedDecorator = React.isValidElement(slug ?? decorator) + ? (slug ?? decorator) + : null; + if ( + normalizedDecorator && + normalizedDecorator['type']?.displayName === 'AILabel' + ) { + normalizedDecorator = React.cloneElement( + normalizedDecorator as React.ReactElement, + { + size: 'mini', + kind: 'default', + } + ); } return ( @@ -258,7 +275,16 @@ const RadioButtonGroup = React.forwardRef( {legendText && ( {legendText} - {normalizedSlug} + {slug ? ( + normalizedDecorator + ) : decorator ? ( +
+ {normalizedDecorator} +
+ ) : ( + '' + )}
)} {getRadioButtons()} @@ -298,6 +324,11 @@ RadioButtonGroup.propTypes = { */ className: PropTypes.string, + /** + * **Experimental**: Provide a decorator component to be rendered inside the `RadioButtonGroup` component + */ + decorator: PropTypes.node, + /** * Specify the `` to be selected by default */ @@ -363,7 +394,10 @@ RadioButtonGroup.propTypes = { /** * **Experimental**: Provide a `Slug` component to be rendered inside the `RadioButtonGroup` component */ - slug: PropTypes.node, + slug: deprecate( + PropTypes.node, + 'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.' + ), /** * Specify the value that is currently selected in the group diff --git a/packages/styles/scss/components/radio-button/_radio-button.scss b/packages/styles/scss/components/radio-button/_radio-button.scss index a3f542f29f84..8ecb9eadb497 100644 --- a/packages/styles/scss/components/radio-button/_radio-button.scss +++ b/packages/styles/scss/components/radio-button/_radio-button.scss @@ -289,12 +289,23 @@ $radio-border-width: 1px !default; } // AILabel styles + .#{$prefix}--radio-button-group--decorator legend.#{$prefix}--label, + .#{$prefix}--radio-button-wrapper--decorator + .#{$prefix}--radio-button__label-text, .#{$prefix}--radio-button-group--slug legend.#{$prefix}--label, .#{$prefix}--radio-button-wrapper--slug .#{$prefix}--radio-button__label-text { display: flex; } + .#{$prefix}--radio-button-group--decorator + legend.#{$prefix}--label + .#{$prefix}--radio-button-group-inner--decorator + > *, + .#{$prefix}--radio-button-wrapper--decorator + .#{$prefix}--radio-button__label-text + .#{$prefix}--radio-button-wrapper-inner--decorator + > *, .#{$prefix}--radio-button-group--slug legend.#{$prefix}--label .#{$prefix}--ai-label, @@ -310,6 +321,9 @@ $radio-border-width: 1px !default; margin-inline-start: $spacing-03; } + .#{$prefix}--radio-button-wrapper--decorator + .#{$prefix}--radio-button__label-text + .#{$prefix}--ai-label__button--inline, .#{$prefix}--radio-button-wrapper--slug .#{$prefix}--radio-button__label-text .#{$prefix}--ai-label__button--inline,