@shipfox/react-ui 0.10.0 → 0.12.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.
Files changed (90) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/.turbo/turbo-check.log +2 -2
  3. package/.turbo/turbo-type.log +1 -1
  4. package/CHANGELOG.md +12 -0
  5. package/dist/components/button/button.js +14 -11
  6. package/dist/components/button/button.js.map +1 -1
  7. package/dist/components/calendar/calendar.d.ts +5 -0
  8. package/dist/components/calendar/calendar.d.ts.map +1 -0
  9. package/dist/components/calendar/calendar.js +46 -0
  10. package/dist/components/calendar/calendar.js.map +1 -0
  11. package/dist/components/calendar/index.d.ts +2 -0
  12. package/dist/components/calendar/index.d.ts.map +1 -0
  13. package/dist/components/calendar/index.js +3 -0
  14. package/dist/components/calendar/index.js.map +1 -0
  15. package/dist/components/date-picker/date-picker.d.ts +19 -0
  16. package/dist/components/date-picker/date-picker.d.ts.map +1 -0
  17. package/dist/components/date-picker/date-picker.js +114 -0
  18. package/dist/components/date-picker/date-picker.js.map +1 -0
  19. package/dist/components/date-picker/date-picker.stories.js +333 -0
  20. package/dist/components/date-picker/date-picker.stories.js.map +1 -0
  21. package/dist/components/date-picker/index.d.ts +2 -0
  22. package/dist/components/date-picker/index.d.ts.map +1 -0
  23. package/dist/components/date-picker/index.js +3 -0
  24. package/dist/components/date-picker/index.js.map +1 -0
  25. package/dist/components/date-time-range-picker/date-time-range-picker.d.ts +24 -0
  26. package/dist/components/date-time-range-picker/date-time-range-picker.d.ts.map +1 -0
  27. package/dist/components/date-time-range-picker/date-time-range-picker.js +130 -0
  28. package/dist/components/date-time-range-picker/date-time-range-picker.js.map +1 -0
  29. package/dist/components/date-time-range-picker/index.d.ts +2 -0
  30. package/dist/components/date-time-range-picker/index.d.ts.map +1 -0
  31. package/dist/components/date-time-range-picker/index.js +3 -0
  32. package/dist/components/date-time-range-picker/index.js.map +1 -0
  33. package/dist/components/dropdown-menu/dropdown-menu.d.ts +58 -0
  34. package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -0
  35. package/dist/components/dropdown-menu/dropdown-menu.js +280 -0
  36. package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -0
  37. package/dist/components/dropdown-menu/dropdown-menu.stories.js +462 -0
  38. package/dist/components/dropdown-menu/dropdown-menu.stories.js.map +1 -0
  39. package/dist/components/dropdown-menu/index.d.ts +3 -0
  40. package/dist/components/dropdown-menu/index.d.ts.map +1 -0
  41. package/dist/components/dropdown-menu/index.js +3 -0
  42. package/dist/components/dropdown-menu/index.js.map +1 -0
  43. package/dist/components/dynamic-item/dynamic-item.d.ts +1 -1
  44. package/dist/components/dynamic-item/dynamic-item.d.ts.map +1 -1
  45. package/dist/components/dynamic-item/dynamic-item.js +4 -4
  46. package/dist/components/dynamic-item/dynamic-item.js.map +1 -1
  47. package/dist/components/dynamic-item/dynamic-item.stories.js +11 -1
  48. package/dist/components/dynamic-item/dynamic-item.stories.js.map +1 -1
  49. package/dist/components/index.d.ts +5 -0
  50. package/dist/components/index.d.ts.map +1 -1
  51. package/dist/components/index.js +5 -0
  52. package/dist/components/index.js.map +1 -1
  53. package/dist/components/inline-tips/inline-tips.stories.js +5 -0
  54. package/dist/components/inline-tips/inline-tips.stories.js.map +1 -1
  55. package/dist/components/item/item.stories.js +15 -8
  56. package/dist/components/item/item.stories.js.map +1 -1
  57. package/dist/components/modal/modal.stories.js +14 -6
  58. package/dist/components/modal/modal.stories.js.map +1 -1
  59. package/dist/components/popover/index.d.ts +2 -0
  60. package/dist/components/popover/index.d.ts.map +1 -0
  61. package/dist/components/popover/index.js +3 -0
  62. package/dist/components/popover/index.js.map +1 -0
  63. package/dist/components/popover/popover.d.ts +10 -0
  64. package/dist/components/popover/popover.d.ts.map +1 -0
  65. package/dist/components/popover/popover.js +47 -0
  66. package/dist/components/popover/popover.js.map +1 -0
  67. package/dist/components/textarea/textarea.stories.js +8 -2
  68. package/dist/components/textarea/textarea.stories.js.map +1 -1
  69. package/dist/styles.css +1 -1
  70. package/package.json +3 -3
  71. package/src/components/button/button.tsx +12 -12
  72. package/src/components/calendar/calendar.tsx +90 -0
  73. package/src/components/calendar/index.ts +1 -0
  74. package/src/components/date-picker/date-picker.stories.tsx +230 -0
  75. package/src/components/date-picker/date-picker.tsx +179 -0
  76. package/src/components/date-picker/index.ts +1 -0
  77. package/src/components/date-time-range-picker/date-time-range-picker.tsx +211 -0
  78. package/src/components/date-time-range-picker/index.ts +1 -0
  79. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +384 -0
  80. package/src/components/dropdown-menu/dropdown-menu.tsx +416 -0
  81. package/src/components/dropdown-menu/index.ts +29 -0
  82. package/src/components/dynamic-item/dynamic-item.stories.tsx +6 -1
  83. package/src/components/dynamic-item/dynamic-item.tsx +9 -3
  84. package/src/components/index.ts +5 -0
  85. package/src/components/inline-tips/inline-tips.stories.tsx +5 -0
  86. package/src/components/item/item.stories.tsx +65 -56
  87. package/src/components/modal/modal.stories.tsx +16 -6
  88. package/src/components/popover/index.ts +1 -0
  89. package/src/components/popover/popover.tsx +60 -0
  90. package/src/components/textarea/textarea.stories.tsx +8 -2
@@ -0,0 +1,211 @@
1
+ import {cva, type VariantProps} from 'class-variance-authority';
2
+ import {Calendar} from 'components/calendar';
3
+ import {Icon} from 'components/icon';
4
+ import {Popover, PopoverContent, PopoverTrigger} from 'components/popover';
5
+ import {format} from 'date-fns';
6
+ import type {ComponentProps, ReactNode} from 'react';
7
+ import {forwardRef, useState} from 'react';
8
+ import type {DateRange as DayPickerDateRange} from 'react-day-picker';
9
+ import {cn} from 'utils/cn';
10
+
11
+ export const dateTimeRangePickerVariants = cva(
12
+ 'min-w-240 relative flex items-center rounded-6 shadow-button-neutral transition-[background-color,box-shadow] outline-none',
13
+ {
14
+ variants: {
15
+ variant: {
16
+ base: 'bg-background-field-base hover:bg-background-field-hover',
17
+ component: 'bg-background-field-component hover:bg-background-field-component-hover',
18
+ },
19
+ size: {
20
+ base: 'h-32',
21
+ small: 'h-28',
22
+ },
23
+ state: {
24
+ default: '',
25
+ error: 'shadow-border-error',
26
+ disabled:
27
+ 'bg-background-neutral-disabled shadow-none pointer-events-none cursor-not-allowed',
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: 'base',
32
+ size: 'base',
33
+ state: 'default',
34
+ },
35
+ },
36
+ );
37
+
38
+ export type DateRange = {
39
+ start?: Date;
40
+ end?: Date;
41
+ };
42
+
43
+ export type DateTimeRangePickerProps = Omit<ComponentProps<'input'>, 'size' | 'type'> &
44
+ VariantProps<typeof dateTimeRangePickerVariants> & {
45
+ dateRange?: DateRange;
46
+ onDateRangeSelect?: (range: DateRange | undefined) => void;
47
+ placeholder?: string;
48
+ dateFormat?: string;
49
+ leftIcon?: ReactNode;
50
+ rightIcon?: ReactNode;
51
+ onClear?: () => void;
52
+ numberOfMonths?: number;
53
+ closeOnSelect?: boolean;
54
+ };
55
+
56
+ export const DateTimeRangePicker = forwardRef<HTMLInputElement, DateTimeRangePickerProps>(
57
+ (
58
+ {
59
+ className,
60
+ variant,
61
+ size,
62
+ state,
63
+ dateRange,
64
+ onDateRangeSelect,
65
+ placeholder = 'DD/MM/YYYY - DD/MM/YYYY',
66
+ dateFormat = 'dd/MM/yyyy',
67
+ leftIcon,
68
+ rightIcon,
69
+ onClear,
70
+ disabled,
71
+ numberOfMonths = 2,
72
+ closeOnSelect = false,
73
+ ...props
74
+ },
75
+ ref,
76
+ ) => {
77
+ const [open, setOpen] = useState(false);
78
+ const isDisabled = disabled || state === 'disabled';
79
+ const hasRange = dateRange?.start && dateRange?.end;
80
+ const displayValue =
81
+ hasRange && dateRange.start && dateRange.end
82
+ ? `${format(dateRange.start, dateFormat)} - ${format(dateRange.end, dateFormat)}`
83
+ : '';
84
+
85
+ // Convert our DateRange to react-day-picker's DateRange format
86
+ const dayPickerRange: DayPickerDateRange | undefined =
87
+ dateRange?.start || dateRange?.end
88
+ ? {
89
+ from: dateRange?.start,
90
+ to: dateRange?.end,
91
+ }
92
+ : undefined;
93
+
94
+ const handleSelect = (selectedRange: DayPickerDateRange | undefined) => {
95
+ if (selectedRange) {
96
+ onDateRangeSelect?.({
97
+ start: selectedRange.from,
98
+ end: selectedRange.to,
99
+ });
100
+ // Only close if both dates are selected and closeOnSelect is true
101
+ if (closeOnSelect && selectedRange.from && selectedRange.to) {
102
+ setOpen(false);
103
+ }
104
+ } else {
105
+ onDateRangeSelect?.(undefined);
106
+ }
107
+ };
108
+
109
+ const handleClear = (e: React.MouseEvent) => {
110
+ e.stopPropagation();
111
+ onClear?.();
112
+ setOpen(false);
113
+ };
114
+
115
+ return (
116
+ <Popover open={open} onOpenChange={setOpen}>
117
+ <div
118
+ className={cn(
119
+ open && 'shadow-border-interactive-with-active',
120
+ dateTimeRangePickerVariants({variant, size, state: isDisabled ? 'disabled' : state}),
121
+ className,
122
+ )}
123
+ >
124
+ {/* Calendar Icon Button */}
125
+ <PopoverTrigger asChild>
126
+ <button
127
+ type="button"
128
+ disabled={isDisabled}
129
+ className={cn(
130
+ 'flex items-center justify-center shrink-0 transition-colors',
131
+ size === 'small' ? 'size-28' : 'size-32',
132
+ isDisabled && 'text-foreground-neutral-disabled',
133
+ )}
134
+ aria-label="Open calendar"
135
+ >
136
+ {leftIcon || (
137
+ <Icon
138
+ name="calendar2Line"
139
+ className={cn(
140
+ 'size-16 text-foreground-neutral-muted',
141
+ isDisabled && 'text-foreground-neutral-disabled',
142
+ )}
143
+ />
144
+ )}
145
+ </button>
146
+ </PopoverTrigger>
147
+
148
+ {/* Divider */}
149
+ <div className="h-full w-px bg-border-neutral-base shrink-0" />
150
+
151
+ {/* Input Field */}
152
+ <input
153
+ ref={ref}
154
+ type="text"
155
+ disabled={isDisabled}
156
+ placeholder={placeholder}
157
+ value={displayValue}
158
+ readOnly
159
+ className={cn(
160
+ 'flex-1 min-w-0 px-8 text-sm leading-20 bg-transparent outline-none border-none cursor-pointer',
161
+ 'placeholder:text-foreground-neutral-muted',
162
+ 'text-foreground-neutral-base',
163
+ 'disabled:text-foreground-neutral-disabled disabled:cursor-not-allowed',
164
+ size === 'small' ? 'py-4' : 'py-6',
165
+ )}
166
+ onClick={() => !isDisabled && setOpen(true)}
167
+ {...props}
168
+ />
169
+
170
+ {/* Clear Button (shown when date range is selected) */}
171
+ <button
172
+ type="button"
173
+ onClick={handleClear}
174
+ className={cn(
175
+ 'flex items-center justify-center shrink-0 transition-colors hover:text-foreground-neutral-base',
176
+ size === 'small' ? 'size-28' : 'size-32',
177
+ hasRange && onClear && !isDisabled ? 'visible' : 'invisible',
178
+ )}
179
+ aria-label="Clear date range"
180
+ >
181
+ <Icon name="closeLine" className="size-16 text-foreground-neutral-muted" />
182
+ </button>
183
+
184
+ {/* Custom Right Icon */}
185
+ {rightIcon && !hasRange && (
186
+ <div
187
+ className={cn(
188
+ 'flex items-center justify-center shrink-0',
189
+ size === 'small' ? 'size-28' : 'size-32',
190
+ )}
191
+ >
192
+ {rightIcon}
193
+ </div>
194
+ )}
195
+ </div>
196
+
197
+ <PopoverContent className="w-auto p-0" align="start">
198
+ <Calendar
199
+ mode="range"
200
+ defaultMonth={dayPickerRange?.from}
201
+ selected={dayPickerRange}
202
+ onSelect={handleSelect}
203
+ numberOfMonths={numberOfMonths}
204
+ />
205
+ </PopoverContent>
206
+ </Popover>
207
+ );
208
+ },
209
+ );
210
+
211
+ DateTimeRangePicker.displayName = 'DateTimeRangePicker';
@@ -0,0 +1 @@
1
+ export * from './date-time-range-picker';
@@ -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&apos;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
+ };