create-auto-app 1.34.0 → 1.35.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/package.json +2 -2
- package/templates/typical/.context/components-db.json +4006 -0
- package/templates/typical/.gitignore +1 -1
- package/templates/typical/auto.config.ts +85 -80
- package/templates/typical/client/.gitignore +0 -4
- package/templates/typical/client/.storybook/main.ts +0 -66
- package/templates/typical/client/.storybook/manager-head.html +0 -154
- package/templates/typical/client/.storybook/manager.ts +0 -164
- package/templates/typical/client/.storybook/preview-head.html +0 -31
- package/templates/typical/client/.storybook/preview.tsx +0 -120
- package/templates/typical/client/codegen.ts +0 -17
- package/templates/typical/client/components.json +0 -29
- package/templates/typical/client/index.html +0 -12
- package/templates/typical/client/package.json +0 -69
- package/templates/typical/client/pnpm-lock.yaml +0 -7868
- package/templates/typical/client/public/blank.svg +0 -1
- package/templates/typical/client/public/mockServiceWorker.js +0 -336
- package/templates/typical/client/src/App.tsx +0 -29
- package/templates/typical/client/src/components/.gitkeep +0 -0
- package/templates/typical/client/src/components/ui/Accordion.stories.tsx +0 -49
- package/templates/typical/client/src/components/ui/Accordion.tsx +0 -55
- package/templates/typical/client/src/components/ui/Alert.stories.tsx +0 -29
- package/templates/typical/client/src/components/ui/Alert.tsx +0 -56
- package/templates/typical/client/src/components/ui/AlertDialog.stories.tsx +0 -67
- package/templates/typical/client/src/components/ui/AlertDialog.tsx +0 -178
- package/templates/typical/client/src/components/ui/AspectRatio.stories.tsx +0 -35
- package/templates/typical/client/src/components/ui/AspectRatio.tsx +0 -13
- package/templates/typical/client/src/components/ui/Avatar.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/Avatar.tsx +0 -98
- package/templates/typical/client/src/components/ui/Badge.stories.tsx +0 -41
- package/templates/typical/client/src/components/ui/Badge.tsx +0 -45
- package/templates/typical/client/src/components/ui/Breadcrumb.stories.tsx +0 -54
- package/templates/typical/client/src/components/ui/Breadcrumb.tsx +0 -104
- package/templates/typical/client/src/components/ui/Button.stories.tsx +0 -102
- package/templates/typical/client/src/components/ui/Button.tsx +0 -67
- package/templates/typical/client/src/components/ui/ButtonGroup.stories.tsx +0 -32
- package/templates/typical/client/src/components/ui/ButtonGroup.tsx +0 -81
- package/templates/typical/client/src/components/ui/Calendar.stories.tsx +0 -40
- package/templates/typical/client/src/components/ui/Calendar.tsx +0 -165
- package/templates/typical/client/src/components/ui/Card.stories.tsx +0 -44
- package/templates/typical/client/src/components/ui/Card.tsx +0 -66
- package/templates/typical/client/src/components/ui/Carousel.stories.tsx +0 -56
- package/templates/typical/client/src/components/ui/Carousel.tsx +0 -225
- package/templates/typical/client/src/components/ui/Chart.stories.tsx +0 -39
- package/templates/typical/client/src/components/ui/Chart.tsx +0 -305
- package/templates/typical/client/src/components/ui/Checkbox.stories.tsx +0 -35
- package/templates/typical/client/src/components/ui/Checkbox.tsx +0 -30
- package/templates/typical/client/src/components/ui/Collapsible.stories.tsx +0 -58
- package/templates/typical/client/src/components/ui/Collapsible.tsx +0 -18
- package/templates/typical/client/src/components/ui/Combobox.stories.tsx +0 -75
- package/templates/typical/client/src/components/ui/Combobox.tsx +0 -296
- package/templates/typical/client/src/components/ui/Command.stories.tsx +0 -71
- package/templates/typical/client/src/components/ui/Command.tsx +0 -157
- package/templates/typical/client/src/components/ui/ContextMenu.stories.tsx +0 -68
- package/templates/typical/client/src/components/ui/ContextMenu.tsx +0 -231
- package/templates/typical/client/src/components/ui/DesignSystem-Colors.mdx +0 -68
- package/templates/typical/client/src/components/ui/DesignSystem-Colors.stories.tsx +0 -117
- package/templates/typical/client/src/components/ui/DesignSystem-Layout.mdx +0 -64
- package/templates/typical/client/src/components/ui/DesignSystem-Layout.stories.tsx +0 -167
- package/templates/typical/client/src/components/ui/DesignSystem-Overview.stories.tsx +0 -748
- package/templates/typical/client/src/components/ui/DesignSystem-Typography.mdx +0 -31
- package/templates/typical/client/src/components/ui/DesignSystem-Typography.stories.tsx +0 -80
- package/templates/typical/client/src/components/ui/Dialog.stories.tsx +0 -74
- package/templates/typical/client/src/components/ui/Dialog.tsx +0 -154
- package/templates/typical/client/src/components/ui/Direction.stories.tsx +0 -38
- package/templates/typical/client/src/components/ui/Direction.tsx +0 -24
- package/templates/typical/client/src/components/ui/Drawer.stories.tsx +0 -70
- package/templates/typical/client/src/components/ui/Drawer.tsx +0 -124
- package/templates/typical/client/src/components/ui/DropdownMenu.stories.tsx +0 -74
- package/templates/typical/client/src/components/ui/DropdownMenu.tsx +0 -239
- package/templates/typical/client/src/components/ui/Empty.stories.tsx +0 -37
- package/templates/typical/client/src/components/ui/Empty.tsx +0 -98
- package/templates/typical/client/src/components/ui/Field.stories.tsx +0 -50
- package/templates/typical/client/src/components/ui/Field.tsx +0 -251
- package/templates/typical/client/src/components/ui/Form.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/Form.tsx +0 -148
- package/templates/typical/client/src/components/ui/HoverCard.stories.tsx +0 -49
- package/templates/typical/client/src/components/ui/HoverCard.tsx +0 -39
- package/templates/typical/client/src/components/ui/Input.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/Input.tsx +0 -22
- package/templates/typical/client/src/components/ui/InputGroup.stories.tsx +0 -53
- package/templates/typical/client/src/components/ui/InputGroup.tsx +0 -153
- package/templates/typical/client/src/components/ui/InputOTP.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/InputOTP.tsx +0 -72
- package/templates/typical/client/src/components/ui/Item.stories.tsx +0 -64
- package/templates/typical/client/src/components/ui/Item.tsx +0 -168
- package/templates/typical/client/src/components/ui/Kbd.stories.tsx +0 -59
- package/templates/typical/client/src/components/ui/Kbd.tsx +0 -22
- package/templates/typical/client/src/components/ui/Label.stories.tsx +0 -90
- package/templates/typical/client/src/components/ui/Label.tsx +0 -44
- package/templates/typical/client/src/components/ui/Menubar.stories.tsx +0 -78
- package/templates/typical/client/src/components/ui/Menubar.tsx +0 -251
- package/templates/typical/client/src/components/ui/NativeSelect.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/NativeSelect.tsx +0 -50
- package/templates/typical/client/src/components/ui/NavigationMenu.stories.tsx +0 -80
- package/templates/typical/client/src/components/ui/NavigationMenu.tsx +0 -152
- package/templates/typical/client/src/components/ui/Pagination.stories.tsx +0 -77
- package/templates/typical/client/src/components/ui/Pagination.tsx +0 -108
- package/templates/typical/client/src/components/ui/Popover.stories.tsx +0 -53
- package/templates/typical/client/src/components/ui/Popover.tsx +0 -57
- package/templates/typical/client/src/components/ui/Progress.stories.tsx +0 -32
- package/templates/typical/client/src/components/ui/Progress.tsx +0 -25
- package/templates/typical/client/src/components/ui/RadioGroup.stories.tsx +0 -50
- package/templates/typical/client/src/components/ui/RadioGroup.tsx +0 -36
- package/templates/typical/client/src/components/ui/Resizable.stories.tsx +0 -72
- package/templates/typical/client/src/components/ui/Resizable.tsx +0 -54
- package/templates/typical/client/src/components/ui/ScrollArea.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/ScrollArea.tsx +0 -51
- package/templates/typical/client/src/components/ui/Select.stories.tsx +0 -59
- package/templates/typical/client/src/components/ui/Select.tsx +0 -171
- package/templates/typical/client/src/components/ui/Separator.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/Separator.tsx +0 -27
- package/templates/typical/client/src/components/ui/Sheet.stories.tsx +0 -68
- package/templates/typical/client/src/components/ui/Sheet.tsx +0 -115
- package/templates/typical/client/src/components/ui/Sidebar.stories.tsx +0 -96
- package/templates/typical/client/src/components/ui/Sidebar.tsx +0 -695
- package/templates/typical/client/src/components/ui/Skeleton.stories.tsx +0 -40
- package/templates/typical/client/src/components/ui/Skeleton.tsx +0 -11
- package/templates/typical/client/src/components/ui/Slider.stories.tsx +0 -24
- package/templates/typical/client/src/components/ui/Slider.tsx +0 -55
- package/templates/typical/client/src/components/ui/Sonner.stories.tsx +0 -45
- package/templates/typical/client/src/components/ui/Sonner.tsx +0 -38
- package/templates/typical/client/src/components/ui/Spinner.stories.tsx +0 -26
- package/templates/typical/client/src/components/ui/Spinner.tsx +0 -13
- package/templates/typical/client/src/components/ui/Switch.stories.tsx +0 -39
- package/templates/typical/client/src/components/ui/Switch.tsx +0 -35
- package/templates/typical/client/src/components/ui/Table.stories.tsx +0 -67
- package/templates/typical/client/src/components/ui/Table.tsx +0 -86
- package/templates/typical/client/src/components/ui/Tabs.stories.tsx +0 -53
- package/templates/typical/client/src/components/ui/Tabs.tsx +0 -75
- package/templates/typical/client/src/components/ui/Textarea.stories.tsx +0 -27
- package/templates/typical/client/src/components/ui/Textarea.tsx +0 -22
- package/templates/typical/client/src/components/ui/Toast.stories.tsx +0 -116
- package/templates/typical/client/src/components/ui/Toast.tsx +0 -123
- package/templates/typical/client/src/components/ui/Toaster.tsx +0 -32
- package/templates/typical/client/src/components/ui/Toggle.stories.tsx +0 -44
- package/templates/typical/client/src/components/ui/Toggle.tsx +0 -42
- package/templates/typical/client/src/components/ui/ToggleGroup.stories.tsx +0 -61
- package/templates/typical/client/src/components/ui/ToggleGroup.tsx +0 -83
- package/templates/typical/client/src/components/ui/Tooltip.stories.tsx +0 -42
- package/templates/typical/client/src/components/ui/Tooltip.tsx +0 -48
- package/templates/typical/client/src/gql/execute.ts +0 -11
- package/templates/typical/client/src/gql/fragment-masking.ts +0 -83
- package/templates/typical/client/src/gql/gql.ts +0 -9
- package/templates/typical/client/src/gql/graphql.ts +0 -182
- package/templates/typical/client/src/gql/index.ts +0 -2
- package/templates/typical/client/src/graphql/mutations.ts +0 -0
- package/templates/typical/client/src/graphql/queries.ts +0 -0
- package/templates/typical/client/src/hooks/.gitkeep +0 -0
- package/templates/typical/client/src/hooks/use-mobile.ts +0 -19
- package/templates/typical/client/src/hooks/use-toast.ts +0 -186
- package/templates/typical/client/src/index.css +0 -121
- package/templates/typical/client/src/lib/utils.ts +0 -6
- package/templates/typical/client/src/main.tsx +0 -5
- package/templates/typical/client/tsconfig.app.json +0 -26
- package/templates/typical/client/tsconfig.json +0 -10
- package/templates/typical/client/vite.config.ts +0 -50
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
|
|
5
|
-
import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
|
|
6
|
-
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
8
|
-
|
|
9
|
-
/** Action menu triggered by a button click. Supports submenus, checkbox/radio items, and keyboard shortcuts. */
|
|
10
|
-
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
11
|
-
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Renders dropdown menu content into a React portal. */
|
|
15
|
-
function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
16
|
-
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Button or element that toggles the dropdown menu open/closed. */
|
|
20
|
-
function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
21
|
-
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** The positioned popup container for dropdown menu items. Portals to the document body. */
|
|
25
|
-
function DropdownMenuContent({
|
|
26
|
-
className,
|
|
27
|
-
sideOffset = 4,
|
|
28
|
-
...props
|
|
29
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
30
|
-
return (
|
|
31
|
-
<DropdownMenuPrimitive.Portal>
|
|
32
|
-
<DropdownMenuPrimitive.Content
|
|
33
|
-
data-slot="dropdown-menu-content"
|
|
34
|
-
sideOffset={sideOffset}
|
|
35
|
-
className={cn(
|
|
36
|
-
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
|
37
|
-
className,
|
|
38
|
-
)}
|
|
39
|
-
{...props}
|
|
40
|
-
/>
|
|
41
|
-
</DropdownMenuPrimitive.Portal>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Groups related dropdown menu items for accessibility. */
|
|
46
|
-
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
47
|
-
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* A selectable action within the dropdown menu.
|
|
52
|
-
* Use variant="destructive" for dangerous actions like delete or logout.
|
|
53
|
-
*/
|
|
54
|
-
function DropdownMenuItem({
|
|
55
|
-
className,
|
|
56
|
-
/** Adds left padding to align with items that have icons or indicators. */
|
|
57
|
-
inset,
|
|
58
|
-
/** Use "destructive" for dangerous actions. */
|
|
59
|
-
variant = 'default',
|
|
60
|
-
...props
|
|
61
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
62
|
-
inset?: boolean;
|
|
63
|
-
variant?: 'default' | 'destructive';
|
|
64
|
-
}) {
|
|
65
|
-
return (
|
|
66
|
-
<DropdownMenuPrimitive.Item
|
|
67
|
-
data-slot="dropdown-menu-item"
|
|
68
|
-
data-inset={inset}
|
|
69
|
-
data-variant={variant}
|
|
70
|
-
className={cn(
|
|
71
|
-
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
72
|
-
className,
|
|
73
|
-
)}
|
|
74
|
-
{...props}
|
|
75
|
-
/>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** A toggleable checkbox item within the dropdown menu. Shows a check indicator when active. */
|
|
80
|
-
function DropdownMenuCheckboxItem({
|
|
81
|
-
className,
|
|
82
|
-
children,
|
|
83
|
-
checked,
|
|
84
|
-
...props
|
|
85
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
86
|
-
return (
|
|
87
|
-
<DropdownMenuPrimitive.CheckboxItem
|
|
88
|
-
data-slot="dropdown-menu-checkbox-item"
|
|
89
|
-
className={cn(
|
|
90
|
-
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
91
|
-
className,
|
|
92
|
-
)}
|
|
93
|
-
checked={checked}
|
|
94
|
-
{...props}
|
|
95
|
-
>
|
|
96
|
-
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
97
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
98
|
-
<CheckIcon className="size-4" />
|
|
99
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
100
|
-
</span>
|
|
101
|
-
{children}
|
|
102
|
-
</DropdownMenuPrimitive.CheckboxItem>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Groups radio items for single-selection within the dropdown menu. */
|
|
107
|
-
function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
108
|
-
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** A radio-selectable item within a DropdownMenuRadioGroup. Shows a dot indicator when selected. */
|
|
112
|
-
function DropdownMenuRadioItem({
|
|
113
|
-
className,
|
|
114
|
-
children,
|
|
115
|
-
...props
|
|
116
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
117
|
-
return (
|
|
118
|
-
<DropdownMenuPrimitive.RadioItem
|
|
119
|
-
data-slot="dropdown-menu-radio-item"
|
|
120
|
-
className={cn(
|
|
121
|
-
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
122
|
-
className,
|
|
123
|
-
)}
|
|
124
|
-
{...props}
|
|
125
|
-
>
|
|
126
|
-
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
127
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
128
|
-
<CircleIcon className="size-2 fill-current" />
|
|
129
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
130
|
-
</span>
|
|
131
|
-
{children}
|
|
132
|
-
</DropdownMenuPrimitive.RadioItem>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Non-interactive label within a dropdown menu, used for section headings. */
|
|
137
|
-
function DropdownMenuLabel({
|
|
138
|
-
className,
|
|
139
|
-
inset,
|
|
140
|
-
...props
|
|
141
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
142
|
-
inset?: boolean;
|
|
143
|
-
}) {
|
|
144
|
-
return (
|
|
145
|
-
<DropdownMenuPrimitive.Label
|
|
146
|
-
data-slot="dropdown-menu-label"
|
|
147
|
-
data-inset={inset}
|
|
148
|
-
className={cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', className)}
|
|
149
|
-
{...props}
|
|
150
|
-
/>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Visual divider between groups of dropdown menu items. */
|
|
155
|
-
function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
156
|
-
return (
|
|
157
|
-
<DropdownMenuPrimitive.Separator
|
|
158
|
-
data-slot="dropdown-menu-separator"
|
|
159
|
-
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
|
160
|
-
{...props}
|
|
161
|
-
/>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Displays a keyboard shortcut hint aligned to the right side of a dropdown menu item. */
|
|
166
|
-
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
|
|
167
|
-
return (
|
|
168
|
-
<span
|
|
169
|
-
data-slot="dropdown-menu-shortcut"
|
|
170
|
-
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
|
|
171
|
-
{...props}
|
|
172
|
-
/>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Container for a submenu within the dropdown menu. */
|
|
177
|
-
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
178
|
-
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Menu item that opens a nested submenu on hover or keyboard navigation. Shows a chevron indicator. */
|
|
182
|
-
function DropdownMenuSubTrigger({
|
|
183
|
-
className,
|
|
184
|
-
inset,
|
|
185
|
-
children,
|
|
186
|
-
...props
|
|
187
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
188
|
-
inset?: boolean;
|
|
189
|
-
}) {
|
|
190
|
-
return (
|
|
191
|
-
<DropdownMenuPrimitive.SubTrigger
|
|
192
|
-
data-slot="dropdown-menu-sub-trigger"
|
|
193
|
-
data-inset={inset}
|
|
194
|
-
className={cn(
|
|
195
|
-
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
196
|
-
className,
|
|
197
|
-
)}
|
|
198
|
-
{...props}
|
|
199
|
-
>
|
|
200
|
-
{children}
|
|
201
|
-
<ChevronRightIcon className="ml-auto size-4" />
|
|
202
|
-
</DropdownMenuPrimitive.SubTrigger>
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/** Popup content for a nested submenu. */
|
|
207
|
-
function DropdownMenuSubContent({
|
|
208
|
-
className,
|
|
209
|
-
...props
|
|
210
|
-
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
211
|
-
return (
|
|
212
|
-
<DropdownMenuPrimitive.SubContent
|
|
213
|
-
data-slot="dropdown-menu-sub-content"
|
|
214
|
-
className={cn(
|
|
215
|
-
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
|
216
|
-
className,
|
|
217
|
-
)}
|
|
218
|
-
{...props}
|
|
219
|
-
/>
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export {
|
|
224
|
-
DropdownMenu,
|
|
225
|
-
DropdownMenuPortal,
|
|
226
|
-
DropdownMenuTrigger,
|
|
227
|
-
DropdownMenuContent,
|
|
228
|
-
DropdownMenuGroup,
|
|
229
|
-
DropdownMenuLabel,
|
|
230
|
-
DropdownMenuItem,
|
|
231
|
-
DropdownMenuCheckboxItem,
|
|
232
|
-
DropdownMenuRadioGroup,
|
|
233
|
-
DropdownMenuRadioItem,
|
|
234
|
-
DropdownMenuSeparator,
|
|
235
|
-
DropdownMenuShortcut,
|
|
236
|
-
DropdownMenuSub,
|
|
237
|
-
DropdownMenuSubTrigger,
|
|
238
|
-
DropdownMenuSubContent,
|
|
239
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyMedia } from '@/components/ui/Empty';
|
|
3
|
-
import { InboxIcon } from 'lucide-react';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<typeof Empty> = {
|
|
6
|
-
title: 'UI Components/Empty',
|
|
7
|
-
component: Empty,
|
|
8
|
-
};
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof Empty>;
|
|
11
|
-
|
|
12
|
-
/** Shows a basic empty state with a title and description text. */
|
|
13
|
-
export const Default: Story = {
|
|
14
|
-
render: () => (
|
|
15
|
-
<Empty>
|
|
16
|
-
<EmptyHeader>
|
|
17
|
-
<EmptyTitle>No results found</EmptyTitle>
|
|
18
|
-
<EmptyDescription>Try adjusting your search or filter to find what you are looking for.</EmptyDescription>
|
|
19
|
-
</EmptyHeader>
|
|
20
|
-
</Empty>
|
|
21
|
-
),
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/** Shows an empty state with a styled icon container above the title and description. */
|
|
25
|
-
export const WithIcon: Story = {
|
|
26
|
-
render: () => (
|
|
27
|
-
<Empty>
|
|
28
|
-
<EmptyHeader>
|
|
29
|
-
<EmptyMedia variant="icon">
|
|
30
|
-
<InboxIcon />
|
|
31
|
-
</EmptyMedia>
|
|
32
|
-
<EmptyTitle>Your inbox is empty</EmptyTitle>
|
|
33
|
-
<EmptyDescription>New messages will appear here when you receive them.</EmptyDescription>
|
|
34
|
-
</EmptyHeader>
|
|
35
|
-
</Empty>
|
|
36
|
-
),
|
|
37
|
-
};
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
-
|
|
3
|
-
import { cn } from '@/lib/utils';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A placeholder component for empty states, such as no search results or empty inboxes.
|
|
7
|
-
* Compose with EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, and EmptyContent
|
|
8
|
-
* to build structured empty state layouts with icons and call-to-action areas.
|
|
9
|
-
*/
|
|
10
|
-
function Empty({ className, ...props }: React.ComponentProps<'div'>) {
|
|
11
|
-
return (
|
|
12
|
-
<div
|
|
13
|
-
data-slot="empty"
|
|
14
|
-
className={cn(
|
|
15
|
-
'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12',
|
|
16
|
-
className,
|
|
17
|
-
)}
|
|
18
|
-
{...props}
|
|
19
|
-
/>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Container for the title, description, and optional media in an empty state. */
|
|
24
|
-
function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
25
|
-
return (
|
|
26
|
-
<div
|
|
27
|
-
data-slot="empty-header"
|
|
28
|
-
className={cn('flex max-w-sm flex-col items-center gap-2 text-center', className)}
|
|
29
|
-
{...props}
|
|
30
|
-
/>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const emptyMediaVariants = cva(
|
|
35
|
-
'flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
36
|
-
{
|
|
37
|
-
variants: {
|
|
38
|
-
variant: {
|
|
39
|
-
default: 'bg-transparent',
|
|
40
|
-
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
defaultVariants: {
|
|
44
|
-
variant: 'default',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Container for an icon or illustration in the empty state.
|
|
51
|
-
* Use variant="icon" for a styled icon container with a muted background.
|
|
52
|
-
*/
|
|
53
|
-
function EmptyMedia({
|
|
54
|
-
className,
|
|
55
|
-
variant = 'default',
|
|
56
|
-
...props
|
|
57
|
-
}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {
|
|
58
|
-
return (
|
|
59
|
-
<div
|
|
60
|
-
data-slot="empty-icon"
|
|
61
|
-
data-variant={variant}
|
|
62
|
-
className={cn(emptyMediaVariants({ variant, className }))}
|
|
63
|
-
{...props}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Title text for the empty state message. */
|
|
69
|
-
function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
70
|
-
return <div data-slot="empty-title" className={cn('text-lg font-medium tracking-tight', className)} {...props} />;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Descriptive text explaining why the content is empty or what the user can do. */
|
|
74
|
-
function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
75
|
-
return (
|
|
76
|
-
<div
|
|
77
|
-
data-slot="empty-description"
|
|
78
|
-
className={cn(
|
|
79
|
-
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
|
|
80
|
-
className,
|
|
81
|
-
)}
|
|
82
|
-
{...props}
|
|
83
|
-
/>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Container for action buttons or additional content below the empty state message. */
|
|
88
|
-
function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
89
|
-
return (
|
|
90
|
-
<div
|
|
91
|
-
data-slot="empty-content"
|
|
92
|
-
className={cn('flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance', className)}
|
|
93
|
-
{...props}
|
|
94
|
-
/>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia };
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { Field, FieldLabel, FieldDescription, FieldError, FieldContent } from '@/components/ui/Field';
|
|
3
|
-
import { Input } from '@/components/ui/Input';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<typeof Field> = {
|
|
6
|
-
title: 'UI Components/Field',
|
|
7
|
-
component: Field,
|
|
8
|
-
};
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof Field>;
|
|
11
|
-
|
|
12
|
-
/** Shows a vertical form field with label, input, and helper description text. */
|
|
13
|
-
export const Default: Story = {
|
|
14
|
-
render: () => (
|
|
15
|
-
<Field>
|
|
16
|
-
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
17
|
-
<FieldContent>
|
|
18
|
-
<Input id="email" type="email" placeholder="you@example.com" />
|
|
19
|
-
<FieldDescription>We will never share your email.</FieldDescription>
|
|
20
|
-
</FieldContent>
|
|
21
|
-
</Field>
|
|
22
|
-
),
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** Shows a field in an invalid state with error styling and a validation error message. */
|
|
26
|
-
export const WithError: Story = {
|
|
27
|
-
render: () => (
|
|
28
|
-
<Field data-invalid="true">
|
|
29
|
-
<FieldLabel htmlFor="email-err">Email</FieldLabel>
|
|
30
|
-
<FieldContent>
|
|
31
|
-
<Input id="email-err" type="email" defaultValue="invalid-email" aria-invalid="true" />
|
|
32
|
-
<FieldDescription>Enter a valid email address.</FieldDescription>
|
|
33
|
-
<FieldError>Please enter a valid email address.</FieldError>
|
|
34
|
-
</FieldContent>
|
|
35
|
-
</Field>
|
|
36
|
-
),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/** Shows a field with horizontal orientation where the label and input are side by side. */
|
|
40
|
-
export const Horizontal: Story = {
|
|
41
|
-
render: () => (
|
|
42
|
-
<Field orientation="horizontal">
|
|
43
|
-
<FieldLabel htmlFor="name-h">Full Name</FieldLabel>
|
|
44
|
-
<FieldContent>
|
|
45
|
-
<Input id="name-h" placeholder="John Doe" />
|
|
46
|
-
<FieldDescription>Your first and last name.</FieldDescription>
|
|
47
|
-
</FieldContent>
|
|
48
|
-
</Field>
|
|
49
|
-
),
|
|
50
|
-
};
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useMemo } from 'react';
|
|
4
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
-
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
7
|
-
import { Label } from '@/components/ui/Label';
|
|
8
|
-
import { Separator } from '@/components/ui/Separator';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A semantic fieldset container for grouping related form fields.
|
|
12
|
-
* Use as a top-level wrapper around FieldGroup or multiple Field components.
|
|
13
|
-
*/
|
|
14
|
-
function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
|
|
15
|
-
return (
|
|
16
|
-
<fieldset
|
|
17
|
-
data-slot="field-set"
|
|
18
|
-
className={cn(
|
|
19
|
-
'flex flex-col gap-6',
|
|
20
|
-
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
|
21
|
-
className,
|
|
22
|
-
)}
|
|
23
|
-
{...props}
|
|
24
|
-
/>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* A legend element for a FieldSet, displayed as the group heading.
|
|
30
|
-
* Use variant="label" for a smaller label-sized heading.
|
|
31
|
-
*/
|
|
32
|
-
function FieldLegend({
|
|
33
|
-
className,
|
|
34
|
-
variant = 'legend',
|
|
35
|
-
...props
|
|
36
|
-
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
|
|
37
|
-
return (
|
|
38
|
-
<legend
|
|
39
|
-
data-slot="field-legend"
|
|
40
|
-
data-variant={variant}
|
|
41
|
-
className={cn('mb-3 font-medium', 'data-[variant=legend]:text-base', 'data-[variant=label]:text-sm', className)}
|
|
42
|
-
{...props}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Container for a group of Field components. Provides container query context for responsive layouts. */
|
|
48
|
-
function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
|
49
|
-
return (
|
|
50
|
-
<div
|
|
51
|
-
data-slot="field-group"
|
|
52
|
-
className={cn(
|
|
53
|
-
'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
|
|
54
|
-
className,
|
|
55
|
-
)}
|
|
56
|
-
{...props}
|
|
57
|
-
/>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const fieldVariants = cva('group/field flex w-full gap-3 data-[invalid=true]:text-destructive', {
|
|
62
|
-
variants: {
|
|
63
|
-
orientation: {
|
|
64
|
-
vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
|
|
65
|
-
horizontal: [
|
|
66
|
-
'flex-row items-center',
|
|
67
|
-
'[&>[data-slot=field-label]]:flex-auto',
|
|
68
|
-
'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
69
|
-
],
|
|
70
|
-
responsive: [
|
|
71
|
-
'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto',
|
|
72
|
-
'@md/field-group:[&>[data-slot=field-label]]:flex-auto',
|
|
73
|
-
'@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
defaultVariants: {
|
|
78
|
-
orientation: 'vertical',
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* A form field wrapper that pairs a label with an input and optional description/error.
|
|
84
|
-
* Supports vertical, horizontal, and responsive orientations.
|
|
85
|
-
* Set data-invalid="true" to apply error styling.
|
|
86
|
-
*/
|
|
87
|
-
function Field({
|
|
88
|
-
className,
|
|
89
|
-
/** Layout direction: "vertical" stacks label above input, "horizontal" places them side by side, "responsive" switches at the md breakpoint. */
|
|
90
|
-
orientation = 'vertical',
|
|
91
|
-
...props
|
|
92
|
-
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
|
|
93
|
-
return (
|
|
94
|
-
<div
|
|
95
|
-
role="group"
|
|
96
|
-
data-slot="field"
|
|
97
|
-
data-orientation={orientation}
|
|
98
|
-
className={cn(fieldVariants({ orientation }), className)}
|
|
99
|
-
{...props}
|
|
100
|
-
/>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Container for the input element plus optional description and error within a Field. */
|
|
105
|
-
function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
106
|
-
return (
|
|
107
|
-
<div
|
|
108
|
-
data-slot="field-content"
|
|
109
|
-
className={cn('group/field-content flex flex-1 flex-col gap-1.5 leading-snug', className)}
|
|
110
|
-
{...props}
|
|
111
|
-
/>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Label for a form field. Supports wrapping checkbox/radio inputs for card-style selection. */
|
|
116
|
-
function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
|
|
117
|
-
return (
|
|
118
|
-
<Label
|
|
119
|
-
data-slot="field-label"
|
|
120
|
-
className={cn(
|
|
121
|
-
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
|
|
122
|
-
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4',
|
|
123
|
-
'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
|
|
124
|
-
className,
|
|
125
|
-
)}
|
|
126
|
-
{...props}
|
|
127
|
-
/>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Non-interactive title text for a field, used when a label association is not needed. */
|
|
132
|
-
function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
133
|
-
return (
|
|
134
|
-
<div
|
|
135
|
-
data-slot="field-label"
|
|
136
|
-
className={cn(
|
|
137
|
-
'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
|
|
138
|
-
className,
|
|
139
|
-
)}
|
|
140
|
-
{...props}
|
|
141
|
-
/>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** Helper text displayed below the input to provide additional guidance. */
|
|
146
|
-
function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
147
|
-
return (
|
|
148
|
-
<p
|
|
149
|
-
data-slot="field-description"
|
|
150
|
-
className={cn(
|
|
151
|
-
'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
|
|
152
|
-
'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',
|
|
153
|
-
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
|
154
|
-
className,
|
|
155
|
-
)}
|
|
156
|
-
{...props}
|
|
157
|
-
/>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** A visual separator between fields within a FieldGroup. Optionally displays centered text. */
|
|
162
|
-
function FieldSeparator({
|
|
163
|
-
children,
|
|
164
|
-
className,
|
|
165
|
-
...props
|
|
166
|
-
}: React.ComponentProps<'div'> & {
|
|
167
|
-
children?: React.ReactNode;
|
|
168
|
-
}) {
|
|
169
|
-
return (
|
|
170
|
-
<div
|
|
171
|
-
data-slot="field-separator"
|
|
172
|
-
data-content={!!children}
|
|
173
|
-
className={cn('relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2', className)}
|
|
174
|
-
{...props}
|
|
175
|
-
>
|
|
176
|
-
<Separator className="absolute inset-0 top-1/2" />
|
|
177
|
-
{children && (
|
|
178
|
-
<span
|
|
179
|
-
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
|
|
180
|
-
data-slot="field-separator-content"
|
|
181
|
-
>
|
|
182
|
-
{children}
|
|
183
|
-
</span>
|
|
184
|
-
)}
|
|
185
|
-
</div>
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Displays validation error messages for a form field.
|
|
191
|
-
* Accepts either children or an errors array; deduplicates and renders as a list if multiple.
|
|
192
|
-
*/
|
|
193
|
-
function FieldError({
|
|
194
|
-
className,
|
|
195
|
-
children,
|
|
196
|
-
/** Array of error objects with message strings. Automatically deduplicated. */
|
|
197
|
-
errors,
|
|
198
|
-
...props
|
|
199
|
-
}: React.ComponentProps<'div'> & {
|
|
200
|
-
errors?: Array<{ message?: string } | undefined>;
|
|
201
|
-
}) {
|
|
202
|
-
const content = useMemo(() => {
|
|
203
|
-
if (children) {
|
|
204
|
-
return children;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (!errors?.length) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
|
|
212
|
-
|
|
213
|
-
if (uniqueErrors?.length == 1) {
|
|
214
|
-
return uniqueErrors[0]?.message;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return (
|
|
218
|
-
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
219
|
-
{uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
|
|
220
|
-
</ul>
|
|
221
|
-
);
|
|
222
|
-
}, [children, errors]);
|
|
223
|
-
|
|
224
|
-
if (!content) {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return (
|
|
229
|
-
<div
|
|
230
|
-
role="alert"
|
|
231
|
-
data-slot="field-error"
|
|
232
|
-
className={cn('text-destructive text-sm font-normal', className)}
|
|
233
|
-
{...props}
|
|
234
|
-
>
|
|
235
|
-
{content}
|
|
236
|
-
</div>
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export {
|
|
241
|
-
Field,
|
|
242
|
-
FieldLabel,
|
|
243
|
-
FieldDescription,
|
|
244
|
-
FieldError,
|
|
245
|
-
FieldGroup,
|
|
246
|
-
FieldLegend,
|
|
247
|
-
FieldSeparator,
|
|
248
|
-
FieldSet,
|
|
249
|
-
FieldContent,
|
|
250
|
-
FieldTitle,
|
|
251
|
-
};
|