@tribepad/themis 1.4.0 → 1.5.1
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/CHANGELOG.md +31 -0
- package/dist/elements/Breadcrumbs/index.js +1 -1
- package/dist/elements/Breadcrumbs/index.js.map +1 -1
- package/dist/elements/Breadcrumbs/index.mjs +1 -1
- package/dist/elements/Breadcrumbs/index.mjs.map +1 -1
- package/dist/elements/PageTitle/index.js +1 -1
- package/dist/elements/PageTitle/index.js.map +1 -1
- package/dist/elements/PageTitle/index.mjs +1 -1
- package/dist/elements/PageTitle/index.mjs.map +1 -1
- package/dist/elements/Sidebar/Sidebar.styles.d.ts.map +1 -1
- package/dist/elements/Sidebar/Sidebar.types.d.ts +13 -3
- package/dist/elements/Sidebar/Sidebar.types.d.ts.map +1 -1
- package/dist/elements/Sidebar/SidebarPanel.d.ts.map +1 -1
- package/dist/elements/Sidebar/index.js +1 -1
- package/dist/elements/Sidebar/index.js.map +1 -1
- package/dist/elements/Sidebar/index.mjs +1 -1
- package/dist/elements/Sidebar/index.mjs.map +1 -1
- package/dist/elements/Tabs/Tabs.d.ts +19 -5
- package/dist/elements/Tabs/Tabs.d.ts.map +1 -1
- package/dist/elements/Tabs/Tabs.types.d.ts +28 -1
- package/dist/elements/Tabs/Tabs.types.d.ts.map +1 -1
- package/dist/elements/Tabs/index.js +1 -1
- package/dist/elements/Tabs/index.js.map +1 -1
- package/dist/elements/Tabs/index.mjs +1 -1
- package/dist/elements/Tabs/index.mjs.map +1 -1
- package/dist/elements/Tree/Tree.d.ts +62 -0
- package/dist/elements/Tree/Tree.d.ts.map +1 -0
- package/dist/elements/Tree/Tree.styles.d.ts +28 -0
- package/dist/elements/Tree/Tree.styles.d.ts.map +1 -0
- package/dist/elements/Tree/Tree.types.d.ts +161 -0
- package/dist/elements/Tree/Tree.types.d.ts.map +1 -0
- package/dist/elements/Tree/index.d.ts +33 -0
- package/dist/elements/Tree/index.d.ts.map +1 -0
- package/dist/elements/Tree/index.js +3 -0
- package/dist/elements/Tree/index.js.map +1 -0
- package/dist/elements/Tree/index.mjs +3 -0
- package/dist/elements/Tree/index.mjs.map +1 -0
- package/dist/elements/index.d.ts +2 -0
- package/dist/elements/index.d.ts.map +1 -1
- package/dist/elements/index.js +1 -1
- package/dist/elements/index.js.map +1 -1
- package/dist/elements/index.mjs +1 -1
- package/dist/elements/index.mjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles/animations.css +1 -1
- package/package.json +1 -1
- package/src/elements/Tabs/Tabs.stories.tsx +204 -3
- package/src/elements/Tree/Tree.stories.tsx +407 -0
package/package.json
CHANGED
|
@@ -26,8 +26,8 @@ const meta = {
|
|
|
26
26
|
argTypes: {
|
|
27
27
|
variant: {
|
|
28
28
|
control: 'select',
|
|
29
|
-
options: ['default', 'block', 'stepper'],
|
|
30
|
-
description: 'Visual variant: underline (default), block
|
|
29
|
+
options: ['default', 'block', 'pill', 'stepper'],
|
|
30
|
+
description: 'Visual variant: underline (default), block (segmented), pill (independent rounded tabs), or stepper (numbered steps)',
|
|
31
31
|
},
|
|
32
32
|
orientation: {
|
|
33
33
|
control: 'select',
|
|
@@ -48,6 +48,11 @@ const meta = {
|
|
|
48
48
|
options: ['line', 'chevron'],
|
|
49
49
|
description: 'Stepper separator style (horizontal stepper only)',
|
|
50
50
|
},
|
|
51
|
+
visualSize: {
|
|
52
|
+
control: 'select',
|
|
53
|
+
options: ['sm', 'default', 'lg'],
|
|
54
|
+
description: 'Layer 2 visual size (pill variant only). Layer 1 is always 44×44px.',
|
|
55
|
+
},
|
|
51
56
|
},
|
|
52
57
|
} satisfies Meta<typeof Tabs>;
|
|
53
58
|
|
|
@@ -98,7 +103,7 @@ export const Default: Story = {
|
|
|
98
103
|
};
|
|
99
104
|
|
|
100
105
|
/**
|
|
101
|
-
* Block:
|
|
106
|
+
* Block: Segmented control with accent background container and card-style active tab
|
|
102
107
|
*/
|
|
103
108
|
export const Block: Story = {
|
|
104
109
|
render: () => (
|
|
@@ -136,6 +141,202 @@ export const Block: Story = {
|
|
|
136
141
|
),
|
|
137
142
|
};
|
|
138
143
|
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Pill Variant Stories
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Pill: Independent rounded tabs. Selected tab fills with primary-action colour.
|
|
150
|
+
* Each tab stands alone with a small gap between — unlike the segmented Block variant.
|
|
151
|
+
*/
|
|
152
|
+
export const Pill: Story = {
|
|
153
|
+
render: () => (
|
|
154
|
+
<Tabs variant="pill" defaultSelectedKey="account" className="w-[400px]">
|
|
155
|
+
<TabList aria-label="Account settings">
|
|
156
|
+
<Tab id="account">Account</Tab>
|
|
157
|
+
<Tab id="password">Password</Tab>
|
|
158
|
+
<Tab id="settings">Settings</Tab>
|
|
159
|
+
</TabList>
|
|
160
|
+
<TabPanel id="account">
|
|
161
|
+
<div className="p-4">
|
|
162
|
+
<h3 className="text-lg font-semibold mb-2">Account Information</h3>
|
|
163
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
164
|
+
Manage your account details and preferences.
|
|
165
|
+
</p>
|
|
166
|
+
</div>
|
|
167
|
+
</TabPanel>
|
|
168
|
+
<TabPanel id="password">
|
|
169
|
+
<div className="p-4">
|
|
170
|
+
<h3 className="text-lg font-semibold mb-2">Password Settings</h3>
|
|
171
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
172
|
+
Update your password and security settings.
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
</TabPanel>
|
|
176
|
+
<TabPanel id="settings">
|
|
177
|
+
<div className="p-4">
|
|
178
|
+
<h3 className="text-lg font-semibold mb-2">General Settings</h3>
|
|
179
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
180
|
+
Configure application preferences.
|
|
181
|
+
</p>
|
|
182
|
+
</div>
|
|
183
|
+
</TabPanel>
|
|
184
|
+
</Tabs>
|
|
185
|
+
),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Pill with composed content: a language switcher with code chip and "Default" badge,
|
|
190
|
+
* matching the email-template editor pattern. Uses default visualSize="sm" (28px).
|
|
191
|
+
*/
|
|
192
|
+
export const PillWithBadges: Story = {
|
|
193
|
+
render: () => (
|
|
194
|
+
<Tabs variant="pill" defaultSelectedKey="en" className="w-[500px]">
|
|
195
|
+
<TabList aria-label="Template language">
|
|
196
|
+
<Tab id="en">
|
|
197
|
+
<span className="rounded bg-black/20 px-1 py-px text-[10px] font-bold leading-none">
|
|
198
|
+
EN
|
|
199
|
+
</span>
|
|
200
|
+
<span>English</span>
|
|
201
|
+
<span className="rounded-sm bg-white/20 px-1 py-px text-[9px] font-medium uppercase tracking-wide leading-none">
|
|
202
|
+
Default
|
|
203
|
+
</span>
|
|
204
|
+
</Tab>
|
|
205
|
+
<Tab id="fr">
|
|
206
|
+
<span className="rounded bg-black/20 px-1 py-px text-[10px] font-bold leading-none">
|
|
207
|
+
FR
|
|
208
|
+
</span>
|
|
209
|
+
<span>French</span>
|
|
210
|
+
</Tab>
|
|
211
|
+
</TabList>
|
|
212
|
+
<TabPanel id="en">
|
|
213
|
+
<div className="p-4 text-sm">English template content</div>
|
|
214
|
+
</TabPanel>
|
|
215
|
+
<TabPanel id="fr">
|
|
216
|
+
<div className="p-4 text-sm">French template content</div>
|
|
217
|
+
</TabPanel>
|
|
218
|
+
</Tabs>
|
|
219
|
+
),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Pill sizes: Layer 1 is always 44×44px (the transparent touch target);
|
|
224
|
+
* `visualSize` controls Layer 2 only — sm (28px), default (36px), lg (44px).
|
|
225
|
+
*/
|
|
226
|
+
export const PillSizes: Story = {
|
|
227
|
+
render: () => (
|
|
228
|
+
<div className="flex flex-col gap-6">
|
|
229
|
+
<div>
|
|
230
|
+
<p className="mb-2 text-xs font-medium text-[var(--muted-foreground)]">
|
|
231
|
+
visualSize="sm" — 28px (default for pill)
|
|
232
|
+
</p>
|
|
233
|
+
<Tabs variant="pill" visualSize="sm" defaultSelectedKey="en">
|
|
234
|
+
<TabList aria-label="Languages — sm">
|
|
235
|
+
<Tab id="en">English</Tab>
|
|
236
|
+
<Tab id="fr">French</Tab>
|
|
237
|
+
</TabList>
|
|
238
|
+
<TabPanel id="en" />
|
|
239
|
+
<TabPanel id="fr" />
|
|
240
|
+
</Tabs>
|
|
241
|
+
</div>
|
|
242
|
+
<div>
|
|
243
|
+
<p className="mb-2 text-xs font-medium text-[var(--muted-foreground)]">
|
|
244
|
+
visualSize="default" — 36px
|
|
245
|
+
</p>
|
|
246
|
+
<Tabs variant="pill" visualSize="default" defaultSelectedKey="en">
|
|
247
|
+
<TabList aria-label="Languages — default">
|
|
248
|
+
<Tab id="en">English</Tab>
|
|
249
|
+
<Tab id="fr">French</Tab>
|
|
250
|
+
</TabList>
|
|
251
|
+
<TabPanel id="en" />
|
|
252
|
+
<TabPanel id="fr" />
|
|
253
|
+
</Tabs>
|
|
254
|
+
</div>
|
|
255
|
+
<div>
|
|
256
|
+
<p className="mb-2 text-xs font-medium text-[var(--muted-foreground)]">
|
|
257
|
+
visualSize="lg" — 44px (fills touch target)
|
|
258
|
+
</p>
|
|
259
|
+
<Tabs variant="pill" visualSize="lg" defaultSelectedKey="en">
|
|
260
|
+
<TabList aria-label="Languages — lg">
|
|
261
|
+
<Tab id="en">English</Tab>
|
|
262
|
+
<Tab id="fr">French</Tab>
|
|
263
|
+
</TabList>
|
|
264
|
+
<TabPanel id="en" />
|
|
265
|
+
<TabPanel id="fr" />
|
|
266
|
+
</Tabs>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Pill (vertical): pills stack with a gap, useful for sidebar navigation.
|
|
274
|
+
*/
|
|
275
|
+
export const PillVertical: Story = {
|
|
276
|
+
render: () => (
|
|
277
|
+
<Tabs
|
|
278
|
+
variant="pill"
|
|
279
|
+
orientation="vertical"
|
|
280
|
+
defaultSelectedKey="account"
|
|
281
|
+
className="w-[600px]"
|
|
282
|
+
>
|
|
283
|
+
<div className="flex gap-6">
|
|
284
|
+
<TabList aria-label="Account settings" className="w-[180px]">
|
|
285
|
+
<Tab id="account">Account</Tab>
|
|
286
|
+
<Tab id="password">Password</Tab>
|
|
287
|
+
<Tab id="settings">Settings</Tab>
|
|
288
|
+
</TabList>
|
|
289
|
+
<div className="flex-1">
|
|
290
|
+
<TabPanel id="account">
|
|
291
|
+
<h3 className="text-lg font-semibold mb-2">Account</h3>
|
|
292
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
293
|
+
Manage your account details.
|
|
294
|
+
</p>
|
|
295
|
+
</TabPanel>
|
|
296
|
+
<TabPanel id="password">
|
|
297
|
+
<h3 className="text-lg font-semibold mb-2">Password</h3>
|
|
298
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
299
|
+
Update password.
|
|
300
|
+
</p>
|
|
301
|
+
</TabPanel>
|
|
302
|
+
<TabPanel id="settings">
|
|
303
|
+
<h3 className="text-lg font-semibold mb-2">Settings</h3>
|
|
304
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
305
|
+
General preferences.
|
|
306
|
+
</p>
|
|
307
|
+
</TabPanel>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</Tabs>
|
|
311
|
+
),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Pill with a disabled tab.
|
|
316
|
+
*/
|
|
317
|
+
export const PillDisabled: Story = {
|
|
318
|
+
render: () => (
|
|
319
|
+
<Tabs variant="pill" defaultSelectedKey="en" className="w-[400px]">
|
|
320
|
+
<TabList aria-label="Languages">
|
|
321
|
+
<Tab id="en">English</Tab>
|
|
322
|
+
<Tab id="fr">French</Tab>
|
|
323
|
+
<Tab id="de" isDisabled>
|
|
324
|
+
German
|
|
325
|
+
</Tab>
|
|
326
|
+
</TabList>
|
|
327
|
+
<TabPanel id="en">
|
|
328
|
+
<div className="p-4 text-sm">English content</div>
|
|
329
|
+
</TabPanel>
|
|
330
|
+
<TabPanel id="fr">
|
|
331
|
+
<div className="p-4 text-sm">French content</div>
|
|
332
|
+
</TabPanel>
|
|
333
|
+
<TabPanel id="de">
|
|
334
|
+
<div className="p-4 text-sm">German content</div>
|
|
335
|
+
</TabPanel>
|
|
336
|
+
</Tabs>
|
|
337
|
+
),
|
|
338
|
+
};
|
|
339
|
+
|
|
139
340
|
// ============================================================================
|
|
140
341
|
// Stepper Variant Stories
|
|
141
342
|
// ============================================================================
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { useState, type ReactElement } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import {
|
|
4
|
+
Tree,
|
|
5
|
+
TreeItem,
|
|
6
|
+
TreeItemContent,
|
|
7
|
+
TreeItemChevron,
|
|
8
|
+
} from './Tree';
|
|
9
|
+
import type { Selection } from './Tree.types';
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'Elements/Tree',
|
|
13
|
+
component: Tree,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'padded',
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component:
|
|
20
|
+
'Accessible hierarchical list built on React Aria `Tree`. Supports keyboard navigation (Arrow keys, Home/End, type-ahead), expansion, single/multiple selection, lazy children via `hasChildItems`, and custom row content.',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
argTypes: {
|
|
25
|
+
selectionMode: {
|
|
26
|
+
control: 'select',
|
|
27
|
+
options: ['none', 'single', 'multiple'],
|
|
28
|
+
},
|
|
29
|
+
variant: {
|
|
30
|
+
control: 'select',
|
|
31
|
+
options: ['default', 'bordered'],
|
|
32
|
+
},
|
|
33
|
+
size: {
|
|
34
|
+
control: 'select',
|
|
35
|
+
options: ['sm', 'default', 'lg'],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
} satisfies Meta<typeof Tree>;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof Tree>;
|
|
42
|
+
|
|
43
|
+
function Sample(): ReactElement {
|
|
44
|
+
return (
|
|
45
|
+
<Tree aria-label="Departments" className="max-w-md">
|
|
46
|
+
<TreeItem id="eng" textValue="Engineering" hasChildItems>
|
|
47
|
+
<TreeItemContent>
|
|
48
|
+
<TreeItemChevron />
|
|
49
|
+
<span>Engineering</span>
|
|
50
|
+
</TreeItemContent>
|
|
51
|
+
<TreeItem id="eng.platform" textValue="Platform" hasChildItems>
|
|
52
|
+
<TreeItemContent>
|
|
53
|
+
<TreeItemChevron />
|
|
54
|
+
<span>Platform</span>
|
|
55
|
+
</TreeItemContent>
|
|
56
|
+
<TreeItem id="eng.platform.ml" textValue="ML">
|
|
57
|
+
<TreeItemContent>
|
|
58
|
+
<TreeItemChevron hasChildItems={false} />
|
|
59
|
+
<span>ML</span>
|
|
60
|
+
</TreeItemContent>
|
|
61
|
+
</TreeItem>
|
|
62
|
+
<TreeItem id="eng.platform.observability" textValue="Observability">
|
|
63
|
+
<TreeItemContent>
|
|
64
|
+
<TreeItemChevron hasChildItems={false} />
|
|
65
|
+
<span>Observability</span>
|
|
66
|
+
</TreeItemContent>
|
|
67
|
+
</TreeItem>
|
|
68
|
+
</TreeItem>
|
|
69
|
+
<TreeItem id="eng.tooling" textValue="Tooling">
|
|
70
|
+
<TreeItemContent>
|
|
71
|
+
<TreeItemChevron hasChildItems={false} />
|
|
72
|
+
<span>Tooling</span>
|
|
73
|
+
</TreeItemContent>
|
|
74
|
+
</TreeItem>
|
|
75
|
+
</TreeItem>
|
|
76
|
+
<TreeItem id="design" textValue="Design">
|
|
77
|
+
<TreeItemContent>
|
|
78
|
+
<TreeItemChevron hasChildItems={false} />
|
|
79
|
+
<span>Design</span>
|
|
80
|
+
</TreeItemContent>
|
|
81
|
+
</TreeItem>
|
|
82
|
+
</Tree>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const Default: Story = {
|
|
87
|
+
render: () => <Sample />,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const Bordered: Story = {
|
|
91
|
+
render: () => (
|
|
92
|
+
<Tree aria-label="Departments" variant="bordered" className="max-w-md">
|
|
93
|
+
<TreeItem id="a" textValue="Item A">
|
|
94
|
+
<TreeItemContent>
|
|
95
|
+
<TreeItemChevron hasChildItems={false} />
|
|
96
|
+
<span>Item A</span>
|
|
97
|
+
</TreeItemContent>
|
|
98
|
+
</TreeItem>
|
|
99
|
+
<TreeItem id="b" textValue="Item B">
|
|
100
|
+
<TreeItemContent>
|
|
101
|
+
<TreeItemChevron hasChildItems={false} />
|
|
102
|
+
<span>Item B</span>
|
|
103
|
+
</TreeItemContent>
|
|
104
|
+
</TreeItem>
|
|
105
|
+
</Tree>
|
|
106
|
+
),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Realistic ltree-shaped hierarchy used by the selection stories. Three
|
|
111
|
+
* roots, each with at least one expandable subtree. Mirrors what an EOS
|
|
112
|
+
* Job Categories taxonomy might look like.
|
|
113
|
+
*/
|
|
114
|
+
function LtreeSampleData(): ReactElement {
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
<TreeItem id="engineering" textValue="Engineering" hasChildItems>
|
|
118
|
+
<TreeItemContent>
|
|
119
|
+
<TreeItemChevron />
|
|
120
|
+
<span>Engineering</span>
|
|
121
|
+
</TreeItemContent>
|
|
122
|
+
<TreeItem id="engineering.platform" textValue="Platform" hasChildItems>
|
|
123
|
+
<TreeItemContent>
|
|
124
|
+
<TreeItemChevron />
|
|
125
|
+
<span>Platform</span>
|
|
126
|
+
</TreeItemContent>
|
|
127
|
+
<TreeItem id="engineering.platform.ml" textValue="ML">
|
|
128
|
+
<TreeItemContent>
|
|
129
|
+
<TreeItemChevron hasChildItems={false} />
|
|
130
|
+
<span>ML</span>
|
|
131
|
+
</TreeItemContent>
|
|
132
|
+
</TreeItem>
|
|
133
|
+
<TreeItem
|
|
134
|
+
id="engineering.platform.observability"
|
|
135
|
+
textValue="Observability"
|
|
136
|
+
>
|
|
137
|
+
<TreeItemContent>
|
|
138
|
+
<TreeItemChevron hasChildItems={false} />
|
|
139
|
+
<span>Observability</span>
|
|
140
|
+
</TreeItemContent>
|
|
141
|
+
</TreeItem>
|
|
142
|
+
</TreeItem>
|
|
143
|
+
<TreeItem id="engineering.tooling" textValue="Tooling" hasChildItems>
|
|
144
|
+
<TreeItemContent>
|
|
145
|
+
<TreeItemChevron />
|
|
146
|
+
<span>Tooling</span>
|
|
147
|
+
</TreeItemContent>
|
|
148
|
+
<TreeItem id="engineering.tooling.ci" textValue="CI/CD">
|
|
149
|
+
<TreeItemContent>
|
|
150
|
+
<TreeItemChevron hasChildItems={false} />
|
|
151
|
+
<span>CI/CD</span>
|
|
152
|
+
</TreeItemContent>
|
|
153
|
+
</TreeItem>
|
|
154
|
+
</TreeItem>
|
|
155
|
+
</TreeItem>
|
|
156
|
+
|
|
157
|
+
<TreeItem id="design" textValue="Design" hasChildItems>
|
|
158
|
+
<TreeItemContent>
|
|
159
|
+
<TreeItemChevron />
|
|
160
|
+
<span>Design</span>
|
|
161
|
+
</TreeItemContent>
|
|
162
|
+
<TreeItem id="design.brand" textValue="Brand">
|
|
163
|
+
<TreeItemContent>
|
|
164
|
+
<TreeItemChevron hasChildItems={false} />
|
|
165
|
+
<span>Brand</span>
|
|
166
|
+
</TreeItemContent>
|
|
167
|
+
</TreeItem>
|
|
168
|
+
<TreeItem id="design.product" textValue="Product">
|
|
169
|
+
<TreeItemContent>
|
|
170
|
+
<TreeItemChevron hasChildItems={false} />
|
|
171
|
+
<span>Product</span>
|
|
172
|
+
</TreeItemContent>
|
|
173
|
+
</TreeItem>
|
|
174
|
+
<TreeItem id="design.research" textValue="Research">
|
|
175
|
+
<TreeItemContent>
|
|
176
|
+
<TreeItemChevron hasChildItems={false} />
|
|
177
|
+
<span>Research</span>
|
|
178
|
+
</TreeItemContent>
|
|
179
|
+
</TreeItem>
|
|
180
|
+
</TreeItem>
|
|
181
|
+
|
|
182
|
+
<TreeItem id="operations" textValue="Operations" hasChildItems>
|
|
183
|
+
<TreeItemContent>
|
|
184
|
+
<TreeItemChevron />
|
|
185
|
+
<span>Operations</span>
|
|
186
|
+
</TreeItemContent>
|
|
187
|
+
<TreeItem id="operations.people" textValue="People">
|
|
188
|
+
<TreeItemContent>
|
|
189
|
+
<TreeItemChevron hasChildItems={false} />
|
|
190
|
+
<span>People</span>
|
|
191
|
+
</TreeItemContent>
|
|
192
|
+
</TreeItem>
|
|
193
|
+
<TreeItem id="operations.finance" textValue="Finance">
|
|
194
|
+
<TreeItemContent>
|
|
195
|
+
<TreeItemChevron hasChildItems={false} />
|
|
196
|
+
<span>Finance</span>
|
|
197
|
+
</TreeItemContent>
|
|
198
|
+
</TreeItem>
|
|
199
|
+
</TreeItem>
|
|
200
|
+
</>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const SingleSelection: Story = {
|
|
205
|
+
render: () => {
|
|
206
|
+
function Inner(): ReactElement {
|
|
207
|
+
const [selected, setSelected] = useState<Selection>(
|
|
208
|
+
new Set(['engineering.platform.ml']),
|
|
209
|
+
);
|
|
210
|
+
return (
|
|
211
|
+
<div className="flex flex-col gap-3">
|
|
212
|
+
<Tree
|
|
213
|
+
aria-label="Job categories"
|
|
214
|
+
selectionMode="single"
|
|
215
|
+
selectedKeys={selected}
|
|
216
|
+
defaultExpandedKeys={new Set(['engineering', 'engineering.platform'])}
|
|
217
|
+
onSelectionChange={setSelected}
|
|
218
|
+
className="max-w-md"
|
|
219
|
+
>
|
|
220
|
+
<LtreeSampleData />
|
|
221
|
+
</Tree>
|
|
222
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
223
|
+
Selected:{' '}
|
|
224
|
+
<code>{Array.from(selected as Set<string>).join(', ') || '—'}</code>
|
|
225
|
+
</p>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return <Inner />;
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const MultipleSelection: Story = {
|
|
234
|
+
render: () => {
|
|
235
|
+
function Inner(): ReactElement {
|
|
236
|
+
const [selected, setSelected] = useState<Selection>(
|
|
237
|
+
new Set(['engineering.platform.ml', 'design.research']),
|
|
238
|
+
);
|
|
239
|
+
return (
|
|
240
|
+
<div className="flex flex-col gap-3">
|
|
241
|
+
<Tree
|
|
242
|
+
aria-label="Job categories"
|
|
243
|
+
selectionMode="multiple"
|
|
244
|
+
selectedKeys={selected}
|
|
245
|
+
defaultExpandedKeys={
|
|
246
|
+
new Set(['engineering', 'engineering.platform', 'design'])
|
|
247
|
+
}
|
|
248
|
+
onSelectionChange={setSelected}
|
|
249
|
+
className="max-w-md"
|
|
250
|
+
>
|
|
251
|
+
<LtreeSampleData />
|
|
252
|
+
</Tree>
|
|
253
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
254
|
+
Selected:{' '}
|
|
255
|
+
<code>
|
|
256
|
+
{Array.from(selected as Set<string>).join(', ') || '—'}
|
|
257
|
+
</code>
|
|
258
|
+
</p>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return <Inner />;
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export const SingleSelectionDeepLtree: Story = {
|
|
267
|
+
render: () => {
|
|
268
|
+
function Inner(): ReactElement {
|
|
269
|
+
const [selected, setSelected] = useState<Selection>(new Set([]));
|
|
270
|
+
return (
|
|
271
|
+
<div className="flex flex-col gap-3">
|
|
272
|
+
<Tree
|
|
273
|
+
aria-label="Job categories — deep"
|
|
274
|
+
selectionMode="single"
|
|
275
|
+
selectedKeys={selected}
|
|
276
|
+
defaultExpandedKeys={
|
|
277
|
+
new Set([
|
|
278
|
+
'engineering',
|
|
279
|
+
'engineering.platform',
|
|
280
|
+
'engineering.tooling',
|
|
281
|
+
'design',
|
|
282
|
+
'operations',
|
|
283
|
+
])
|
|
284
|
+
}
|
|
285
|
+
onSelectionChange={setSelected}
|
|
286
|
+
className="max-w-md"
|
|
287
|
+
>
|
|
288
|
+
<LtreeSampleData />
|
|
289
|
+
</Tree>
|
|
290
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
291
|
+
Tip: use{' '}
|
|
292
|
+
<kbd className="rounded bg-[var(--secondary)] px-1.5 py-0.5">↓</kbd>{' '}
|
|
293
|
+
<kbd className="rounded bg-[var(--secondary)] px-1.5 py-0.5">↑</kbd>{' '}
|
|
294
|
+
to walk rows,{' '}
|
|
295
|
+
<kbd className="rounded bg-[var(--secondary)] px-1.5 py-0.5">→</kbd>{' '}
|
|
296
|
+
<kbd className="rounded bg-[var(--secondary)] px-1.5 py-0.5">←</kbd>{' '}
|
|
297
|
+
to expand/collapse, type-ahead to jump.
|
|
298
|
+
</p>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return <Inner />;
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export const MultipleSelectionDeepLtree: Story = {
|
|
307
|
+
render: () => {
|
|
308
|
+
function Inner(): ReactElement {
|
|
309
|
+
const [selected, setSelected] = useState<Selection>(
|
|
310
|
+
new Set([
|
|
311
|
+
'engineering.platform.observability',
|
|
312
|
+
'engineering.tooling.ci',
|
|
313
|
+
'design.brand',
|
|
314
|
+
'operations.finance',
|
|
315
|
+
]),
|
|
316
|
+
);
|
|
317
|
+
return (
|
|
318
|
+
<div className="flex flex-col gap-3">
|
|
319
|
+
<Tree
|
|
320
|
+
aria-label="Job categories — multi-pick"
|
|
321
|
+
selectionMode="multiple"
|
|
322
|
+
selectedKeys={selected}
|
|
323
|
+
defaultExpandedKeys={
|
|
324
|
+
new Set([
|
|
325
|
+
'engineering',
|
|
326
|
+
'engineering.platform',
|
|
327
|
+
'engineering.tooling',
|
|
328
|
+
'design',
|
|
329
|
+
'operations',
|
|
330
|
+
])
|
|
331
|
+
}
|
|
332
|
+
onSelectionChange={setSelected}
|
|
333
|
+
className="max-w-md"
|
|
334
|
+
>
|
|
335
|
+
<LtreeSampleData />
|
|
336
|
+
</Tree>
|
|
337
|
+
<p className="text-sm text-[var(--muted-foreground)]">
|
|
338
|
+
{Array.from(selected as Set<string>).length} selected
|
|
339
|
+
</p>
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return <Inner />;
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export const DisabledRow: Story = {
|
|
348
|
+
render: () => (
|
|
349
|
+
<Tree aria-label="Departments" disabledKeys={['eng']} className="max-w-md">
|
|
350
|
+
<TreeItem id="eng" textValue="Engineering">
|
|
351
|
+
<TreeItemContent>
|
|
352
|
+
<TreeItemChevron hasChildItems={false} />
|
|
353
|
+
<span>Engineering (disabled)</span>
|
|
354
|
+
</TreeItemContent>
|
|
355
|
+
</TreeItem>
|
|
356
|
+
<TreeItem id="design" textValue="Design">
|
|
357
|
+
<TreeItemContent>
|
|
358
|
+
<TreeItemChevron hasChildItems={false} />
|
|
359
|
+
<span>Design</span>
|
|
360
|
+
</TreeItemContent>
|
|
361
|
+
</TreeItem>
|
|
362
|
+
</Tree>
|
|
363
|
+
),
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export const EmptyState: Story = {
|
|
367
|
+
render: () => (
|
|
368
|
+
<Tree
|
|
369
|
+
aria-label="Empty"
|
|
370
|
+
renderEmptyState={() => <span>No items yet</span>}
|
|
371
|
+
className="max-w-md"
|
|
372
|
+
>
|
|
373
|
+
{[]}
|
|
374
|
+
</Tree>
|
|
375
|
+
),
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
export const Sizes: Story = {
|
|
379
|
+
render: () => (
|
|
380
|
+
<div className="flex flex-col gap-6">
|
|
381
|
+
<Tree aria-label="sm" size="sm" className="max-w-md">
|
|
382
|
+
<TreeItem id="sm.1" textValue="Small row">
|
|
383
|
+
<TreeItemContent>
|
|
384
|
+
<TreeItemChevron hasChildItems={false} />
|
|
385
|
+
<span>Small row</span>
|
|
386
|
+
</TreeItemContent>
|
|
387
|
+
</TreeItem>
|
|
388
|
+
</Tree>
|
|
389
|
+
<Tree aria-label="default" className="max-w-md">
|
|
390
|
+
<TreeItem id="def.1" textValue="Default row">
|
|
391
|
+
<TreeItemContent>
|
|
392
|
+
<TreeItemChevron hasChildItems={false} />
|
|
393
|
+
<span>Default row</span>
|
|
394
|
+
</TreeItemContent>
|
|
395
|
+
</TreeItem>
|
|
396
|
+
</Tree>
|
|
397
|
+
<Tree aria-label="lg" size="lg" className="max-w-md">
|
|
398
|
+
<TreeItem id="lg.1" textValue="Large row">
|
|
399
|
+
<TreeItemContent>
|
|
400
|
+
<TreeItemChevron hasChildItems={false} />
|
|
401
|
+
<span>Large row</span>
|
|
402
|
+
</TreeItemContent>
|
|
403
|
+
</TreeItem>
|
|
404
|
+
</Tree>
|
|
405
|
+
</div>
|
|
406
|
+
),
|
|
407
|
+
};
|