@shipfox/react-ui 0.10.0 → 0.11.0
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/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-check.log +2 -2
- package/.turbo/turbo-type.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/components/button/button.js +14 -11
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.ts +58 -0
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu/dropdown-menu.js +280 -0
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -0
- package/dist/components/dropdown-menu/dropdown-menu.stories.js +462 -0
- package/dist/components/dropdown-menu/dropdown-menu.stories.js.map +1 -0
- package/dist/components/dropdown-menu/index.d.ts +3 -0
- package/dist/components/dropdown-menu/index.d.ts.map +1 -0
- package/dist/components/dropdown-menu/index.js +3 -0
- package/dist/components/dropdown-menu/index.js.map +1 -0
- package/dist/components/dynamic-item/dynamic-item.d.ts +1 -1
- package/dist/components/dynamic-item/dynamic-item.d.ts.map +1 -1
- package/dist/components/dynamic-item/dynamic-item.js +4 -4
- package/dist/components/dynamic-item/dynamic-item.js.map +1 -1
- package/dist/components/dynamic-item/dynamic-item.stories.js +11 -1
- package/dist/components/dynamic-item/dynamic-item.stories.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- package/src/components/button/button.tsx +12 -12
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +384 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +416 -0
- package/src/components/dropdown-menu/index.ts +29 -0
- package/src/components/dynamic-item/dynamic-item.stories.tsx +6 -1
- package/src/components/dynamic-item/dynamic-item.tsx +9 -3
- package/src/components/index.ts +1 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import {argosScreenshot} from '@argos-ci/storybook/vitest';
|
|
2
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
+
import {screen, within} from '@testing-library/react';
|
|
4
|
+
import type {UserEvent} from '@testing-library/user-event';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import {useState} from 'react';
|
|
7
|
+
import {Avatar} from '../avatar';
|
|
8
|
+
import {Button} from '../button';
|
|
9
|
+
import {
|
|
10
|
+
DropdownMenu,
|
|
11
|
+
DropdownMenuCheckboxItem,
|
|
12
|
+
DropdownMenuContent,
|
|
13
|
+
DropdownMenuGroup,
|
|
14
|
+
DropdownMenuItem,
|
|
15
|
+
DropdownMenuLabel,
|
|
16
|
+
DropdownMenuRadioGroup,
|
|
17
|
+
DropdownMenuRadioItem,
|
|
18
|
+
DropdownMenuSeparator,
|
|
19
|
+
DropdownMenuSub,
|
|
20
|
+
DropdownMenuSubContent,
|
|
21
|
+
DropdownMenuSubTrigger,
|
|
22
|
+
DropdownMenuTrigger,
|
|
23
|
+
} from './dropdown-menu';
|
|
24
|
+
|
|
25
|
+
const OPEN_MENU_REGEX = /open menu/i;
|
|
26
|
+
const ACTIONS_REGEX = /actions/i;
|
|
27
|
+
const ORGANIZATION_REGEX = /organization/i;
|
|
28
|
+
const COMPLETE_MENU_REGEX = /complete menu/i;
|
|
29
|
+
const SWITCH_ORGANIZATION_REGEX = /switch organization/i;
|
|
30
|
+
|
|
31
|
+
const isTestEnvironment = () => typeof navigator !== 'undefined' && navigator.webdriver === true;
|
|
32
|
+
|
|
33
|
+
type StoryContext = Parameters<NonNullable<Story['play']>>[0];
|
|
34
|
+
type AdditionalStepsCallback = (ctx: StoryContext, user: UserEvent) => Promise<void>;
|
|
35
|
+
|
|
36
|
+
async function openMenuAndScreenshot(
|
|
37
|
+
ctx: StoryContext,
|
|
38
|
+
triggerRegex: RegExp,
|
|
39
|
+
screenshotName: string,
|
|
40
|
+
additionalSteps?: AdditionalStepsCallback,
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const {canvasElement, step} = ctx;
|
|
43
|
+
const canvas = within(canvasElement);
|
|
44
|
+
const user = userEvent.setup();
|
|
45
|
+
|
|
46
|
+
let triggerButton: HTMLElement | null = null;
|
|
47
|
+
|
|
48
|
+
await step('Open the dropdown menu', async () => {
|
|
49
|
+
triggerButton = canvas.getByRole('button', {name: triggerRegex});
|
|
50
|
+
await user.click(triggerButton);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await step('Wait for menu to appear and render', async () => {
|
|
54
|
+
await screen.findByRole('menu');
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
56
|
+
|
|
57
|
+
if (isTestEnvironment() && triggerButton instanceof HTMLElement) {
|
|
58
|
+
triggerButton.style.display = 'none';
|
|
59
|
+
}
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await argosScreenshot(ctx, screenshotName);
|
|
64
|
+
|
|
65
|
+
if (additionalSteps) {
|
|
66
|
+
await additionalSteps(ctx, user);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const meta = {
|
|
71
|
+
title: 'Components/DropdownMenu',
|
|
72
|
+
component: DropdownMenuContent,
|
|
73
|
+
subcomponents: {
|
|
74
|
+
DropdownMenu,
|
|
75
|
+
DropdownMenuTrigger,
|
|
76
|
+
DropdownMenuItem,
|
|
77
|
+
DropdownMenuCheckboxItem,
|
|
78
|
+
DropdownMenuRadioGroup,
|
|
79
|
+
DropdownMenuRadioItem,
|
|
80
|
+
DropdownMenuLabel,
|
|
81
|
+
DropdownMenuSeparator,
|
|
82
|
+
DropdownMenuGroup,
|
|
83
|
+
DropdownMenuSub,
|
|
84
|
+
DropdownMenuSubTrigger,
|
|
85
|
+
DropdownMenuSubContent,
|
|
86
|
+
},
|
|
87
|
+
parameters: {
|
|
88
|
+
layout: 'centered',
|
|
89
|
+
},
|
|
90
|
+
tags: ['autodocs'],
|
|
91
|
+
argTypes: {
|
|
92
|
+
side: {
|
|
93
|
+
control: 'select',
|
|
94
|
+
options: ['top', 'right', 'bottom', 'left'],
|
|
95
|
+
description: 'The preferred side of the trigger to render against',
|
|
96
|
+
table: {defaultValue: {summary: 'bottom'}},
|
|
97
|
+
},
|
|
98
|
+
align: {
|
|
99
|
+
control: 'select',
|
|
100
|
+
options: ['start', 'center', 'end'],
|
|
101
|
+
description: 'The preferred alignment against the trigger',
|
|
102
|
+
table: {defaultValue: {summary: 'start'}},
|
|
103
|
+
},
|
|
104
|
+
sideOffset: {
|
|
105
|
+
control: {type: 'number', min: 0, max: 20, step: 1},
|
|
106
|
+
description: 'Distance in pixels from the trigger',
|
|
107
|
+
table: {defaultValue: {summary: '4'}},
|
|
108
|
+
},
|
|
109
|
+
alignOffset: {
|
|
110
|
+
control: {type: 'number', min: -20, max: 20, step: 1},
|
|
111
|
+
description: 'Offset in pixels from the alignment edge',
|
|
112
|
+
table: {defaultValue: {summary: '0'}},
|
|
113
|
+
},
|
|
114
|
+
size: {
|
|
115
|
+
control: 'select',
|
|
116
|
+
options: ['sm', 'md', 'lg'],
|
|
117
|
+
description: 'Size variant of the dropdown content',
|
|
118
|
+
table: {defaultValue: {summary: 'md'}},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
args: {
|
|
122
|
+
side: 'bottom',
|
|
123
|
+
align: 'center',
|
|
124
|
+
sideOffset: 8,
|
|
125
|
+
alignOffset: 0,
|
|
126
|
+
size: 'md',
|
|
127
|
+
},
|
|
128
|
+
} satisfies Meta<typeof DropdownMenuContent>;
|
|
129
|
+
|
|
130
|
+
export default meta;
|
|
131
|
+
type Story = StoryObj<typeof meta>;
|
|
132
|
+
|
|
133
|
+
export const Default: Story = {
|
|
134
|
+
play: (ctx) => openMenuAndScreenshot(ctx, OPEN_MENU_REGEX, 'DropdownMenu Default Open'),
|
|
135
|
+
render: function DefaultStory(args) {
|
|
136
|
+
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div
|
|
140
|
+
ref={setContainer}
|
|
141
|
+
className="relative flex h-500 w-500 items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip overflow-visible"
|
|
142
|
+
>
|
|
143
|
+
{container && (
|
|
144
|
+
<DropdownMenu>
|
|
145
|
+
<DropdownMenuTrigger asChild>
|
|
146
|
+
<Button variant="secondary">Open Menu</Button>
|
|
147
|
+
</DropdownMenuTrigger>
|
|
148
|
+
<DropdownMenuContent {...args} container={container} side="bottom" align="center">
|
|
149
|
+
<DropdownMenuItem icon="editLine">Edit</DropdownMenuItem>
|
|
150
|
+
<DropdownMenuItem icon="addLine">Add</DropdownMenuItem>
|
|
151
|
+
<DropdownMenuSeparator />
|
|
152
|
+
<DropdownMenuItem icon="deleteBinLine">Delete</DropdownMenuItem>
|
|
153
|
+
</DropdownMenuContent>
|
|
154
|
+
</DropdownMenu>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const WithShortcuts: Story = {
|
|
162
|
+
play: (ctx) => openMenuAndScreenshot(ctx, ACTIONS_REGEX, 'DropdownMenu With Shortcuts Open'),
|
|
163
|
+
render: function WithShortcutsStory(args) {
|
|
164
|
+
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div
|
|
168
|
+
ref={setContainer}
|
|
169
|
+
className="relative flex h-500 w-500 items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip overflow-visible"
|
|
170
|
+
>
|
|
171
|
+
{container && (
|
|
172
|
+
<DropdownMenu>
|
|
173
|
+
<DropdownMenuTrigger asChild>
|
|
174
|
+
<Button variant="secondary">Actions</Button>
|
|
175
|
+
</DropdownMenuTrigger>
|
|
176
|
+
<DropdownMenuContent {...args} container={container} side="bottom" align="center">
|
|
177
|
+
<DropdownMenuItem icon="fileCopyLine" shortcut="⌘C">
|
|
178
|
+
Copy
|
|
179
|
+
</DropdownMenuItem>
|
|
180
|
+
<DropdownMenuItem icon="clipboardLine" shortcut="⌘V">
|
|
181
|
+
Paste
|
|
182
|
+
</DropdownMenuItem>
|
|
183
|
+
<DropdownMenuSeparator />
|
|
184
|
+
<DropdownMenuItem icon="deleteBinLine" shortcut="⌘⌫">
|
|
185
|
+
Delete
|
|
186
|
+
</DropdownMenuItem>
|
|
187
|
+
</DropdownMenuContent>
|
|
188
|
+
</DropdownMenu>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
function UserProfileSection() {
|
|
196
|
+
return (
|
|
197
|
+
<div className="flex items-center gap-8 px-8 py-6">
|
|
198
|
+
<Avatar
|
|
199
|
+
size="sm"
|
|
200
|
+
content="image"
|
|
201
|
+
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=faces"
|
|
202
|
+
fallback="John Doe"
|
|
203
|
+
/>
|
|
204
|
+
<div className="flex flex-col">
|
|
205
|
+
<span className="text-sm font-medium leading-20 text-foreground-neutral-base">
|
|
206
|
+
John Doe
|
|
207
|
+
</span>
|
|
208
|
+
<span className="text-xs leading-16 text-foreground-neutral-muted">john@example.com</span>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function OrganizationItem() {
|
|
215
|
+
return (
|
|
216
|
+
<div className="flex items-center gap-8 px-8 py-6">
|
|
217
|
+
<Avatar size="3xs" content="logo" logoName="stripe" radius="rounded" />
|
|
218
|
+
<span className="text-sm leading-20 text-foreground-neutral-subtle">
|
|
219
|
+
Stripe's organization
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const OrganizationMenu: Story = {
|
|
226
|
+
args: {
|
|
227
|
+
size: 'md',
|
|
228
|
+
},
|
|
229
|
+
play: (ctx) =>
|
|
230
|
+
openMenuAndScreenshot(
|
|
231
|
+
ctx,
|
|
232
|
+
ORGANIZATION_REGEX,
|
|
233
|
+
'DropdownMenu Organization Menu Open',
|
|
234
|
+
async ({step}, user) => {
|
|
235
|
+
await step('Hover over submenu trigger', async () => {
|
|
236
|
+
const submenuTrigger = screen.getByRole('menuitem', {name: SWITCH_ORGANIZATION_REGEX});
|
|
237
|
+
await user.hover(submenuTrigger);
|
|
238
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await argosScreenshot(ctx, 'DropdownMenu Organization Menu Submenu Open');
|
|
242
|
+
},
|
|
243
|
+
),
|
|
244
|
+
render: function OrganizationMenuStory(args) {
|
|
245
|
+
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<div
|
|
249
|
+
ref={setContainer}
|
|
250
|
+
className="relative flex h-600 w-600 items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip overflow-visible"
|
|
251
|
+
>
|
|
252
|
+
{container && (
|
|
253
|
+
<DropdownMenu>
|
|
254
|
+
<DropdownMenuTrigger asChild>
|
|
255
|
+
<Button variant="secondary" iconLeft="buildingLine">
|
|
256
|
+
Organization
|
|
257
|
+
</Button>
|
|
258
|
+
</DropdownMenuTrigger>
|
|
259
|
+
<DropdownMenuContent {...args} container={container} side="bottom" align="start">
|
|
260
|
+
<OrganizationItem />
|
|
261
|
+
<DropdownMenuSeparator />
|
|
262
|
+
<DropdownMenuItem icon="settings3Line">Settings</DropdownMenuItem>
|
|
263
|
+
<DropdownMenuSub>
|
|
264
|
+
<DropdownMenuSubTrigger icon="arrowLeftRightLine">
|
|
265
|
+
Switch organization
|
|
266
|
+
</DropdownMenuSubTrigger>
|
|
267
|
+
<DropdownMenuSubContent>
|
|
268
|
+
<DropdownMenuItem
|
|
269
|
+
icon="shipfox"
|
|
270
|
+
iconStyle="text-foreground-neutral-base"
|
|
271
|
+
className="text-foreground-neutral-base"
|
|
272
|
+
>
|
|
273
|
+
Shipfox
|
|
274
|
+
</DropdownMenuItem>
|
|
275
|
+
<DropdownMenuItem
|
|
276
|
+
icon="github"
|
|
277
|
+
iconStyle="text-foreground-neutral-base"
|
|
278
|
+
className="text-foreground-neutral-base"
|
|
279
|
+
>
|
|
280
|
+
Github
|
|
281
|
+
</DropdownMenuItem>
|
|
282
|
+
<DropdownMenuItem
|
|
283
|
+
icon="google"
|
|
284
|
+
iconStyle="text-foreground-neutral-base"
|
|
285
|
+
className="text-foreground-neutral-base"
|
|
286
|
+
>
|
|
287
|
+
Google
|
|
288
|
+
</DropdownMenuItem>
|
|
289
|
+
</DropdownMenuSubContent>
|
|
290
|
+
</DropdownMenuSub>
|
|
291
|
+
<DropdownMenuItem icon="addLine">New organization</DropdownMenuItem>
|
|
292
|
+
</DropdownMenuContent>
|
|
293
|
+
</DropdownMenu>
|
|
294
|
+
)}
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export const CompleteExample: Story = {
|
|
301
|
+
args: {
|
|
302
|
+
size: 'lg',
|
|
303
|
+
side: 'bottom',
|
|
304
|
+
},
|
|
305
|
+
play: (ctx) =>
|
|
306
|
+
openMenuAndScreenshot(ctx, COMPLETE_MENU_REGEX, 'DropdownMenu Complete Example Open'),
|
|
307
|
+
render: function CompleteExampleStory(args) {
|
|
308
|
+
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
|
309
|
+
const [showNotifications, setShowNotifications] = useState(true);
|
|
310
|
+
const [showBadges, setShowBadges] = useState(false);
|
|
311
|
+
const [theme, setTheme] = useState('dark');
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div
|
|
315
|
+
ref={setContainer}
|
|
316
|
+
className="relative flex h-600 w-500 items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip overflow-visible"
|
|
317
|
+
>
|
|
318
|
+
{container && (
|
|
319
|
+
<DropdownMenu>
|
|
320
|
+
<DropdownMenuTrigger asChild>
|
|
321
|
+
<Button variant="secondary" iconLeft="menu3Line">
|
|
322
|
+
Complete Menu
|
|
323
|
+
</Button>
|
|
324
|
+
</DropdownMenuTrigger>
|
|
325
|
+
<DropdownMenuContent
|
|
326
|
+
{...args}
|
|
327
|
+
className="w-260"
|
|
328
|
+
container={container}
|
|
329
|
+
side="bottom"
|
|
330
|
+
align="start"
|
|
331
|
+
>
|
|
332
|
+
<UserProfileSection />
|
|
333
|
+
<DropdownMenuSeparator />
|
|
334
|
+
|
|
335
|
+
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
336
|
+
<DropdownMenuGroup>
|
|
337
|
+
<DropdownMenuItem icon="sparklingLine">Getting started</DropdownMenuItem>
|
|
338
|
+
<DropdownMenuItem icon="userLine">Profile settings</DropdownMenuItem>
|
|
339
|
+
</DropdownMenuGroup>
|
|
340
|
+
|
|
341
|
+
<DropdownMenuSeparator />
|
|
342
|
+
|
|
343
|
+
<DropdownMenuLabel>Preferences</DropdownMenuLabel>
|
|
344
|
+
<DropdownMenuGroup>
|
|
345
|
+
<DropdownMenuCheckboxItem
|
|
346
|
+
checked={showNotifications}
|
|
347
|
+
onCheckedChange={setShowNotifications}
|
|
348
|
+
closeOnSelect={false}
|
|
349
|
+
>
|
|
350
|
+
Show notifications
|
|
351
|
+
</DropdownMenuCheckboxItem>
|
|
352
|
+
<DropdownMenuCheckboxItem
|
|
353
|
+
checked={showBadges}
|
|
354
|
+
onCheckedChange={setShowBadges}
|
|
355
|
+
closeOnSelect={false}
|
|
356
|
+
>
|
|
357
|
+
Show badges
|
|
358
|
+
</DropdownMenuCheckboxItem>
|
|
359
|
+
</DropdownMenuGroup>
|
|
360
|
+
|
|
361
|
+
<DropdownMenuSeparator />
|
|
362
|
+
|
|
363
|
+
<DropdownMenuLabel>Theme</DropdownMenuLabel>
|
|
364
|
+
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
|
|
365
|
+
<DropdownMenuRadioItem value="light" closeOnSelect={false}>
|
|
366
|
+
Light
|
|
367
|
+
</DropdownMenuRadioItem>
|
|
368
|
+
<DropdownMenuRadioItem value="dark" closeOnSelect={false}>
|
|
369
|
+
Dark
|
|
370
|
+
</DropdownMenuRadioItem>
|
|
371
|
+
<DropdownMenuRadioItem value="system" closeOnSelect={false}>
|
|
372
|
+
System
|
|
373
|
+
</DropdownMenuRadioItem>
|
|
374
|
+
</DropdownMenuRadioGroup>
|
|
375
|
+
|
|
376
|
+
<DropdownMenuSeparator />
|
|
377
|
+
<DropdownMenuItem icon="logoutCircleLine">Log out</DropdownMenuItem>
|
|
378
|
+
</DropdownMenuContent>
|
|
379
|
+
</DropdownMenu>
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
};
|