Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c59405b
feat(ListBox): support for all value
tenphi Jul 28, 2025
5d8bca7
feat(ListBox): select all
tenphi Jul 29, 2025
212d5a1
fix(Button): layout
tenphi Jul 29, 2025
5ce5472
fix(ListBox): select all issue
tenphi Jul 29, 2025
aa1432a
chore(ListBox): add tests
tenphi Jul 29, 2025
996e4d3
fix(FilterPicker): initial state inconsistency
tenphi Jul 29, 2025
2d0f978
fix(ListBox): improved select all behavior
tenphi Jul 29, 2025
a3b9e2e
chore: linting
tenphi Jul 29, 2025
1263bbd
fix(ListBox): select all behavior
tenphi Jul 29, 2025
7a32e44
fix(FilterPicker): story
tenphi Jul 29, 2025
39ab37c
fix(ListBox): minor fixes
tenphi Jul 29, 2025
d4f87e6
chore: increase the size limit
tenphi Jul 29, 2025
f2aa1e0
fix(Button): icon position
tenphi Jul 29, 2025
5b869a1
fix(Button): icon position * 2
tenphi Jul 29, 2025
ff03f7e
fix(FilterListBox): support for items prop
tenphi Jul 29, 2025
cc0c552
chore(FilterPicker): update documentation
tenphi Jul 29, 2025
0e9466a
fix(Menu): styles after button update
tenphi Jul 29, 2025
47d903f
chore(FilterPicker): remove custom trigger styles story
tenphi Jul 29, 2025
d134a82
fix(Button): single-icon mod
tenphi Jul 29, 2025
36661aa
fix(Button): text overflow ellipsis with icons
tenphi Jul 29, 2025
76c2820
refactor(Select): improve button styles usage
tenphi Jul 29, 2025
0a86e06
fix(Button): layout
tenphi Jul 30, 2025
664ee58
fix(FilterPicker): layout and rightIcon pass
tenphi Jul 30, 2025
770c843
fix(FilterPicker): typing
tenphi Jul 30, 2025
40cf970
fix(ListBox): prop types
tenphi Jul 30, 2025
3be0dc3
fix(FilterPicker): type issues
tenphi Jul 30, 2025
be2ca69
fix(Button): layout styles
tenphi Jul 30, 2025
be753ce
fix(FilterListBox): custom value with all keys
tenphi Jul 30, 2025
a2e5b44
fix(Button): children rendering
tenphi Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/curly-pans-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Fix flipping of popover in FilterPicker if it's already open.
5 changes: 5 additions & 0 deletions .changeset/nervous-rules-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Improved Button layout.
5 changes: 5 additions & 0 deletions .changeset/nice-comics-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Improved FilterPicker layout with additional wrapper for consistency.
5 changes: 5 additions & 0 deletions .changeset/slow-fans-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Fix initial state inconsistency in FilterPicker.
5 changes: 5 additions & 0 deletions .changeset/sour-donkeys-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Overflow text ellipsis in Buttons with icons by default.
5 changes: 5 additions & 0 deletions .changeset/thirty-bikes-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Add `showSelectAll` and `selectAllLabel` options for ListBox, FilterListBox, and FilterPicker to add "Select All" option. The label can be customized.
2 changes: 1 addition & 1 deletion .size-limit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = [
}),
);
},
limit: '280kB',
limit: '281kB',
},
{
name: 'Tree shaking (just a Button)',
Expand Down
2 changes: 1 addition & 1 deletion src/components/actions/Button/Button.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ The `mods` prop accepts the following modifiers you can override:
| loading | `boolean` | Displays loading spinner. |
| selected | `boolean` | Displays selected state. |
| with-icons | `boolean` | Indicates that the button contains at least one icon. |
| single-icon-only | `boolean` | Icon-only button without text. |
| single-icon | `boolean` | Icon-only button without text. |

## Variants

Expand Down
61 changes: 46 additions & 15 deletions src/components/actions/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TEXT_STYLES,
} from '../../../tasty';
import { accessibilityWarning } from '../../../utils/warnings';
import { Text } from '../../content/Text';
import { CubeActionProps } from '../Action/Action';
import { useAction } from '../use-action';

Expand Down Expand Up @@ -60,8 +61,16 @@ const STYLE_PROPS = [...CONTAINER_STYLES, ...TEXT_STYLES];

export const DEFAULT_BUTTON_STYLES = {
display: 'inline-grid',
placeItems: 'center stretch',
placeContent: 'center',
flow: 'column',
placeItems: 'center start',
placeContent: {
'': 'center',
'right-icon | suffix': 'center stretch',
},
gridColumns: {
'': 'initial',
'left-icon | loading | prefix': 'max-content',
},
position: 'relative',
margin: 0,
boxSizing: 'border-box',
Expand All @@ -73,7 +82,6 @@ export const DEFAULT_BUTTON_STYLES = {
'': '.75x',
'[data-size="small"]': '.5x',
},
flow: 'column',
preset: {
'': 't3m',
'[data-size="xsmall"]': 't4',
Expand All @@ -85,19 +93,19 @@ export const DEFAULT_BUTTON_STYLES = {
outlineOffset: 1,
padding: {
'': '.5x (1.5x - 1bw)',
'[data-size="small"] | [data-size="xsmall"]': '.5x (1x - 1bw)',
'[data-size="small"] | [data-size="xsmall"]': '.5x (1.25x - 1bw)',
'[data-size="medium"]': '.5x (1.5x - 1bw)',
'[data-size="large"]': '.5x (2x - 1bw)',
'[data-size="xlarge"]': '.5x (2.25x - 1bw)',
'single-icon-only | [data-type="link"]': 0,
'[data-size="large"]': '.5x (1.75x - 1bw)',
'[data-size="xlarge"]': '.5x (2x - 1bw)',
'single-icon | [data-type="link"]': 0,
},
width: {
'': 'initial',
'[data-size="xsmall"] & single-icon-only': '@size-xs @size-xs',
'[data-size="small"] & single-icon-only': '@size-sm @size-sm',
'[data-size="medium"] & single-icon-only': '@size-md @size-md',
'[data-size="large"] & single-icon-only': '@size-lg @size-lg',
'[data-size="xlarge"] & single-icon-only': '@size-xl @size-xl',
'[data-size="xsmall"] & single-icon': '@size-xs @size-xs',
'[data-size="small"] & single-icon': '@size-sm @size-sm',
'[data-size="medium"] & single-icon': '@size-md @size-md',
'[data-size="large"] & single-icon': '@size-lg @size-lg',
'[data-size="xlarge"] & single-icon': '@size-xl @size-xl',
'[data-type="link"]': 'initial',
},
height: {
Expand All @@ -114,6 +122,20 @@ export const DEFAULT_BUTTON_STYLES = {
'': true,
'[data-type="link"] & !focused': 0,
},

ButtonIcon: {
width: 'max-content',
},

'& [data-element="ButtonIcon"]:first-child:not(:last-child)': {
marginLeft: '-.5x',
placeSelf: 'center start',
},

'& [data-element="ButtonIcon"]:last-child:not(:first-child)': {
marginRight: '-.5x',
placeSelf: 'center end',
},
} as const;

// ---------- DEFAULT THEME ----------
Expand Down Expand Up @@ -657,12 +679,16 @@ export const Button = forwardRef(function Button(
!children
);

const hasIcons = !!icon || !!rightIcon;

const modifiers = useMemo(
() => ({
loading: isLoading,
selected: isSelected,
'with-icons': !!icon || !!rightIcon,
'single-icon-only': singleIcon,
'with-icons': hasIcons,
'left-icon': !!icon,
'right-icon': !!rightIcon,
'single-icon': singleIcon,
...mods,
}),
[mods, isDisabled, isLoading, isSelected, singleIcon],
Expand Down Expand Up @@ -696,7 +722,12 @@ export const Button = forwardRef(function Button(
<LoadingIcon data-element="ButtonIcon" />
)
) : null}
{children}
{((hasIcons && children) || (!!icon && !!rightIcon)) &&
typeof children === 'string' ? (
<Text ellipsis>{children}</Text>
) : (
children
)}
{rightIcon}
</ButtonElement>
);
Expand Down
8 changes: 8 additions & 0 deletions src/components/actions/Menu/styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ export const StyledItem = tasty({
placeItems: 'center',
},

'& [data-element="ButtonIcon"]:first-child:not(:last-child)': {
marginLeft: 0,
},

'& [data-element="ButtonIcon"]:last-child:not(:first-child)': {
marginRight: 0,
},

Postfix: {
color: {
'': '#dark-03',
Expand Down
36 changes: 33 additions & 3 deletions src/components/fields/FilterListBox/FilterListBox.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ The `mods` property accepts the following modifiers you can override:
</FilterListBox>
```

### Multiple Selection with Select All

<Story of={FilterListBoxStories.WithSelectAll} />

```jsx
<FilterListBox
label="Select permissions"
selectionMode="multiple"
showSelectAll={true}
selectAllLabel="All Permissions"
searchPlaceholder="Search permissions..."
>
<FilterListBox.Item key="read">Read</FilterListBox.Item>
<FilterListBox.Item key="write">Write</FilterListBox.Item>
<FilterListBox.Item key="execute">Execute</FilterListBox.Item>
</FilterListBox>
```

### With Descriptions

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

4. **Performance**: Use custom filter functions for specialized search needs
5. **UX**: Provide meaningful empty state messages
6. **Accessibility**: Always provide clear search placeholders
4. **Do**: Use `showSelectAll` for efficient multiple selection from filtered lists
```jsx
<FilterListBox
selectionMode="multiple"
showSelectAll
selectAllLabel="Select All Visible"
searchPlaceholder="Filter items..."
>
{/* many items */}
</FilterListBox>
```

5. **Performance**: Use custom filter functions for specialized search needs
6. **UX**: Provide meaningful empty state messages
7. **Accessibility**: Always provide clear search placeholders

## Integration with Forms

Expand Down
70 changes: 59 additions & 11 deletions src/components/fields/FilterListBox/FilterListBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,16 @@ import {
CheckIcon,
DatabaseIcon,
FilterIcon,
PlusIcon,
RightIcon,
SearchIcon,
SettingsIcon,
UserIcon,
} from '../../../icons';
import { baseProps } from '../../../stories/lists/baseProps';
import { Button } from '../../actions/Button/Button';
import { Badge } from '../../content/Badge/Badge';
import { Paragraph } from '../../content/Paragraph';
import { Text } from '../../content/Text';
import { Title } from '../../content/Title';
import { Form, SubmitButton } from '../../form';
import { Flow } from '../../layout/Flow';
import { Space } from '../../layout/Space';
import { Dialog } from '../../overlays/Dialog/Dialog';
import { DialogTrigger } from '../../overlays/Dialog/DialogTrigger';
Expand Down Expand Up @@ -48,11 +44,13 @@ const meta: Meta<typeof FilterListBox> = {
},
selectedKeys: {
control: { type: 'object' },
description: 'The selected keys in controlled multiple mode',
description:
'The selected keys in controlled multiple mode. Use "all" to select all items or an array of keys.',
},
defaultSelectedKeys: {
control: { type: 'object' },
description: 'The default selected keys in uncontrolled multiple mode',
description:
'The default selected keys in uncontrolled multiple mode. Use "all" to select all items or an array of keys.',
},
selectionMode: {
options: ['single', 'multiple'],
Expand All @@ -64,7 +62,8 @@ const meta: Meta<typeof FilterListBox> = {
},
allowsCustomValue: {
control: { type: 'boolean' },
description: 'Whether the FilterListBox allows custom values',
description:
'Whether to allow entering custom values that are not present in the predefined options',
table: {
defaultValue: { summary: false },
},
Expand Down Expand Up @@ -106,7 +105,8 @@ const meta: Meta<typeof FilterListBox> = {
},
filter: {
control: false,
description: 'Custom filter function for search',
description:
'Custom filter function for determining if an option should be included in search results',
},

/* Presentation */
Expand All @@ -130,7 +130,7 @@ const meta: Meta<typeof FilterListBox> = {
/* Behavior */
isCheckable: {
control: { type: 'boolean' },
description: 'Whether to show checkboxes for multiple selection',
description: 'Whether to show checkboxes for multiple selection mode',
table: {
defaultValue: { summary: false },
},
Expand Down Expand Up @@ -201,6 +201,21 @@ const meta: Meta<typeof FilterListBox> = {
action: 'option clicked',
description: 'Callback when an option is clicked (non-checkbox area)',
},
showSelectAll: {
control: { type: 'boolean' },
description:
'Whether to show the "Select All" option in multiple selection mode',
table: {
defaultValue: { summary: false },
},
},
selectAllLabel: {
control: { type: 'text' },
description: 'Label for the "Select All" option',
table: {
defaultValue: { summary: 'Select All' },
},
},
},
};

Expand Down Expand Up @@ -1201,13 +1216,14 @@ export const VirtualizedList: StoryFn<CubeFilterListBoxProps<any>> = (args) => {
selectedKeys={selectedKeys}
height="300px"
overflow="auto"
items={items}
onSelectionChange={(keys) => setSelectedKeys(keys as string[])}
>
{items.map((item) => (
{(item) => (
<FilterListBox.Item key={item.id} description={item.description}>
{item.name}
</FilterListBox.Item>
))}
)}
</FilterListBox>

<Text>
Expand All @@ -1230,3 +1246,35 @@ VirtualizedList.parameters = {
},
},
};

export const WithSelectAll: Story = {
render: (args) => (
<FilterListBox {...args}>
{permissions.map((permission) => (
<FilterListBox.Item
key={permission.key}
description={permission.description}
>
{permission.label}
</FilterListBox.Item>
))}
</FilterListBox>
),
args: {
label: 'Select permissions with Select All',
selectionMode: 'multiple',
isCheckable: true,
showSelectAll: true,
selectAllLabel: 'All Permissions',
defaultSelectedKeys: ['read'],
searchPlaceholder: 'Search permissions...',
},
parameters: {
docs: {
description: {
story:
'When `showSelectAll={true}` is used with multiple selection mode in FilterListBox, a "Select All" option appears in the header above the search input. The checkbox shows indeterminate state when some items are selected, checked when all are selected, and unchecked when none are selected. The select all functionality works seamlessly with filtering - it only affects the currently visible (filtered) items.',
},
},
},
};
Loading
Loading