Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/fix-layout-panel-touch-resize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': patch
---

Fix Layout.Panel resize handler not working properly on touch devices by adding `touch-action: none` to prevent browser scroll interference during drag
4 changes: 4 additions & 0 deletions src/components/content/Layout/LayoutPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ const ResizeHandlerElement = tasty({
horizontal: 'col-resize',
disabled: 'not-allowed',
},
touchAction: {
'': 'none',
disabled: 'auto',
},
padding: 0,
outline: 0,
boxSizing: 'border-box',
Expand Down
29 changes: 29 additions & 0 deletions src/components/fields/ComboBox/ComboBox.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,33 @@ const [selectedKey, setSelectedKey] = useState(null);
</ComboBox>
```

### With Sections (Dynamic)

<Story of={ComboBoxStories.DynamicSections} />

Sections and items can be defined dynamically using the `items` prop with a render function. Pass hierarchical data where each section contains a `children` array.

```jsx
const categories = [
{ name: 'Fruits', children: [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
]},
{ name: 'Vegetables', children: [
{ key: 'carrot', label: 'Carrot' },
{ key: 'broccoli', label: 'Broccoli' },
]},
];

<ComboBox label="Food" placeholder="Select food..." items={categories}>
{(category) => (
<ComboBox.Section key={category.name} title={category.name} items={category.children}>
{(item) => <ComboBox.Item key={item.key}>{item.label}</ComboBox.Item>}
</ComboBox.Section>
)}
</ComboBox>
```

### With Disabled Items

<Story of={ComboBoxStories.WithDisabledItems} />
Expand Down Expand Up @@ -442,6 +469,8 @@ const fruits = [

<Story of={ComboBoxStories.VirtualizedList} />

For large datasets, use the `items` prop with a render function. This enables automatic virtualization — only visible items are rendered in the DOM, providing smooth scrolling even with thousands of items. Virtualization is disabled when sections are present.

```jsx
const items = Array.from({ length: 1000 }, (_, i) => ({
key: `item-${i}`,
Expand Down
37 changes: 37 additions & 0 deletions src/components/fields/ComboBox/ComboBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,43 @@ export const WithSections = () => (
</ComboBox>
);

export const DynamicSections = () => {
const categories = [
{
name: 'Fruits',
children: [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
{ key: 'cherry', label: 'Cherry' },
],
},
{
name: 'Vegetables',
children: [
{ key: 'carrot', label: 'Carrot' },
{ key: 'broccoli', label: 'Broccoli' },
{ key: 'spinach', label: 'Spinach' },
],
},
];

return (
<ComboBox label="Food" placeholder="Select food..." items={categories}>
{(category: any) => (
<ComboBox.Section
key={category.name}
title={category.name}
items={category.children}
>
{(item: any) => (
<ComboBox.Item key={item.key}>{item.label}</ComboBox.Item>
)}
</ComboBox.Section>
)}
</ComboBox>
);
};

export const WithDisabledItems = () => (
<ComboBox
label="Fruit"
Expand Down
27 changes: 27 additions & 0 deletions src/components/fields/FilterListBox/FilterListBox.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,33 @@ Groups related items together with an optional heading.
</FilterListBox>
```

### With Sections (Dynamic)

<Story of={FilterListBoxStories.DynamicSections} />

Sections and items can be defined dynamically using the `items` prop with a render function. Pass hierarchical data where each section contains a `children` array.

```jsx
const categories = [
{ name: 'Fruits', children: [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
]},
{ name: 'Vegetables', children: [
{ key: 'carrot', label: 'Carrot' },
{ key: 'broccoli', label: 'Broccoli' },
]},
];

<FilterListBox label="Choose an ingredient" searchPlaceholder="Search ingredients..." items={categories}>
{(category) => (
<FilterListBox.Section key={category.name} title={category.name} items={category.children}>
{(item) => <FilterListBox.Item key={item.key}>{item.label}</FilterListBox.Item>}
</FilterListBox.Section>
)}
</FilterListBox>
```

### With Descriptions

<Story of={FilterListBoxStories.WithDescriptions} />
Expand Down
49 changes: 49 additions & 0 deletions src/components/fields/FilterListBox/FilterListBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,55 @@ WithSections.args = {
searchPlaceholder: 'Search ingredients...',
};

export const DynamicSections: StoryFn<CubeFilterListBoxProps<any>> = (args) => {
const categories = [
{
name: 'Fruits',
children: [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
{ key: 'cherry', label: 'Cherry' },
],
},
{
name: 'Vegetables',
children: [
{ key: 'carrot', label: 'Carrot' },
{ key: 'broccoli', label: 'Broccoli' },
{ key: 'spinach', label: 'Spinach' },
],
},
{
name: 'Herbs',
children: [
{ key: 'basil', label: 'Basil' },
{ key: 'cilantro', label: 'Cilantro' },
{ key: 'parsley', label: 'Parsley' },
],
},
];

return (
<FilterListBox {...args} items={categories}>
{(category: any) => (
<FilterListBox.Section
key={category.name}
title={category.name}
items={category.children}
>
{(item: any) => (
<FilterListBox.Item key={item.key}>{item.label}</FilterListBox.Item>
)}
</FilterListBox.Section>
)}
</FilterListBox>
);
};
DynamicSections.args = {
label: 'Choose an ingredient',
searchPlaceholder: 'Search ingredients...',
};

export const WithSectionsAndDescriptions: StoryFn<
CubeFilterListBoxProps<any>
> = (args) => (
Expand Down
34 changes: 34 additions & 0 deletions src/components/fields/FilterPicker/FilterPicker.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,40 @@ For large datasets or dynamic content, use the `items` prop with a render functi
</FilterPicker>
```

### Dynamic Sections Pattern

For hierarchical data with sections, use the `items` prop with nested render functions. Each section contains a `children` array of items.

```jsx
const categories = [
{ name: 'Fruits', children: [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
]},
{ name: 'Vegetables', children: [
{ key: 'carrot', label: 'Carrot' },
{ key: 'broccoli', label: 'Broccoli' },
]},
];

<FilterPicker
label="Choose items"
placeholder="Choose items..."
searchPlaceholder="Search..."
items={categories}
>
{(category) => (
<FilterPicker.Section key={category.name} title={category.name} items={category.children}>
{(item) => (
<FilterPicker.Item key={item.key} textValue={item.label}>
{item.label}
</FilterPicker.Item>
)}
</FilterPicker.Section>
)}
</FilterPicker>
```

**Key Benefits:**
- **Virtualization**: Automatically enabled for large lists without sections
- **Performance**: Only renders visible items in the DOM
Expand Down
69 changes: 69 additions & 0 deletions src/components/fields/FilterPicker/FilterPicker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,75 @@ export const WithSections: Story = {
},
};

export const DynamicSections: Story = {
args: {
label: 'Organized by Sections',
placeholder: 'Choose items...',
selectionMode: 'multiple',
searchPlaceholder: 'Search ingredients...',
width: 'max 30x',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = canvas.getByRole('button');
await userEvent.click(trigger);
},
render: (args) => {
const categories = [
{
name: 'Fruits',
children: [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
{ key: 'cherry', label: 'Cherry' },
],
},
{
name: 'Vegetables',
children: [
{ key: 'carrot', label: 'Carrot' },
{ key: 'broccoli', label: 'Broccoli' },
{ key: 'spinach', label: 'Spinach' },
],
},
{
name: 'Grains',
children: [
{ key: 'rice', label: 'Rice' },
{ key: 'oats', label: 'Oats' },
{ key: 'barley', label: 'Barley' },
],
},
];

return (
<FilterPicker {...args} items={categories}>
{(category: any) => (
<FilterPicker.Section
key={category.name}
title={category.name}
items={category.children}
>
{(item: any) => (
<FilterPicker.Item key={item.key} textValue={item.label}>
{item.label}
</FilterPicker.Item>
)}
</FilterPicker.Section>
)}
</FilterPicker>
);
},
parameters: {
docs: {
description: {
story:
'Sections and items can be defined dynamically using the `items` prop with a render function. Pass hierarchical data where each section contains a `children` array.',
},
},
},
};

export const CustomSummary: Story = {
args: {
label: 'Custom Summary Display',
Expand Down
50 changes: 42 additions & 8 deletions src/components/fields/ListBox/ListBox.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,33 @@ Groups related items together with an optional heading.
</ListBox>
```

### With Sections (Dynamic)

<Story of={ListBoxStories.DynamicSections} />

Sections and items can be defined dynamically using the `items` prop with a render function. Pass hierarchical data where each section contains a `children` array.

```jsx
const categories = [
{ name: 'Fruits', children: [
{ key: 'apple', label: 'Apple' },
{ key: 'banana', label: 'Banana' },
]},
{ name: 'Vegetables', children: [
{ key: 'carrot', label: 'Carrot' },
{ key: 'broccoli', label: 'Broccoli' },
]},
];

<ListBox label="Select food items" selectionMode="single" items={categories}>
{(category) => (
<ListBox.Section key={category.name} title={category.name} items={category.children}>
{(item) => <ListBox.Item key={item.key}>{item.label}</ListBox.Item>}
</ListBox.Section>
)}
</ListBox>
```

### Multiple Selection

<Story of={ListBoxStories.MultipleSelection} />
Expand Down Expand Up @@ -736,15 +763,22 @@ This component supports all [Field properties](/docs/getting-started-field-prope

### Virtualization

ListBox automatically enables virtualization when:
- No sections are present (sections disable virtualization)
- Provides smooth scrolling even with large datasets
- Only visible items are rendered in the DOM
- Uses `@tanstack/react-virtual` for efficient rendering
- Automatically measures item heights for consistent scrolling
- Supports items with varying heights (descriptions, complex content)
<Story of={ListBoxStories.VirtualizedList} />

For large datasets, use the `items` prop with a render function. This enables automatic virtualization — only visible items are rendered in the DOM, providing smooth scrolling even with thousands of items.

```jsx
const items = Array.from({ length: 1000 }, (_, i) => ({
key: `item-${i}`,
label: `Item ${i + 1}`,
}));

<ListBox label="Large Dataset" selectionMode="single" items={items}>
{(item) => <ListBox.Item key={item.key}>{item.label}</ListBox.Item>}
</ListBox>
```

**Note**: Virtualization is transparent to the user - no configuration needed. It works automatically when you have a flat list without sections.
**Note**: Virtualization is disabled when sections are present. For very large datasets, prefer a flat list structure.

### Optimization Tips

Expand Down
Loading
Loading