From 633ab3c483dfacb9cad85bdf3555a30b631d05c0 Mon Sep 17 00:00:00 2001 From: Ariella Gilmore Date: Wed, 20 Nov 2024 11:04:13 -0800 Subject: [PATCH 01/12] feat(tag): new decorator prop (#18077) * feat(tag): new decorator prop * feat(tag-dismissible): add decorator * fix(tag): format and remove filter updates only add to dissmissible * fix(tag): format * chore(tag): remove extra stories --- .../__snapshots__/PublicAPI-test.js.snap | 14 ++--- .../src/components/Tag/DismissibleTag.tsx | 48 ++++++++++++++--- packages/react/src/components/Tag/Tag-test.js | 44 +++++++++++++++- .../react/src/components/Tag/Tag.stories.js | 29 ++++++----- packages/react/src/components/Tag/Tag.tsx | 51 +++++++++++++++---- packages/styles/scss/components/tag/_tag.scss | 8 +++ 6 files changed, 154 insertions(+), 40 deletions(-) diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 638c99b2d459..5aaf88a0a061 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -2879,6 +2879,9 @@ Map { "className": Object { "type": "string", }, + "decorator": Object { + "type": "node", + }, "disabled": Object { "type": "bool", }, @@ -2911,9 +2914,7 @@ Map { ], "type": "oneOf", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "tagTitle": Object { "type": "string", }, @@ -8442,6 +8443,9 @@ Map { "className": Object { "type": "string", }, + "decorator": Object { + "type": "node", + }, "disabled": Object { "type": "bool", }, @@ -8473,9 +8477,7 @@ Map { ], "type": "oneOf", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "title": [Function], "type": Object { "args": Array [ diff --git a/packages/react/src/components/Tag/DismissibleTag.tsx b/packages/react/src/components/Tag/DismissibleTag.tsx index eb68a9b6a41d..8e4baf20f1b1 100644 --- a/packages/react/src/components/Tag/DismissibleTag.tsx +++ b/packages/react/src/components/Tag/DismissibleTag.tsx @@ -11,6 +11,7 @@ import classNames from 'classnames'; import { useId } from '../../internal/useId'; import { usePrefix } from '../../internal/usePrefix'; import { PolymorphicProps } from '../../types/common'; +import deprecate from '../../prop-types/deprecate'; import Tag, { SIZES, TYPES } from './Tag'; import { Close } from '@carbon/icons-react'; import { Tooltip } from '../Tooltip'; @@ -23,6 +24,11 @@ export interface DismissibleTagBaseProps { */ className?: string; + /** + * **Experimental:** Provide a `decorator` component to be rendered inside the `DismissibleTag` component + */ + decorator?: ReactNode; + /** * Specify if the `DismissibleTag` is disabled */ @@ -51,6 +57,7 @@ export interface DismissibleTagBaseProps { size?: keyof typeof SIZES; /** + * @deprecated please use `decorator` instead. * **Experimental:** Provide a `Slug` component to be rendered inside the `DismissibleTag` component */ slug?: ReactNode; @@ -83,6 +90,7 @@ export type DismissibleTagProps = PolymorphicProps< const DismissibleTag = ({ className, + decorator, disabled, id, renderIcon, @@ -114,12 +122,20 @@ const DismissibleTag = ({ } }; - let normalizedSlug; - if (slug && slug['type']?.displayName === 'AILabel') { - normalizedSlug = React.cloneElement(slug as React.ReactElement, { - size: 'sm', - kind: 'inline', - }); + let normalizedDecorator = React.isValidElement(slug ?? decorator) + ? (slug ?? decorator) + : null; + if ( + normalizedDecorator && + normalizedDecorator['type']?.displayName === 'AILabel' + ) { + normalizedDecorator = React.cloneElement( + normalizedDecorator as React.ReactElement, + { + size: 'sm', + kind: 'inline', + } + ); } const tooltipClasses = classNames( @@ -149,7 +165,15 @@ const DismissibleTag = ({ className={`${prefix}--tag__label`}> {text} - {normalizedSlug} + {slug ? ( + normalizedDecorator + ) : decorator ? ( +
+ {normalizedDecorator} +
+ ) : ( + '' + )} { // requirement expect(accessibilityLabel).toEqual(expect.stringContaining('Close tag')); }); + + it('should respect decorator prop', () => { + render( + } + /> + ); + + expect( + screen.getByRole('button', { name: 'AI - Show information' }) + ).toBeInTheDocument(); + }); + + it('should respect deprecated slug prop', () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + render( + } + /> + ); + + expect( + screen.getByRole('button', { name: 'AI - Show information' }) + ).toBeInTheDocument(); + spy.mockRestore(); + }); }); it('should allow for a custom label', () => { @@ -90,12 +122,22 @@ describe('Tag', () => { expect(screen.getByTestId('test')).toBeInTheDocument(); }); - it('should respect slug prop', () => { + it('should respect decorator prop', () => { + render(} />); + + expect( + screen.getByRole('button', { name: 'AI - Show information' }) + ).toBeInTheDocument(); + }); + + it('should respect deprecated slug prop', () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); render(} />); expect( screen.getByRole('button', { name: 'AI - Show information' }) ).toBeInTheDocument(); + spy.mockRestore(); }); describe('Selectable Tag', () => { diff --git a/packages/react/src/components/Tag/Tag.stories.js b/packages/react/src/components/Tag/Tag.stories.js index ddef8f4f2902..9339fc7bc17e 100644 --- a/packages/react/src/components/Tag/Tag.stories.js +++ b/packages/react/src/components/Tag/Tag.stories.js @@ -8,6 +8,7 @@ import React from 'react'; import { default as Tag } from '../Tag'; import TagSkeleton from '../Tag/Tag.Skeleton'; +import DismissibleTag from '../Tag/DismissibleTag'; import { Asleep, View, FolderOpen, Folders } from '@carbon/icons-react'; import Button from '../Button'; import { AILabel, AILabelContent, AILabelActions } from '../AILabel'; @@ -217,36 +218,36 @@ const aiLabel = ( export const withAILabel = () => (
- - {'Tag'} - - {'Tag'} + + {'Tag'} - - {'Tag'} - + title="Clear Filter" + text="Tag">
); diff --git a/packages/react/src/components/Tag/Tag.tsx b/packages/react/src/components/Tag/Tag.tsx index 115fade0449b..8a762ffd64db 100644 --- a/packages/react/src/components/Tag/Tag.tsx +++ b/packages/react/src/components/Tag/Tag.tsx @@ -56,6 +56,11 @@ export interface TagBaseProps { */ className?: string; + /** + * **Experimental:** Provide a `decorator` component to be rendered inside the `Tag` component + */ + decorator?: ReactNode; + /** * Specify if the `Tag` is disabled */ @@ -89,6 +94,7 @@ export interface TagBaseProps { size?: keyof typeof SIZES; /** + * @deprecated please use `decorator` instead. * **Experimental:** Provide a `Slug` component to be rendered inside the `Tag` component */ slug?: ReactNode; @@ -113,6 +119,7 @@ const Tag = React.forwardRef(function Tag( { children, className, + decorator, id, type, filter, // remove filter in next major release - V12 @@ -168,13 +175,22 @@ const Tag = React.forwardRef(function Tag( } }; - // Slug is always size `md` and `inline` - let normalizedSlug; - if (slug && slug['type']?.displayName === 'AILabel' && !isInteractiveTag) { - normalizedSlug = React.cloneElement(slug as React.ReactElement, { - size: 'sm', - kind: 'inline', - }); + // AILabel is always size `sm` and `inline` + let normalizedDecorator = React.isValidElement(slug ?? decorator) + ? (slug ?? decorator) + : null; + if ( + normalizedDecorator && + normalizedDecorator['type']?.displayName === 'AILabel' && + !isInteractiveTag + ) { + normalizedDecorator = React.cloneElement( + normalizedDecorator as React.ReactElement, + { + size: 'sm', + kind: 'inline', + } + ); } if (filter) { @@ -194,7 +210,7 @@ const Tag = React.forwardRef(function Tag( className={`${prefix}--tag__label`}> {children !== null && children !== undefined ? children : typeText} - {normalizedSlug} + {normalizedDecorator} + + + +); + +export const withAILabel = () => ( +
+ + + + + + + +
+); + export const Playground = (args) => (
diff --git a/packages/react/src/components/Form/Form.stories.js b/packages/react/src/components/Form/Form.stories.js index 506a6fe1f75e..c80a3339a377 100644 --- a/packages/react/src/components/Form/Form.stories.js +++ b/packages/react/src/components/Form/Form.stories.js @@ -365,7 +365,7 @@ export const withAILabel = (args) => { id="select-1" labelText="Select an option" helperText="Optional helper text" - slug={aiLabel} + decorator={aiLabel} {...rest}> { />
- + diff --git a/packages/react/src/components/Select/Select.stories.js b/packages/react/src/components/Select/Select.stories.js index 0177dc80ac1f..baf83ad3a3ac 100644 --- a/packages/react/src/components/Select/Select.stories.js +++ b/packages/react/src/components/Select/Select.stories.js @@ -17,6 +17,7 @@ import { AILabel, AILabelContent, AILabelActions } from '../AILabel'; import { IconButton } from '../IconButton'; import { View, FolderOpen, Folders } from '@carbon/icons-react'; import mdx from './Select.mdx'; +import { Tooltip } from '../Tooltip'; export default { title: 'Components/Select', @@ -166,12 +167,12 @@ const aiLabel = ( ); export const withAILabel = () => ( -
+
` */ @@ -410,11 +439,10 @@ Select.propTypes = { */ size: PropTypes.oneOf(['sm', 'md', 'lg']), - /** - * **Experimental**: Provide a `Slug` component to be rendered inside the `Select` 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 whether the control is currently in warning state */ diff --git a/packages/react/src/components/Select/__tests__/Select-test.js b/packages/react/src/components/Select/__tests__/Select-test.js index d146a59ed08c..f68bf82b5a2d 100644 --- a/packages/react/src/components/Select/__tests__/Select-test.js +++ b/packages/react/src/components/Select/__tests__/Select-test.js @@ -242,6 +242,7 @@ describe('Select', () => { }); it('should respect slug prop', () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); const { container } = render( } /> + ); + + expect(container.firstChild.firstChild).toHaveClass( + `${prefix}--select--decorator` + ); }); }); diff --git a/packages/styles/scss/components/fluid-select/_fluid-select.scss b/packages/styles/scss/components/fluid-select/_fluid-select.scss index a2e8e7ca7a75..b9702ca02b7c 100644 --- a/packages/styles/scss/components/fluid-select/_fluid-select.scss +++ b/packages/styles/scss/components/fluid-select/_fluid-select.scss @@ -201,10 +201,14 @@ } // AILabel styles + .#{$prefix}--select--fluid + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > *, .#{$prefix}--select--fluid .#{$prefix}--select--slug .#{$prefix}--ai-label, .#{$prefix}--select--fluid .#{$prefix}--select--slug .#{$prefix}--slug { inset-block-start: convert.to-rem(42px); - inset-inline-end: $spacing-08; + inset-inline-end: $spacing-09; } .#{$prefix}--select--fluid diff --git a/packages/styles/scss/components/select/_select.scss b/packages/styles/scss/components/select/_select.scss index 3632bfd885f2..614f80415190 100644 --- a/packages/styles/scss/components/select/_select.scss +++ b/packages/styles/scss/components/select/_select.scss @@ -283,6 +283,9 @@ $divider-width: 1px; + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > *, .#{$prefix}--select--slug .#{$prefix}--ai-label, .#{$prefix}--select--slug .#{$prefix}--slug { position: absolute; @@ -292,6 +295,12 @@ transform: translateY(-50%); } + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > *::after, + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > *::before, .#{$prefix}--select--slug .#{$prefix}--ai-label::after, .#{$prefix}--select--slug .#{$prefix}--ai-label::before, .#{$prefix}--select--slug .#{$prefix}--slug::after, @@ -303,48 +312,92 @@ inline-size: convert.to-rem(1px); } + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > *::before, .#{$prefix}--select--slug .#{$prefix}--ai-label::before, .#{$prefix}--select--slug .#{$prefix}--slug::before { display: none; inset-inline-start: calc(-#{$spacing-03} - #{$divider-width}); } + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > *::after, .#{$prefix}--select--slug .#{$prefix}--ai-label::after, .#{$prefix}--select--slug .#{$prefix}--slug::after { display: block; + inset-block-start: 0; inset-inline-end: calc(-#{$spacing-03} - #{$divider-width}); } + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > .#{$prefix}--ai-label--revert::before, + .#{$prefix}--select--slug .#{$prefix}--ai-label--revert::before, + .#{$prefix}--select--slug .#{$prefix}--slug--revert::before { + inset-block-start: 0.5rem; + inset-inline-start: 0; + } + + .#{$prefix}--select--decorator + .#{$prefix}--select__inner-wrapper--decorator + > .#{$prefix}--ai-label--revert, + .#{$prefix}--select--slug .#{$prefix}--ai-label--revert { + inset-inline-end: convert.to-rem(41px); + } + + .#{$prefix}--select--decorator .#{$prefix}--ai-label--revert::after, .#{$prefix}--select--slug .#{$prefix}--ai-label--revert::after, .#{$prefix}--select--slug .#{$prefix}--slug--revert::after { inset-block-start: convert.to-rem(8px); - inset-inline-end: convert.to-rem(-1px); + inset-inline-end: -($divider-width); } - .#{$prefix}--select--slug - .#{$prefix}--select-input:has(~ .#{$prefix}--ai-label):not( - :has(~ .#{$prefix}--ai-label--revert) + .#{$prefix}--select--decorator + .#{$prefix}--select-input:has( + ~ .#{$prefix}--select__inner-wrapper--decorator ), .#{$prefix}--select--slug - .#{$prefix}--select-input:has(~ .#{$prefix}--slug):not( - :has(~ .#{$prefix}--slug--revert) - ) { - @include ai-gradient; - + .#{$prefix}--select-input:has(~ .#{$prefix}--ai-label), + .#{$prefix}--select--slug .#{$prefix}--select-input:has(~ .#{$prefix}--slug) { padding-inline-end: $spacing-10; } + .#{$prefix}--select--decorator:has(.#{$prefix}--select__invalid-icon) + .#{$prefix}--select-input:has( + ~ .#{$prefix}--select__inner-wrapper--decorator + ), .#{$prefix}--select--slug:has(.#{$prefix}--select__invalid-icon) + .#{$prefix}--select-input:has(~ .#{$prefix}--ai-label), + .#{$prefix}--select--slug:has(.#{$prefix}--select__invalid-icon) + .#{$prefix}--select-input:has(~ .#{$prefix}--slug) { + padding-inline-end: $spacing-12; + } + + .#{$prefix}--select--decorator + .#{$prefix}--select-input:has( + ~ .#{$prefix}--select__inner-wrapper--decorator .#{$prefix}--ai-label + ):not( + :has( + ~ .#{$prefix}--select__inner-wrapper--decorator + .#{$prefix}--ai-label--revert + ) + ), + .#{$prefix}--select--slug .#{$prefix}--select-input:has(~ .#{$prefix}--ai-label):not( :has(~ .#{$prefix}--ai-label--revert) ), - .#{$prefix}--select--slug:has(.#{$prefix}--select__invalid-icon) + .#{$prefix}--select--slug .#{$prefix}--select-input:has(~ .#{$prefix}--slug):not( :has(~ .#{$prefix}--slug--revert) ) { - padding-inline-end: $spacing-12; + @include ai-gradient; } + .#{$prefix}--select--decorator:has(.#{$prefix}--select__invalid-icon) + .#{$prefix}--select__inner-wrapper--decorator + > *::before, .#{$prefix}--select--slug:has(.#{$prefix}--select__invalid-icon) .#{$prefix}--ai-label::before, .#{$prefix}--select--slug:has(.#{$prefix}--select__invalid-icon) @@ -352,6 +405,10 @@ display: block; } + .#{$prefix}--select--decorator + .#{$prefix}--select-input__wrapper + .#{$prefix}--select-input + ~ .#{$prefix}--select__invalid-icon, .#{$prefix}--select--slug .#{$prefix}--select-input__wrapper[data-invalid] .#{$prefix}--select-input From c52884cfc64972d75e133a698417e8d6dab588a3 Mon Sep 17 00:00:00 2001 From: Alison Joseph Date: Thu, 21 Nov 2024 09:07:56 -0600 Subject: [PATCH 11/12] feat: replace slug with decorator ComboBox, MultiSelect, FilterableMultiSelect (#18069) * feat(combobox): replace slug prop with decorator * chore: update tests * feat: add decorator prop to multiselect * feat: add decorator prop to filterablemultiselect * chore: format and snapshot * chore: update form story * feat: add with ailabel stories to fluid and form stories * chore:format --------- Co-authored-by: Guilherme Datilio Ribeiro --- .../__snapshots__/PublicAPI-test.js.snap | 28 +++++----- .../src/components/ComboBox/ComboBox-test.js | 12 +++++ .../components/ComboBox/ComboBox.stories.js | 2 +- .../src/components/ComboBox/ComboBox.tsx | 51 +++++++++++++++---- .../FluidComboBox/FluidComboBox.stories.js | 50 +++++++++++++++++- .../FluidDropdown/FluidDropdown.stories.js | 50 +++++++++++++++++- .../FluidMultiSelect.stories.js | 51 ++++++++++++++++++- .../react/src/components/Form/Form.stories.js | 12 ++--- .../MultiSelect/FilterableMultiSelect.tsx | 51 +++++++++++++++---- .../MultiSelect/MultiSelect.stories.js | 5 +- .../components/MultiSelect/MultiSelect.tsx | 51 +++++++++++++++---- .../__tests__/FilterableMultiSelect-test.js | 13 +++++ .../MultiSelect/__tests__/MultiSelect-test.js | 20 ++++++++ 13 files changed, 338 insertions(+), 58 deletions(-) diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 4e35619514b3..138dc96a9985 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -1265,6 +1265,9 @@ Map { "className": Object { "type": "string", }, + "decorator": Object { + "type": "node", + }, "direction": Object { "args": Array [ Array [ @@ -1375,9 +1378,7 @@ Map { ], "type": "oneOf", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "titleText": Object { "type": "node", }, @@ -3559,6 +3560,9 @@ Map { "compareItems": Object { "type": "func", }, + "decorator": Object { + "type": "node", + }, "direction": Object { "args": Array [ Array [ @@ -3773,9 +3777,7 @@ Map { ], "type": "oneOf", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "sortItems": Object { "type": "func", }, @@ -5240,6 +5242,9 @@ Map { "compareItems": Object { "type": "func", }, + "decorator": Object { + "type": "node", + }, "direction": Object { "args": Array [ Array [ @@ -5454,9 +5459,7 @@ Map { ], "type": "oneOf", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "sortItems": Object { "type": "func", }, @@ -5494,6 +5497,9 @@ Map { "compareItems": Object { "type": "func", }, + "decorator": Object { + "type": "node", + }, "direction": Object { "args": Array [ Array [ @@ -5581,9 +5587,7 @@ Map { ], "type": "oneOf", }, - "slug": Object { - "type": "node", - }, + "slug": [Function], "sortItems": Object { "type": "func", }, diff --git a/packages/react/src/components/ComboBox/ComboBox-test.js b/packages/react/src/components/ComboBox/ComboBox-test.js index 15576ee7c555..36bca1f45cbb 100644 --- a/packages/react/src/components/ComboBox/ComboBox-test.js +++ b/packages/react/src/components/ComboBox/ComboBox-test.js @@ -285,6 +285,7 @@ describe('ComboBox', () => { }); it('should respect slug prop', async () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); const { container } = render( } /> ); @@ -292,6 +293,17 @@ describe('ComboBox', () => { expect(container.firstChild).toHaveClass( `${prefix}--list-box__wrapper--slug` ); + spy.mockRestore(); + }); + + it('should respect decorator prop', async () => { + const { container } = render( + } /> + ); + await waitForPosition(); + expect(container.firstChild).toHaveClass( + `${prefix}--list-box__wrapper--decorator` + ); }); describe('should display initially selected item found in `initialSelectedItem`', () => { diff --git a/packages/react/src/components/ComboBox/ComboBox.stories.js b/packages/react/src/components/ComboBox/ComboBox.stories.js index 3a355fae4d27..451ace723902 100644 --- a/packages/react/src/components/ComboBox/ComboBox.stories.js +++ b/packages/react/src/components/ComboBox/ComboBox.stories.js @@ -197,7 +197,7 @@ export const withAILabel = () => ( itemToString={(item) => (item ? item.text : '')} titleText="ComboBox title" helperText="Combobox helper text" - slug={aiLabel} + decorator={aiLabel} />
); diff --git a/packages/react/src/components/ComboBox/ComboBox.tsx b/packages/react/src/components/ComboBox/ComboBox.tsx index 1283e740e568..dd49efd7c52c 100644 --- a/packages/react/src/components/ComboBox/ComboBox.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.tsx @@ -204,6 +204,11 @@ export interface ComboBoxProps */ className?: string; + /** + * **Experimental**: Provide a `decorator` component to be rendered inside the `ComboBox` component + */ + decorator?: ReactNode; + /** * Specify the direction of the combobox dropdown. Can be either top or bottom. */ @@ -349,6 +354,7 @@ export interface ComboBoxProps size?: ListBoxSize; /** + * @deprecated please use decorator instead. * **Experimental**: Provide a `Slug` component to be rendered inside the `ComboBox` component */ slug?: ReactNode; @@ -388,6 +394,7 @@ const ComboBox = forwardRef( ariaLabel: deprecatedAriaLabel, autoAlign = false, className: containerClassName, + decorator, direction = 'bottom', disabled = false, downshiftActions, @@ -694,6 +701,7 @@ const ComboBox = forwardRef( [`${prefix}--list-box__wrapper--fluid--invalid`]: isFluid && invalid, [`${prefix}--list-box__wrapper--fluid--focus`]: isFluid && isFocused, [`${prefix}--list-box__wrapper--slug`]: slug, + [`${prefix}--list-box__wrapper--decorator`]: decorator, }, ]); @@ -705,12 +713,20 @@ const ComboBox = forwardRef( // needs to be Capitalized for react to render it correctly const ItemToElement = itemToElement; - // Slug is always size `mini` - let normalizedSlug; - if (slug && slug['type']?.displayName === 'AILabel') { - normalizedSlug = React.cloneElement(slug as React.ReactElement, { - size: 'mini', - }); + // AILabel 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', + } + ); } const { @@ -1038,7 +1054,15 @@ const ComboBox = forwardRef( translateWithId={translateWithId} />
- {normalizedSlug} + {slug ? ( + normalizedDecorator + ) : decorator ? ( +
+ {normalizedDecorator} +
+ ) : ( + '' + )} {isOpen ? filterItems(items, itemToString, inputValue).map( @@ -1133,6 +1157,11 @@ ComboBox.propTypes = { */ className: PropTypes.string, + /** + * **Experimental**: Provide a decorator component to be rendered inside the `ComboBox` component + */ + decorator: PropTypes.node, + /** * Specify the direction of the combobox dropdown. Can be either top or bottom. */ @@ -1283,10 +1312,10 @@ ComboBox.propTypes = { */ size: ListBoxPropTypes.ListBoxSize, - /** - * **Experimental**: Provide a `Slug` component to be rendered inside the `ComboBox` 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.' + ), /** * Provide text to be used in a `
); +const aiLabel = ( + + +
+

AI Explained

+

84%

+

Confidence score

+

+ Lorem ipsum dolor sit amet, di os consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut fsil labore et dolore magna aliqua. +

+
+

Model type

+

Foundation model

+
+ + + + + + + + + + + + +
+
+); + +export const withAILabel = () => ( +
+ {}} + id="default" + titleText="Label" + label="Choose an option" + items={items} + itemToString={(item) => (item ? item.text : '')} + decorator={aiLabel} + /> +
+); + export const Playground = (args) => (
(
); +const aiLabel = ( + + +
+

AI Explained

+

84%

+

Confidence score

+

+ Lorem ipsum dolor sit amet, di os consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut fsil labore et dolore magna aliqua. +

+
+

Model type

+

Foundation model

+
+ + + + + + + + + + + + +
+
+); + +export const withAILabel = () => ( +
+ (item ? item.text : '')} + decorator={aiLabel} + /> +
+); + export const Playground = (args) => (
(
); +const aiLabel = ( + + +
+

AI Explained

+

84%

+

Confidence score

+

+ Lorem ipsum dolor sit amet, di os consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut fsil labore et dolore magna aliqua. +

+
+

Model type

+

Foundation model

+
+ + + + + + + + + + + + +
+
+); + +export const withAILabel = () => ( +
+ {}} + initialSelectedItem={items[2]} + id="default" + titleText="Label" + label="Choose an option" + items={items} + itemToString={(item) => (item ? item.text : '')} + decorator={aiLabel} + /> +
+); + export const Playground = (args) => (
{ items={items} itemToString={(item) => (item ? item.text : '')} selectionFeedback="top-after-reopen" - slug={aiLabel} + decorator={aiLabel} {...rest} /> { items={items} itemToString={(item) => (item ? item.text : '')} selectionFeedback="top-after-reopen" - slug={aiLabel} + decorator={aiLabel} {...rest} /> { itemToString={(item) => (item ? item.text : '')} titleText="ComboBox title" helperText="Combobox helper text" - slug={aiLabel} + decorator={aiLabel} {...rest} />