Skip to content

Commit ff03f7e

Browse files
committed
fix(FilterListBox): support for items prop
1 parent 5b869a1 commit ff03f7e

File tree

8 files changed

+147
-26
lines changed

8 files changed

+147
-26
lines changed

src/components/actions/Button/Button.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,18 +117,16 @@ export const DEFAULT_BUTTON_STYLES = {
117117

118118
ButtonIcon: {
119119
width: 'max-content',
120-
margin: {
121-
'': 0,
122-
':first-child': '-.5x left',
123-
':last-child': '-.5x right',
124-
'(:first-child & :last-child) | [data-size="xsmall"]': 0,
125-
},
126-
placeSelf: {
127-
'': 'center',
128-
':first-child': 'center start',
129-
':last-child': 'center end',
130-
':first-child & :last-child': 'center',
131-
},
120+
},
121+
122+
'& [data-element="ButtonIcon"]:first-child:not(:last-child)': {
123+
marginLeft: '-.5x',
124+
placeSelf: 'center start',
125+
},
126+
127+
'& [data-element="ButtonIcon"]:last-child:not(:first-child)': {
128+
marginRight: '-.5x',
129+
placeSelf: 'center end',
132130
},
133131
} as const;
134132

src/components/fields/FilterListBox/FilterListBox.docs.mdx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,24 @@ The `mods` property accepts the following modifiers you can override:
157157
</FilterListBox>
158158
```
159159

160+
### Multiple Selection with Select All
161+
162+
<Story of={FilterListBoxStories.WithSelectAll} />
163+
164+
```jsx
165+
<FilterListBox
166+
label="Select permissions"
167+
selectionMode="multiple"
168+
showSelectAll={true}
169+
selectAllLabel="All Permissions"
170+
searchPlaceholder="Search permissions..."
171+
>
172+
<FilterListBox.Item key="read">Read</FilterListBox.Item>
173+
<FilterListBox.Item key="write">Write</FilterListBox.Item>
174+
<FilterListBox.Item key="execute">Execute</FilterListBox.Item>
175+
</FilterListBox>
176+
```
177+
160178
### With Descriptions
161179

162180
<Story of={FilterListBoxStories.WithDescriptions} />
@@ -322,9 +340,21 @@ The `mods` property accepts the following modifiers you can override:
322340
</FilterListBox.Item>
323341
```
324342

325-
4. **Performance**: Use custom filter functions for specialized search needs
326-
5. **UX**: Provide meaningful empty state messages
327-
6. **Accessibility**: Always provide clear search placeholders
343+
4. **Do**: Use `showSelectAll` for efficient multiple selection from filtered lists
344+
```jsx
345+
<FilterListBox
346+
selectionMode="multiple"
347+
showSelectAll
348+
selectAllLabel="Select All Visible"
349+
searchPlaceholder="Filter items..."
350+
>
351+
{/* many items */}
352+
</FilterListBox>
353+
```
354+
355+
5. **Performance**: Use custom filter functions for specialized search needs
356+
6. **UX**: Provide meaningful empty state messages
357+
7. **Accessibility**: Always provide clear search placeholders
328358

329359
## Integration with Forms
330360

src/components/fields/FilterListBox/FilterListBox.stories.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,13 +1220,14 @@ export const VirtualizedList: StoryFn<CubeFilterListBoxProps<any>> = (args) => {
12201220
selectedKeys={selectedKeys}
12211221
height="300px"
12221222
overflow="auto"
1223+
items={items}
12231224
onSelectionChange={(keys) => setSelectedKeys(keys as string[])}
12241225
>
1225-
{items.map((item) => (
1226+
{(item) => (
12261227
<FilterListBox.Item key={item.id} description={item.description}>
12271228
{item.name}
12281229
</FilterListBox.Item>
1229-
))}
1230+
)}
12301231
</FilterListBox>
12311232

12321233
<Text>

src/components/fields/FilterListBox/FilterListBox.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,48 @@ export const FilterListBox = forwardRef(function FilterListBox<
215215
headerStyles,
216216
footerStyles,
217217
listBoxStyles,
218-
children,
218+
items,
219+
children: renderChildren,
219220
onEscape,
220221
isCheckable,
221222
onOptionClick,
222223
selectionMode = 'single',
223224
...otherProps
224225
} = props;
225226

227+
// Preserve the original `children` (may be a render function) before we
228+
// potentially overwrite it.
229+
let children: ReactNode = renderChildren as ReactNode;
230+
231+
const renderFn = renderChildren as unknown;
232+
233+
if (items && typeof renderFn === 'function') {
234+
try {
235+
const itemsArray = Array.from(items as Iterable<any>);
236+
// Execute the render function for each item to obtain <Item/> / <Section/> nodes.
237+
children = itemsArray.map((item, idx) => {
238+
const rendered = (renderFn as (it: any) => ReactNode)(item);
239+
// Ensure every element has a stable key: rely on the user-provided key
240+
// inside the render function, otherwise fall back to the item itself or
241+
// the index. This mirrors React Aria examples where the render function
242+
// is expected to set keys, but we add a fallback for robustness.
243+
if (
244+
React.isValidElement(rendered) &&
245+
(rendered as ReactElement).key == null
246+
) {
247+
return React.cloneElement(rendered as ReactElement, {
248+
key: (rendered as any)?.key ?? item?.key ?? idx,
249+
});
250+
}
251+
252+
return rendered as ReactNode;
253+
});
254+
} catch {
255+
// If conversion fails for some reason, we silently ignore and proceed
256+
// with the original children value so we don't break runtime.
257+
}
258+
}
259+
226260
// Collect original option keys to avoid duplicating them as custom values.
227261
const originalKeys = useMemo(() => {
228262
const keys = new Set<string>();
@@ -881,6 +915,7 @@ export const FilterListBox = forwardRef(function FilterListBox<
881915
mods={mods}
882916
size={size}
883917
isCheckable={isCheckable}
918+
items={items as any}
884919
onSelectionChange={handleSelectionChange}
885920
onEscape={onEscape}
886921
onOptionClick={handleOptionClick}

src/components/fields/FilterPicker/FilterPicker.docs.mdx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,24 @@ The `mods` property accepts the following modifiers you can override:
165165
</FilterPicker>
166166
```
167167

168+
### Multiple Selection with Select All
169+
170+
<Story of={FilterPickerStories.WithSelectAll} />
171+
172+
```jsx
173+
<FilterPicker
174+
label="Select permissions"
175+
placeholder="Choose permissions"
176+
selectionMode="multiple"
177+
showSelectAll={true}
178+
selectAllLabel="All Permissions"
179+
>
180+
<FilterPicker.Item key="read">Read</FilterPicker.Item>
181+
<FilterPicker.Item key="write">Write</FilterPicker.Item>
182+
<FilterPicker.Item key="execute">Execute</FilterPicker.Item>
183+
</FilterPicker>
184+
```
185+
168186
### Custom Summary
169187

170188
<Story of={FilterPickerStories.CustomSummary} />
@@ -365,9 +383,21 @@ The `mods` property accepts the following modifiers you can override:
365383
</FilterPicker.Section>
366384
```
367385

368-
4. **Accessibility**: Always provide meaningful labels and placeholders
369-
5. **Performance**: Use `textValue` prop for complex option content
370-
6. **UX**: Consider using `isCheckable` for multiple selection clarity
386+
4. **Do**: Use `showSelectAll` for efficient multiple selection in compact interfaces
387+
```jsx
388+
<FilterPicker
389+
selectionMode="multiple"
390+
showSelectAll
391+
selectAllLabel="Select All Options"
392+
placeholder="Choose items..."
393+
>
394+
{/* many items */}
395+
</FilterPicker>
396+
```
397+
398+
5. **Accessibility**: Always provide meaningful labels and placeholders
399+
6. **Performance**: Use `textValue` prop for complex option content
400+
7. **UX**: Consider using `isCheckable` for multiple selection clarity
371401

372402
## Integration with Forms
373403

src/components/fields/FilterPicker/FilterPicker.stories.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,17 +1518,18 @@ export const VirtualizedList: Story = {
15181518
<FilterPicker
15191519
{...args}
15201520
selectedKeys={selectedKeys}
1521+
items={items}
15211522
onSelectionChange={(keys) => setSelectedKeys(keys as string[])}
15221523
>
1523-
{items.map((item) => (
1524+
{(item) => (
15241525
<FilterPicker.Item
15251526
key={item.id}
15261527
textValue={item.name}
15271528
description={item.description}
15281529
>
15291530
{item.name}
15301531
</FilterPicker.Item>
1531-
))}
1532+
)}
15321533
</FilterPicker>
15331534

15341535
<Text preset="t4" color="#dark.60">

src/components/fields/FilterPicker/FilterPicker.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
196196
focusOnHover,
197197
showSelectAll,
198198
selectAllLabel,
199+
items,
199200
header,
200201
footer,
201202
headerStyles,
@@ -742,6 +743,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
742743
<FocusScope restoreFocus>
743744
<FilterListBox
744745
autoFocus
746+
items={items}
745747
// Pass an aria-label so the internal ListBox is properly labeled and React Aria doesn't warn.
746748
aria-label={`${props['aria-label'] ?? props.label ?? ''} Picker`}
747749
selectedKey={

src/components/fields/ListBox/ListBox.docs.mdx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,23 @@ The `mods` property accepts the following modifiers you can override:
168168
</ListBox>
169169
```
170170

171+
### Multiple Selection with Select All
172+
173+
<Story of={ListBoxStories.WithSelectAll} />
174+
175+
```jsx
176+
<ListBox
177+
label="Select permissions"
178+
selectionMode="multiple"
179+
showSelectAll={true}
180+
selectAllLabel="All Permissions"
181+
>
182+
<ListBox.Item key="read">Read</ListBox.Item>
183+
<ListBox.Item key="write">Write</ListBox.Item>
184+
<ListBox.Item key="execute">Execute</ListBox.Item>
185+
</ListBox>
186+
```
187+
171188
### With Header and Footer
172189

173190
<Story of={ListBoxStories.WithHeaderAndFooter} />
@@ -294,9 +311,16 @@ const [selectedKey, setSelectedKey] = useState('apple');
294311
</ListBox.Section>
295312
```
296313

297-
4. **Accessibility**: Always provide meaningful labels and descriptions
298-
5. **Performance**: Use virtualization for lists with 50+ items
299-
6. **UX**: Consider FilterListBox for searchable lists with many options
314+
4. **Do**: Use `showSelectAll` for efficient multiple selection from large lists
315+
```jsx
316+
<ListBox selectionMode="multiple" showSelectAll selectAllLabel="Select All Items">
317+
{/* many items */}
318+
</ListBox>
319+
```
320+
321+
5. **Accessibility**: Always provide meaningful labels and descriptions
322+
6. **Performance**: Use virtualization for lists with 50+ items
323+
7. **UX**: Consider FilterListBox for searchable lists with many options
300324

301325
## Integration with Forms
302326

0 commit comments

Comments
 (0)