@dxos/react-ui-list 0.9.0 → 0.9.1-main.c7dcc2e112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +993 -521
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +993 -521
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/aspects/index.d.ts +6 -0
- package/dist/types/src/aspects/index.d.ts.map +1 -0
- package/dist/types/src/aspects/useListDisclosure.d.ts +60 -0
- package/dist/types/src/aspects/useListDisclosure.d.ts.map +1 -0
- package/dist/types/src/aspects/useListDisclosure.test.d.ts +2 -0
- package/dist/types/src/aspects/useListDisclosure.test.d.ts.map +1 -0
- package/dist/types/src/aspects/useListGrid.d.ts +30 -0
- package/dist/types/src/aspects/useListGrid.d.ts.map +1 -0
- package/dist/types/src/aspects/useListGrid.test.d.ts +2 -0
- package/dist/types/src/aspects/useListGrid.test.d.ts.map +1 -0
- package/dist/types/src/aspects/useListNavigation.d.ts +68 -0
- package/dist/types/src/aspects/useListNavigation.d.ts.map +1 -0
- package/dist/types/src/aspects/useListNavigation.test.d.ts +2 -0
- package/dist/types/src/aspects/useListNavigation.test.d.ts.map +1 -0
- package/dist/types/src/aspects/useListSelection.d.ts +48 -0
- package/dist/types/src/aspects/useListSelection.d.ts.map +1 -0
- package/dist/types/src/aspects/useListSelection.test.d.ts +2 -0
- package/dist/types/src/aspects/useListSelection.test.d.ts.map +1 -0
- package/dist/types/src/aspects/useReorder.d.ts +103 -0
- package/dist/types/src/aspects/useReorder.d.ts.map +1 -0
- package/dist/types/src/components/Accordion/Accordion.d.ts +1 -1
- package/dist/types/src/components/Accordion/AccordionItem.d.ts +5 -3
- package/dist/types/src/components/Accordion/AccordionItem.d.ts.map +1 -1
- package/dist/types/src/components/Accordion/AccordionRoot.d.ts +1 -1
- package/dist/types/src/components/Accordion/AccordionRoot.d.ts.map +1 -1
- package/dist/types/src/components/Listbox/Listbox.d.ts +60 -20
- package/dist/types/src/components/Listbox/Listbox.d.ts.map +1 -1
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts +27 -3
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts.map +1 -1
- package/dist/types/src/components/OrderedList/OrderedList.d.ts +49 -0
- package/dist/types/src/components/OrderedList/OrderedList.d.ts.map +1 -0
- package/dist/types/src/components/OrderedList/OrderedList.stories.d.ts +11 -0
- package/dist/types/src/components/OrderedList/OrderedList.stories.d.ts.map +1 -0
- package/dist/types/src/components/OrderedList/OrderedList.test.d.ts +2 -0
- package/dist/types/src/components/OrderedList/OrderedList.test.d.ts.map +1 -0
- package/dist/types/src/components/OrderedList/OrderedListItem.d.ts +94 -0
- package/dist/types/src/components/OrderedList/OrderedListItem.d.ts.map +1 -0
- package/dist/types/src/components/OrderedList/OrderedListRoot.d.ts +73 -0
- package/dist/types/src/components/OrderedList/OrderedListRoot.d.ts.map +1 -0
- package/dist/types/src/components/OrderedList/index.d.ts +2 -0
- package/dist/types/src/components/OrderedList/index.d.ts.map +1 -0
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/vitest-setup.d.ts +2 -0
- package/dist/types/src/vitest-setup.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -15
- package/src/aspects/index.ts +9 -0
- package/src/aspects/useListDisclosure.test.ts +72 -0
- package/src/aspects/useListDisclosure.ts +160 -0
- package/src/aspects/useListGrid.test.ts +41 -0
- package/src/aspects/useListGrid.ts +61 -0
- package/src/aspects/useListNavigation.test.ts +44 -0
- package/src/aspects/useListNavigation.ts +160 -0
- package/src/aspects/useListSelection.test.ts +101 -0
- package/src/aspects/useListSelection.ts +162 -0
- package/src/aspects/useReorder.ts +370 -0
- package/src/components/Accordion/Accordion.stories.tsx +1 -1
- package/src/components/Accordion/AccordionItem.tsx +11 -6
- package/src/components/Accordion/AccordionRoot.tsx +4 -1
- package/src/components/Listbox/Listbox.stories.tsx +171 -21
- package/src/components/Listbox/Listbox.tsx +302 -145
- package/src/components/OrderedList/OrderedList.stories.tsx +379 -0
- package/src/components/OrderedList/OrderedList.test.tsx +59 -0
- package/src/components/OrderedList/OrderedList.tsx +63 -0
- package/src/components/OrderedList/OrderedListItem.tsx +348 -0
- package/src/components/OrderedList/OrderedListRoot.tsx +173 -0
- package/src/components/OrderedList/index.ts +5 -0
- package/src/components/Tree/TreeItem.tsx +2 -0
- package/src/components/Tree/TreeItemHeading.tsx +1 -2
- package/src/components/index.ts +1 -2
- package/src/index.ts +1 -0
- package/src/vitest-setup.ts +11 -0
- package/dist/types/src/components/List/List.d.ts +0 -40
- package/dist/types/src/components/List/List.d.ts.map +0 -1
- package/dist/types/src/components/List/List.stories.d.ts +0 -18
- package/dist/types/src/components/List/List.stories.d.ts.map +0 -1
- package/dist/types/src/components/List/ListItem.d.ts +0 -49
- package/dist/types/src/components/List/ListItem.d.ts.map +0 -1
- package/dist/types/src/components/List/ListRoot.d.ts +0 -29
- package/dist/types/src/components/List/ListRoot.d.ts.map +0 -1
- package/dist/types/src/components/List/index.d.ts +0 -2
- package/dist/types/src/components/List/index.d.ts.map +0 -1
- package/dist/types/src/components/List/testing.d.ts +0 -15
- package/dist/types/src/components/List/testing.d.ts.map +0 -1
- package/dist/types/src/components/RowList/RowList.d.ts +0 -61
- package/dist/types/src/components/RowList/RowList.d.ts.map +0 -1
- package/dist/types/src/components/RowList/RowList.stories.d.ts +0 -35
- package/dist/types/src/components/RowList/RowList.stories.d.ts.map +0 -1
- package/dist/types/src/components/RowList/index.d.ts +0 -3
- package/dist/types/src/components/RowList/index.d.ts.map +0 -1
- package/src/components/List/List.stories.tsx +0 -129
- package/src/components/List/List.tsx +0 -47
- package/src/components/List/ListItem.tsx +0 -287
- package/src/components/List/ListRoot.tsx +0 -106
- package/src/components/List/index.ts +0 -5
- package/src/components/List/testing.ts +0 -31
- package/src/components/RowList/RowList.stories.tsx +0 -163
- package/src/components/RowList/RowList.tsx +0 -350
- package/src/components/RowList/index.ts +0 -6
|
@@ -9,7 +9,10 @@ import React, { type ReactNode } from 'react';
|
|
|
9
9
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
10
10
|
import { mx } from '@dxos/ui-theme';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// Records flowing through this compound carry caller-defined shapes (any record with an
|
|
13
|
+
// optional `id` is acceptable; `getId` defaults to reading `.id`). Typed as `any` so the
|
|
14
|
+
// generic parameter remains the caller's source of truth.
|
|
15
|
+
type ListItemRecord = any;
|
|
13
16
|
|
|
14
17
|
type AccordionContext<T extends ListItemRecord> = {
|
|
15
18
|
getId: (item: T) => string;
|
|
@@ -1,48 +1,198 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { random } from '@dxos/random';
|
|
9
|
+
import { Input, Panel, Toolbar } from '@dxos/react-ui';
|
|
9
10
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
11
|
|
|
11
12
|
import { Listbox } from './Listbox';
|
|
12
13
|
|
|
13
|
-
random.seed(
|
|
14
|
+
random.seed(1);
|
|
14
15
|
|
|
15
|
-
type
|
|
16
|
+
type TestItem = { id: string; name: string; description: string };
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
18
|
+
const allItems: TestItem[] = Array.from({ length: 24 }, (_, i) => ({
|
|
19
|
+
id: `item-${i}`,
|
|
20
|
+
name: random.commerce.productName(),
|
|
21
|
+
description: random.lorem.sentences(2),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
//
|
|
25
|
+
// Configurable basic story (Default / Thin / WithDisabled / Compact share this body).
|
|
26
|
+
//
|
|
27
|
+
|
|
28
|
+
type StoryArgs = {
|
|
29
|
+
/** Items to render. Defaults to the full 24-item catalog. */
|
|
30
|
+
items?: TestItem[];
|
|
31
|
+
/** Forwards to `Listbox.Viewport thin`. */
|
|
32
|
+
thin?: boolean;
|
|
33
|
+
/** Forwards to `Listbox.Viewport padding`. */
|
|
34
|
+
padding?: boolean;
|
|
35
|
+
/** Index into `items` that should render disabled. */
|
|
36
|
+
disabledIndex?: number;
|
|
37
|
+
/** Render the description line under each row's name. */
|
|
38
|
+
showDescription?: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DefaultStory = ({
|
|
42
|
+
items = allItems,
|
|
43
|
+
thin = false,
|
|
44
|
+
padding = false,
|
|
45
|
+
disabledIndex,
|
|
46
|
+
showDescription = true,
|
|
47
|
+
}: StoryArgs = {}) => {
|
|
48
|
+
const [selected, setSelected] = useState<string | undefined>(items[0]?.id);
|
|
49
|
+
return (
|
|
50
|
+
<Listbox.Root value={selected} onValueChange={setSelected}>
|
|
51
|
+
<Listbox.Viewport thin={thin} padding={padding}>
|
|
52
|
+
<Listbox.Content aria-label='Items'>
|
|
53
|
+
{items.map((item, i) => {
|
|
54
|
+
const disabled = i === disabledIndex;
|
|
55
|
+
return (
|
|
56
|
+
<Listbox.Item key={item.id} id={item.id} disabled={disabled}>
|
|
57
|
+
<div className='flex flex-col gap-0.5 overflow-hidden'>
|
|
58
|
+
<div className='font-medium'>
|
|
59
|
+
{item.name}
|
|
60
|
+
{disabled && ' (disabled)'}
|
|
61
|
+
</div>
|
|
62
|
+
{showDescription && <div className='text-sm text-description line-clamp-1'>{item.description}</div>}
|
|
63
|
+
</div>
|
|
64
|
+
</Listbox.Item>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</Listbox.Content>
|
|
68
|
+
</Listbox.Viewport>
|
|
69
|
+
</Listbox.Root>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
//
|
|
74
|
+
// Master/detail — list is one pane of a layout.
|
|
75
|
+
//
|
|
76
|
+
|
|
77
|
+
const MasterDetailStory = () => {
|
|
78
|
+
const [selected, setSelected] = useState<string | undefined>(allItems[0].id);
|
|
79
|
+
const detail = allItems.find(({ id }) => id === selected);
|
|
80
|
+
return (
|
|
81
|
+
<div className='dx-container grid grid-cols-[20rem_1fr] divide-x divide-separator'>
|
|
82
|
+
<Listbox.Root value={selected} onValueChange={setSelected}>
|
|
83
|
+
<Listbox.Viewport>
|
|
84
|
+
<Listbox.Content aria-label='Items'>
|
|
85
|
+
{allItems.map((item) => (
|
|
86
|
+
<Listbox.Item key={item.id} id={item.id}>
|
|
87
|
+
<div className='font-medium'>{item.name}</div>
|
|
88
|
+
</Listbox.Item>
|
|
89
|
+
))}
|
|
90
|
+
</Listbox.Content>
|
|
91
|
+
</Listbox.Viewport>
|
|
92
|
+
</Listbox.Root>
|
|
93
|
+
<div role='region' aria-label='Detail' className='dx-container p-4 overflow-auto'>
|
|
94
|
+
{detail && (
|
|
95
|
+
<>
|
|
96
|
+
<h2 className='text-lg font-semibold'>{detail.name}</h2>
|
|
97
|
+
<p className='text-description mt-2'>{detail.description}</p>
|
|
98
|
+
</>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
21
104
|
|
|
22
|
-
|
|
23
|
-
|
|
105
|
+
//
|
|
106
|
+
// Toolbar + viewport siblings — Root is headless, so layout is the caller's responsibility.
|
|
107
|
+
// `Panel` is the canonical chrome wrapper.
|
|
108
|
+
//
|
|
24
109
|
|
|
110
|
+
const WithToolbarStory = () => {
|
|
111
|
+
const [selected, setSelected] = useState<string | undefined>(allItems[0].id);
|
|
112
|
+
const [filter, setFilter] = useState('');
|
|
113
|
+
const filtered = allItems.filter((item) => item.name.toLowerCase().includes(filter.toLowerCase()));
|
|
25
114
|
return (
|
|
26
|
-
<Listbox.Root value={
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
115
|
+
<Listbox.Root value={selected} onValueChange={setSelected}>
|
|
116
|
+
<Panel.Root>
|
|
117
|
+
<Panel.Toolbar asChild>
|
|
118
|
+
<Toolbar.Root>
|
|
119
|
+
<Input.Root>
|
|
120
|
+
<Input.Label srOnly>Filter items</Input.Label>
|
|
121
|
+
<Input.TextInput
|
|
122
|
+
placeholder='Filter…'
|
|
123
|
+
value={filter}
|
|
124
|
+
onChange={(event) => setFilter(event.target.value)}
|
|
125
|
+
/>
|
|
126
|
+
</Input.Root>
|
|
127
|
+
</Toolbar.Root>
|
|
128
|
+
</Panel.Toolbar>
|
|
129
|
+
<Panel.Content asChild>
|
|
130
|
+
<Listbox.Viewport>
|
|
131
|
+
<Listbox.Content aria-label='Items'>
|
|
132
|
+
{filtered.map((item) => (
|
|
133
|
+
<Listbox.Item key={item.id} id={item.id}>
|
|
134
|
+
{item.name}
|
|
135
|
+
</Listbox.Item>
|
|
136
|
+
))}
|
|
137
|
+
</Listbox.Content>
|
|
138
|
+
</Listbox.Viewport>
|
|
139
|
+
</Panel.Content>
|
|
140
|
+
</Panel.Root>
|
|
33
141
|
</Listbox.Root>
|
|
34
142
|
);
|
|
35
143
|
};
|
|
36
144
|
|
|
145
|
+
//
|
|
146
|
+
// Popover variant — no Viewport (caller's popover/dialog owns scroll). Uses the
|
|
147
|
+
// `Listbox.ItemLabel` + `Listbox.Indicator` slots for a confirmatory checkmark.
|
|
148
|
+
//
|
|
149
|
+
|
|
150
|
+
type Option = { value: string; label: string };
|
|
151
|
+
|
|
152
|
+
const popoverOptions: Option[] = random.helpers.multiple(
|
|
153
|
+
() => ({ value: random.string.uuid(), label: random.commerce.productName() }) satisfies Option,
|
|
154
|
+
{ count: 8 },
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const PopoverStory = () => {
|
|
158
|
+
const [selected, setSelected] = useState<string | undefined>(popoverOptions[0]?.value);
|
|
159
|
+
return (
|
|
160
|
+
<div className='max-w-xs p-2 ring-1 ring-subdued-separator rounded'>
|
|
161
|
+
<Listbox.Root value={selected} onValueChange={setSelected}>
|
|
162
|
+
<Listbox.Content aria-label='Models'>
|
|
163
|
+
{popoverOptions.map((option) => (
|
|
164
|
+
<Listbox.Item
|
|
165
|
+
key={option.value}
|
|
166
|
+
id={option.value}
|
|
167
|
+
// Compact / popover styling (the previous standalone `Listbox.Option` look).
|
|
168
|
+
classNames='px-2 py-1 dx-focus-ring rounded-xs'
|
|
169
|
+
>
|
|
170
|
+
<Listbox.ItemLabel>{option.label}</Listbox.ItemLabel>
|
|
171
|
+
<Listbox.Indicator />
|
|
172
|
+
</Listbox.Item>
|
|
173
|
+
))}
|
|
174
|
+
</Listbox.Content>
|
|
175
|
+
</Listbox.Root>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
37
180
|
const meta = {
|
|
38
181
|
title: 'ui/react-ui-list/Listbox',
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
182
|
+
render: (args) => <DefaultStory {...args} />,
|
|
183
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
184
|
+
parameters: {
|
|
185
|
+
layout: 'fullscreen',
|
|
186
|
+
},
|
|
187
|
+
} satisfies Meta<StoryArgs>;
|
|
43
188
|
|
|
44
189
|
export default meta;
|
|
45
190
|
|
|
46
|
-
type Story = StoryObj<
|
|
191
|
+
type Story = StoryObj<StoryArgs>;
|
|
47
192
|
|
|
48
193
|
export const Default: Story = {};
|
|
194
|
+
export const Thin: Story = { args: { thin: true, padding: true, showDescription: false } };
|
|
195
|
+
export const WithDisabled: Story = { args: { items: allItems.slice(0, 6), disabledIndex: 2 } };
|
|
196
|
+
export const MasterDetail: Story = { render: () => <MasterDetailStory /> };
|
|
197
|
+
export const WithToolbar: Story = { render: () => <WithToolbarStory /> };
|
|
198
|
+
export const Popover: Story = { render: () => <PopoverStory /> };
|