Skip to content

Commit c52884c

Browse files
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 <guilhermedatilio@gmail.com>
1 parent ab1861d commit c52884c

File tree

13 files changed

+338
-58
lines changed

13 files changed

+338
-58
lines changed

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,9 @@ Map {
12651265
"className": Object {
12661266
"type": "string",
12671267
},
1268+
"decorator": Object {
1269+
"type": "node",
1270+
},
12681271
"direction": Object {
12691272
"args": Array [
12701273
Array [
@@ -1375,9 +1378,7 @@ Map {
13751378
],
13761379
"type": "oneOf",
13771380
},
1378-
"slug": Object {
1379-
"type": "node",
1380-
},
1381+
"slug": [Function],
13811382
"titleText": Object {
13821383
"type": "node",
13831384
},
@@ -3559,6 +3560,9 @@ Map {
35593560
"compareItems": Object {
35603561
"type": "func",
35613562
},
3563+
"decorator": Object {
3564+
"type": "node",
3565+
},
35623566
"direction": Object {
35633567
"args": Array [
35643568
Array [
@@ -3773,9 +3777,7 @@ Map {
37733777
],
37743778
"type": "oneOf",
37753779
},
3776-
"slug": Object {
3777-
"type": "node",
3778-
},
3780+
"slug": [Function],
37793781
"sortItems": Object {
37803782
"type": "func",
37813783
},
@@ -5240,6 +5242,9 @@ Map {
52405242
"compareItems": Object {
52415243
"type": "func",
52425244
},
5245+
"decorator": Object {
5246+
"type": "node",
5247+
},
52435248
"direction": Object {
52445249
"args": Array [
52455250
Array [
@@ -5454,9 +5459,7 @@ Map {
54545459
],
54555460
"type": "oneOf",
54565461
},
5457-
"slug": Object {
5458-
"type": "node",
5459-
},
5462+
"slug": [Function],
54605463
"sortItems": Object {
54615464
"type": "func",
54625465
},
@@ -5494,6 +5497,9 @@ Map {
54945497
"compareItems": Object {
54955498
"type": "func",
54965499
},
5500+
"decorator": Object {
5501+
"type": "node",
5502+
},
54975503
"direction": Object {
54985504
"args": Array [
54995505
Array [
@@ -5581,9 +5587,7 @@ Map {
55815587
],
55825588
"type": "oneOf",
55835589
},
5584-
"slug": Object {
5585-
"type": "node",
5586-
},
5590+
"slug": [Function],
55875591
"sortItems": Object {
55885592
"type": "func",
55895593
},

packages/react/src/components/ComboBox/ComboBox-test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,25 @@ describe('ComboBox', () => {
285285
});
286286

287287
it('should respect slug prop', async () => {
288+
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
288289
const { container } = render(
289290
<ComboBox {...mockProps} slug={<AILabel />} />
290291
);
291292
await waitForPosition();
292293
expect(container.firstChild).toHaveClass(
293294
`${prefix}--list-box__wrapper--slug`
294295
);
296+
spy.mockRestore();
297+
});
298+
299+
it('should respect decorator prop', async () => {
300+
const { container } = render(
301+
<ComboBox {...mockProps} decorator={<AILabel />} />
302+
);
303+
await waitForPosition();
304+
expect(container.firstChild).toHaveClass(
305+
`${prefix}--list-box__wrapper--decorator`
306+
);
295307
});
296308

297309
describe('should display initially selected item found in `initialSelectedItem`', () => {

packages/react/src/components/ComboBox/ComboBox.stories.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export const withAILabel = () => (
197197
itemToString={(item) => (item ? item.text : '')}
198198
titleText="ComboBox title"
199199
helperText="Combobox helper text"
200-
slug={aiLabel}
200+
decorator={aiLabel}
201201
/>
202202
</div>
203203
);

packages/react/src/components/ComboBox/ComboBox.tsx

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ export interface ComboBoxProps<ItemType>
204204
*/
205205
className?: string;
206206

207+
/**
208+
* **Experimental**: Provide a `decorator` component to be rendered inside the `ComboBox` component
209+
*/
210+
decorator?: ReactNode;
211+
207212
/**
208213
* Specify the direction of the combobox dropdown. Can be either top or bottom.
209214
*/
@@ -349,6 +354,7 @@ export interface ComboBoxProps<ItemType>
349354
size?: ListBoxSize;
350355

351356
/**
357+
* @deprecated please use decorator instead.
352358
* **Experimental**: Provide a `Slug` component to be rendered inside the `ComboBox` component
353359
*/
354360
slug?: ReactNode;
@@ -388,6 +394,7 @@ const ComboBox = forwardRef(
388394
ariaLabel: deprecatedAriaLabel,
389395
autoAlign = false,
390396
className: containerClassName,
397+
decorator,
391398
direction = 'bottom',
392399
disabled = false,
393400
downshiftActions,
@@ -694,6 +701,7 @@ const ComboBox = forwardRef(
694701
[`${prefix}--list-box__wrapper--fluid--invalid`]: isFluid && invalid,
695702
[`${prefix}--list-box__wrapper--fluid--focus`]: isFluid && isFocused,
696703
[`${prefix}--list-box__wrapper--slug`]: slug,
704+
[`${prefix}--list-box__wrapper--decorator`]: decorator,
697705
},
698706
]);
699707

@@ -705,12 +713,20 @@ const ComboBox = forwardRef(
705713
// needs to be Capitalized for react to render it correctly
706714
const ItemToElement = itemToElement;
707715

708-
// Slug is always size `mini`
709-
let normalizedSlug;
710-
if (slug && slug['type']?.displayName === 'AILabel') {
711-
normalizedSlug = React.cloneElement(slug as React.ReactElement<any>, {
712-
size: 'mini',
713-
});
716+
// AILabel always size `mini`
717+
let normalizedDecorator = React.isValidElement(slug ?? decorator)
718+
? (slug ?? decorator)
719+
: null;
720+
if (
721+
normalizedDecorator &&
722+
normalizedDecorator['type']?.displayName === 'AILabel'
723+
) {
724+
normalizedDecorator = React.cloneElement(
725+
normalizedDecorator as React.ReactElement<any>,
726+
{
727+
size: 'mini',
728+
}
729+
);
714730
}
715731

716732
const {
@@ -1038,7 +1054,15 @@ const ComboBox = forwardRef(
10381054
translateWithId={translateWithId}
10391055
/>
10401056
</div>
1041-
{normalizedSlug}
1057+
{slug ? (
1058+
normalizedDecorator
1059+
) : decorator ? (
1060+
<div className={`${prefix}--list-box__inner-wrapper--decorator`}>
1061+
{normalizedDecorator}
1062+
</div>
1063+
) : (
1064+
''
1065+
)}
10421066
<ListBox.Menu {...menuProps}>
10431067
{isOpen
10441068
? filterItems(items, itemToString, inputValue).map(
@@ -1133,6 +1157,11 @@ ComboBox.propTypes = {
11331157
*/
11341158
className: PropTypes.string,
11351159

1160+
/**
1161+
* **Experimental**: Provide a decorator component to be rendered inside the `ComboBox` component
1162+
*/
1163+
decorator: PropTypes.node,
1164+
11361165
/**
11371166
* Specify the direction of the combobox dropdown. Can be either top or bottom.
11381167
*/
@@ -1283,10 +1312,10 @@ ComboBox.propTypes = {
12831312
*/
12841313
size: ListBoxPropTypes.ListBoxSize,
12851314

1286-
/**
1287-
* **Experimental**: Provide a `Slug` component to be rendered inside the `ComboBox` component
1288-
*/
1289-
slug: PropTypes.node,
1315+
slug: deprecate(
1316+
PropTypes.node,
1317+
'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.'
1318+
),
12901319

12911320
/**
12921321
* Provide text to be used in a `<label>` element that is tied to the

packages/react/src/components/FluidComboBox/FluidComboBox.stories.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import {
1313
ToggletipButton,
1414
ToggletipContent,
1515
} from '../Toggletip';
16-
import { Information } from '@carbon/icons-react';
16+
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
17+
import { IconButton } from '../IconButton';
18+
import { Button } from '../Button';
19+
import { Information, View, FolderOpen, Folders } from '@carbon/icons-react';
1720

1821
export default {
1922
title: 'Experimental/Fluid Components/unstable__FluidComboBox',
@@ -92,6 +95,51 @@ export const Condensed = () => (
9295
</div>
9396
);
9497

98+
const aiLabel = (
99+
<AILabel className="ai-label-container">
100+
<AILabelContent>
101+
<div>
102+
<p className="secondary">AI Explained</p>
103+
<h1>84%</h1>
104+
<p className="secondary bold">Confidence score</p>
105+
<p className="secondary">
106+
Lorem ipsum dolor sit amet, di os consectetur adipiscing elit, sed do
107+
eiusmod tempor incididunt ut fsil labore et dolore magna aliqua.
108+
</p>
109+
<hr />
110+
<p className="secondary">Model type</p>
111+
<p className="bold">Foundation model</p>
112+
</div>
113+
<AILabelActions>
114+
<IconButton kind="ghost" label="View">
115+
<View />
116+
</IconButton>
117+
<IconButton kind="ghost" label="Open Folder">
118+
<FolderOpen />
119+
</IconButton>
120+
<IconButton kind="ghost" label="Folders">
121+
<Folders />
122+
</IconButton>
123+
<Button>View details</Button>
124+
</AILabelActions>
125+
</AILabelContent>
126+
</AILabel>
127+
);
128+
129+
export const withAILabel = () => (
130+
<div style={{ width: '400px' }}>
131+
<FluidComboBox
132+
onChange={() => {}}
133+
id="default"
134+
titleText="Label"
135+
label="Choose an option"
136+
items={items}
137+
itemToString={(item) => (item ? item.text : '')}
138+
decorator={aiLabel}
139+
/>
140+
</div>
141+
);
142+
95143
export const Playground = (args) => (
96144
<div style={{ width: args.playgroundWidth }}>
97145
<FluidComboBox

packages/react/src/components/FluidDropdown/FluidDropdown.stories.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import {
1313
ToggletipButton,
1414
ToggletipContent,
1515
} from '../Toggletip';
16-
import { Information } from '@carbon/icons-react';
16+
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
17+
import { IconButton } from '../IconButton';
18+
import { Button } from '../Button';
19+
import { Information, View, FolderOpen, Folders } from '@carbon/icons-react';
1720

1821
export default {
1922
title: 'Experimental/Fluid Components/unstable__FluidDropdown',
@@ -91,6 +94,51 @@ export const Condensed = () => (
9194
</div>
9295
);
9396

97+
const aiLabel = (
98+
<AILabel className="ai-label-container">
99+
<AILabelContent>
100+
<div>
101+
<p className="secondary">AI Explained</p>
102+
<h1>84%</h1>
103+
<p className="secondary bold">Confidence score</p>
104+
<p className="secondary">
105+
Lorem ipsum dolor sit amet, di os consectetur adipiscing elit, sed do
106+
eiusmod tempor incididunt ut fsil labore et dolore magna aliqua.
107+
</p>
108+
<hr />
109+
<p className="secondary">Model type</p>
110+
<p className="bold">Foundation model</p>
111+
</div>
112+
<AILabelActions>
113+
<IconButton kind="ghost" label="View">
114+
<View />
115+
</IconButton>
116+
<IconButton kind="ghost" label="Open Folder">
117+
<FolderOpen />
118+
</IconButton>
119+
<IconButton kind="ghost" label="Folders">
120+
<Folders />
121+
</IconButton>
122+
<Button>View details</Button>
123+
</AILabelActions>
124+
</AILabelContent>
125+
</AILabel>
126+
);
127+
128+
export const withAILabel = () => (
129+
<div style={{ width: '400px' }}>
130+
<FluidDropdown
131+
initialSelectedItem={items[2]}
132+
id="default"
133+
titleText="Label"
134+
label="Choose an option"
135+
items={items}
136+
itemToString={(item) => (item ? item.text : '')}
137+
decorator={aiLabel}
138+
/>
139+
</div>
140+
);
141+
94142
export const Playground = (args) => (
95143
<div style={{ width: args.playgroundWidth }}>
96144
<FluidDropdown

0 commit comments

Comments
 (0)