Skip to content

Commit fb2b3e6

Browse files
authored
feat(LayoutPanel): resizing on mobile devices (#1109)
1 parent 6a54171 commit fb2b3e6

File tree

13 files changed

+420
-8
lines changed

13 files changed

+420
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
Fix Layout.Panel resize handler not working properly on touch devices by adding `touch-action: none` to prevent browser scroll interference during drag

src/components/content/Layout/LayoutPanel.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ const ResizeHandlerElement = tasty({
176176
horizontal: 'col-resize',
177177
disabled: 'not-allowed',
178178
},
179+
touchAction: {
180+
'': 'none',
181+
disabled: 'auto',
182+
},
179183
padding: 0,
180184
outline: 0,
181185
boxSizing: 'border-box',

src/components/fields/ComboBox/ComboBox.docs.mdx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,33 @@ const [selectedKey, setSelectedKey] = useState(null);
296296
</ComboBox>
297297
```
298298

299+
### With Sections (Dynamic)
300+
301+
<Story of={ComboBoxStories.DynamicSections} />
302+
303+
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.
304+
305+
```jsx
306+
const categories = [
307+
{ name: 'Fruits', children: [
308+
{ key: 'apple', label: 'Apple' },
309+
{ key: 'banana', label: 'Banana' },
310+
]},
311+
{ name: 'Vegetables', children: [
312+
{ key: 'carrot', label: 'Carrot' },
313+
{ key: 'broccoli', label: 'Broccoli' },
314+
]},
315+
];
316+
317+
<ComboBox label="Food" placeholder="Select food..." items={categories}>
318+
{(category) => (
319+
<ComboBox.Section key={category.name} title={category.name} items={category.children}>
320+
{(item) => <ComboBox.Item key={item.key}>{item.label}</ComboBox.Item>}
321+
</ComboBox.Section>
322+
)}
323+
</ComboBox>
324+
```
325+
299326
### With Disabled Items
300327

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

443470
<Story of={ComboBoxStories.VirtualizedList} />
444471

472+
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.
473+
445474
```jsx
446475
const items = Array.from({ length: 1000 }, (_, i) => ({
447476
key: `item-${i}`,

src/components/fields/ComboBox/ComboBox.stories.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,43 @@ export const WithSections = () => (
516516
</ComboBox>
517517
);
518518

519+
export const DynamicSections = () => {
520+
const categories = [
521+
{
522+
name: 'Fruits',
523+
children: [
524+
{ key: 'apple', label: 'Apple' },
525+
{ key: 'banana', label: 'Banana' },
526+
{ key: 'cherry', label: 'Cherry' },
527+
],
528+
},
529+
{
530+
name: 'Vegetables',
531+
children: [
532+
{ key: 'carrot', label: 'Carrot' },
533+
{ key: 'broccoli', label: 'Broccoli' },
534+
{ key: 'spinach', label: 'Spinach' },
535+
],
536+
},
537+
];
538+
539+
return (
540+
<ComboBox label="Food" placeholder="Select food..." items={categories}>
541+
{(category: any) => (
542+
<ComboBox.Section
543+
key={category.name}
544+
title={category.name}
545+
items={category.children}
546+
>
547+
{(item: any) => (
548+
<ComboBox.Item key={item.key}>{item.label}</ComboBox.Item>
549+
)}
550+
</ComboBox.Section>
551+
)}
552+
</ComboBox>
553+
);
554+
};
555+
519556
export const WithDisabledItems = () => (
520557
<ComboBox
521558
label="Fruit"

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,33 @@ Groups related items together with an optional heading.
244244
</FilterListBox>
245245
```
246246

247+
### With Sections (Dynamic)
248+
249+
<Story of={FilterListBoxStories.DynamicSections} />
250+
251+
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.
252+
253+
```jsx
254+
const categories = [
255+
{ name: 'Fruits', children: [
256+
{ key: 'apple', label: 'Apple' },
257+
{ key: 'banana', label: 'Banana' },
258+
]},
259+
{ name: 'Vegetables', children: [
260+
{ key: 'carrot', label: 'Carrot' },
261+
{ key: 'broccoli', label: 'Broccoli' },
262+
]},
263+
];
264+
265+
<FilterListBox label="Choose an ingredient" searchPlaceholder="Search ingredients..." items={categories}>
266+
{(category) => (
267+
<FilterListBox.Section key={category.name} title={category.name} items={category.children}>
268+
{(item) => <FilterListBox.Item key={item.key}>{item.label}</FilterListBox.Item>}
269+
</FilterListBox.Section>
270+
)}
271+
</FilterListBox>
272+
```
273+
247274
### With Descriptions
248275

249276
<Story of={FilterListBoxStories.WithDescriptions} />

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,55 @@ WithSections.args = {
533533
searchPlaceholder: 'Search ingredients...',
534534
};
535535

536+
export const DynamicSections: StoryFn<CubeFilterListBoxProps<any>> = (args) => {
537+
const categories = [
538+
{
539+
name: 'Fruits',
540+
children: [
541+
{ key: 'apple', label: 'Apple' },
542+
{ key: 'banana', label: 'Banana' },
543+
{ key: 'cherry', label: 'Cherry' },
544+
],
545+
},
546+
{
547+
name: 'Vegetables',
548+
children: [
549+
{ key: 'carrot', label: 'Carrot' },
550+
{ key: 'broccoli', label: 'Broccoli' },
551+
{ key: 'spinach', label: 'Spinach' },
552+
],
553+
},
554+
{
555+
name: 'Herbs',
556+
children: [
557+
{ key: 'basil', label: 'Basil' },
558+
{ key: 'cilantro', label: 'Cilantro' },
559+
{ key: 'parsley', label: 'Parsley' },
560+
],
561+
},
562+
];
563+
564+
return (
565+
<FilterListBox {...args} items={categories}>
566+
{(category: any) => (
567+
<FilterListBox.Section
568+
key={category.name}
569+
title={category.name}
570+
items={category.children}
571+
>
572+
{(item: any) => (
573+
<FilterListBox.Item key={item.key}>{item.label}</FilterListBox.Item>
574+
)}
575+
</FilterListBox.Section>
576+
)}
577+
</FilterListBox>
578+
);
579+
};
580+
DynamicSections.args = {
581+
label: 'Choose an ingredient',
582+
searchPlaceholder: 'Search ingredients...',
583+
};
584+
536585
export const WithSectionsAndDescriptions: StoryFn<
537586
CubeFilterListBoxProps<any>
538587
> = (args) => (

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,40 @@ For large datasets or dynamic content, use the `items` prop with a render functi
242242
</FilterPicker>
243243
```
244244

245+
### Dynamic Sections Pattern
246+
247+
For hierarchical data with sections, use the `items` prop with nested render functions. Each section contains a `children` array of items.
248+
249+
```jsx
250+
const categories = [
251+
{ name: 'Fruits', children: [
252+
{ key: 'apple', label: 'Apple' },
253+
{ key: 'banana', label: 'Banana' },
254+
]},
255+
{ name: 'Vegetables', children: [
256+
{ key: 'carrot', label: 'Carrot' },
257+
{ key: 'broccoli', label: 'Broccoli' },
258+
]},
259+
];
260+
261+
<FilterPicker
262+
label="Choose items"
263+
placeholder="Choose items..."
264+
searchPlaceholder="Search..."
265+
items={categories}
266+
>
267+
{(category) => (
268+
<FilterPicker.Section key={category.name} title={category.name} items={category.children}>
269+
{(item) => (
270+
<FilterPicker.Item key={item.key} textValue={item.label}>
271+
{item.label}
272+
</FilterPicker.Item>
273+
)}
274+
</FilterPicker.Section>
275+
)}
276+
</FilterPicker>
277+
```
278+
245279
**Key Benefits:**
246280
- **Virtualization**: Automatically enabled for large lists without sections
247281
- **Performance**: Only renders visible items in the DOM

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,75 @@ export const WithSections: Story = {
686686
},
687687
};
688688

689+
export const DynamicSections: Story = {
690+
args: {
691+
label: 'Organized by Sections',
692+
placeholder: 'Choose items...',
693+
selectionMode: 'multiple',
694+
searchPlaceholder: 'Search ingredients...',
695+
width: 'max 30x',
696+
},
697+
play: async ({ canvasElement }) => {
698+
const canvas = within(canvasElement);
699+
const trigger = canvas.getByRole('button');
700+
await userEvent.click(trigger);
701+
},
702+
render: (args) => {
703+
const categories = [
704+
{
705+
name: 'Fruits',
706+
children: [
707+
{ key: 'apple', label: 'Apple' },
708+
{ key: 'banana', label: 'Banana' },
709+
{ key: 'cherry', label: 'Cherry' },
710+
],
711+
},
712+
{
713+
name: 'Vegetables',
714+
children: [
715+
{ key: 'carrot', label: 'Carrot' },
716+
{ key: 'broccoli', label: 'Broccoli' },
717+
{ key: 'spinach', label: 'Spinach' },
718+
],
719+
},
720+
{
721+
name: 'Grains',
722+
children: [
723+
{ key: 'rice', label: 'Rice' },
724+
{ key: 'oats', label: 'Oats' },
725+
{ key: 'barley', label: 'Barley' },
726+
],
727+
},
728+
];
729+
730+
return (
731+
<FilterPicker {...args} items={categories}>
732+
{(category: any) => (
733+
<FilterPicker.Section
734+
key={category.name}
735+
title={category.name}
736+
items={category.children}
737+
>
738+
{(item: any) => (
739+
<FilterPicker.Item key={item.key} textValue={item.label}>
740+
{item.label}
741+
</FilterPicker.Item>
742+
)}
743+
</FilterPicker.Section>
744+
)}
745+
</FilterPicker>
746+
);
747+
},
748+
parameters: {
749+
docs: {
750+
description: {
751+
story:
752+
'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.',
753+
},
754+
},
755+
},
756+
};
757+
689758
export const CustomSummary: Story = {
690759
args: {
691760
label: 'Custom Summary Display',

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

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,33 @@ Groups related items together with an optional heading.
327327
</ListBox>
328328
```
329329

330+
### With Sections (Dynamic)
331+
332+
<Story of={ListBoxStories.DynamicSections} />
333+
334+
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.
335+
336+
```jsx
337+
const categories = [
338+
{ name: 'Fruits', children: [
339+
{ key: 'apple', label: 'Apple' },
340+
{ key: 'banana', label: 'Banana' },
341+
]},
342+
{ name: 'Vegetables', children: [
343+
{ key: 'carrot', label: 'Carrot' },
344+
{ key: 'broccoli', label: 'Broccoli' },
345+
]},
346+
];
347+
348+
<ListBox label="Select food items" selectionMode="single" items={categories}>
349+
{(category) => (
350+
<ListBox.Section key={category.name} title={category.name} items={category.children}>
351+
{(item) => <ListBox.Item key={item.key}>{item.label}</ListBox.Item>}
352+
</ListBox.Section>
353+
)}
354+
</ListBox>
355+
```
356+
330357
### Multiple Selection
331358

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

737764
### Virtualization
738765

739-
ListBox automatically enables virtualization when:
740-
- No sections are present (sections disable virtualization)
741-
- Provides smooth scrolling even with large datasets
742-
- Only visible items are rendered in the DOM
743-
- Uses `@tanstack/react-virtual` for efficient rendering
744-
- Automatically measures item heights for consistent scrolling
745-
- Supports items with varying heights (descriptions, complex content)
766+
<Story of={ListBoxStories.VirtualizedList} />
767+
768+
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.
769+
770+
```jsx
771+
const items = Array.from({ length: 1000 }, (_, i) => ({
772+
key: `item-${i}`,
773+
label: `Item ${i + 1}`,
774+
}));
775+
776+
<ListBox label="Large Dataset" selectionMode="single" items={items}>
777+
{(item) => <ListBox.Item key={item.key}>{item.label}</ListBox.Item>}
778+
</ListBox>
779+
```
746780

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

749783
### Optimization Tips
750784

0 commit comments

Comments
 (0)