@auto-engineer/generate-react-client 1.12.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.
Files changed (154) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +10 -0
  3. package/dist/src/commands/generate-react-client.d.ts +21 -0
  4. package/dist/src/commands/generate-react-client.d.ts.map +1 -0
  5. package/dist/src/commands/generate-react-client.js +62 -0
  6. package/dist/src/commands/generate-react-client.js.map +1 -0
  7. package/dist/src/copy-starter.d.ts +2 -0
  8. package/dist/src/copy-starter.d.ts.map +1 -0
  9. package/dist/src/copy-starter.js +34 -0
  10. package/dist/src/copy-starter.js.map +1 -0
  11. package/dist/src/index.d.ts +10 -0
  12. package/dist/src/index.d.ts.map +1 -0
  13. package/dist/src/index.js +4 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/starter/.storybook/main.ts +33 -0
  16. package/dist/starter/.storybook/preview.tsx +35 -0
  17. package/dist/starter/components.json +29 -0
  18. package/dist/starter/index.html +12 -0
  19. package/dist/starter/package.json +60 -0
  20. package/dist/starter/pnpm-lock.yaml +5236 -0
  21. package/dist/starter/public/mockServiceWorker.js +336 -0
  22. package/dist/starter/src/App.tsx +15 -0
  23. package/dist/starter/src/components/.gitkeep +0 -0
  24. package/dist/starter/src/components/ui/Accordion.stories.tsx +47 -0
  25. package/dist/starter/src/components/ui/Accordion.tsx +51 -0
  26. package/dist/starter/src/components/ui/Alert.stories.tsx +27 -0
  27. package/dist/starter/src/components/ui/Alert.tsx +49 -0
  28. package/dist/starter/src/components/ui/AlertDialog.stories.tsx +65 -0
  29. package/dist/starter/src/components/ui/AlertDialog.tsx +163 -0
  30. package/dist/starter/src/components/ui/AspectRatio.stories.tsx +33 -0
  31. package/dist/starter/src/components/ui/AspectRatio.tsx +9 -0
  32. package/dist/starter/src/components/ui/Avatar.stories.tsx +42 -0
  33. package/dist/starter/src/components/ui/Avatar.tsx +87 -0
  34. package/dist/starter/src/components/ui/Badge.stories.tsx +36 -0
  35. package/dist/starter/src/components/ui/Badge.tsx +40 -0
  36. package/dist/starter/src/components/ui/Breadcrumb.stories.tsx +52 -0
  37. package/dist/starter/src/components/ui/Breadcrumb.tsx +92 -0
  38. package/dist/starter/src/components/ui/Button.stories.tsx +92 -0
  39. package/dist/starter/src/components/ui/Button.tsx +62 -0
  40. package/dist/starter/src/components/ui/ButtonGroup.stories.tsx +30 -0
  41. package/dist/starter/src/components/ui/ButtonGroup.tsx +75 -0
  42. package/dist/starter/src/components/ui/Calendar.stories.tsx +38 -0
  43. package/dist/starter/src/components/ui/Calendar.tsx +159 -0
  44. package/dist/starter/src/components/ui/Card.stories.tsx +42 -0
  45. package/dist/starter/src/components/ui/Card.tsx +56 -0
  46. package/dist/starter/src/components/ui/Carousel.stories.tsx +54 -0
  47. package/dist/starter/src/components/ui/Carousel.tsx +216 -0
  48. package/dist/starter/src/components/ui/Chart.stories.tsx +38 -0
  49. package/dist/starter/src/components/ui/Chart.tsx +296 -0
  50. package/dist/starter/src/components/ui/Checkbox.stories.tsx +31 -0
  51. package/dist/starter/src/components/ui/Checkbox.tsx +29 -0
  52. package/dist/starter/src/components/ui/Collapsible.stories.tsx +56 -0
  53. package/dist/starter/src/components/ui/Collapsible.tsx +15 -0
  54. package/dist/starter/src/components/ui/Combobox.stories.tsx +73 -0
  55. package/dist/starter/src/components/ui/Combobox.tsx +267 -0
  56. package/dist/starter/src/components/ui/Command.stories.tsx +69 -0
  57. package/dist/starter/src/components/ui/Command.tsx +137 -0
  58. package/dist/starter/src/components/ui/ContextMenu.stories.tsx +66 -0
  59. package/dist/starter/src/components/ui/ContextMenu.tsx +211 -0
  60. package/dist/starter/src/components/ui/DesignSystem-Colors.mdx +68 -0
  61. package/dist/starter/src/components/ui/DesignSystem-Colors.stories.tsx +116 -0
  62. package/dist/starter/src/components/ui/DesignSystem-Layout.mdx +64 -0
  63. package/dist/starter/src/components/ui/DesignSystem-Layout.stories.tsx +166 -0
  64. package/dist/starter/src/components/ui/DesignSystem-Typography.mdx +31 -0
  65. package/dist/starter/src/components/ui/DesignSystem-Typography.stories.tsx +79 -0
  66. package/dist/starter/src/components/ui/Dialog.stories.tsx +72 -0
  67. package/dist/starter/src/components/ui/Dialog.tsx +136 -0
  68. package/dist/starter/src/components/ui/Direction.stories.tsx +36 -0
  69. package/dist/starter/src/components/ui/Direction.tsx +18 -0
  70. package/dist/starter/src/components/ui/Drawer.stories.tsx +68 -0
  71. package/dist/starter/src/components/ui/Drawer.tsx +106 -0
  72. package/dist/starter/src/components/ui/DropdownMenu.stories.tsx +72 -0
  73. package/dist/starter/src/components/ui/DropdownMenu.tsx +219 -0
  74. package/dist/starter/src/components/ui/Empty.stories.tsx +35 -0
  75. package/dist/starter/src/components/ui/Empty.tsx +85 -0
  76. package/dist/starter/src/components/ui/Field.stories.tsx +47 -0
  77. package/dist/starter/src/components/ui/Field.tsx +226 -0
  78. package/dist/starter/src/components/ui/Form.stories.tsx +44 -0
  79. package/dist/starter/src/components/ui/Form.tsx +136 -0
  80. package/dist/starter/src/components/ui/HoverCard.stories.tsx +47 -0
  81. package/dist/starter/src/components/ui/HoverCard.tsx +36 -0
  82. package/dist/starter/src/components/ui/Input.stories.tsx +38 -0
  83. package/dist/starter/src/components/ui/Input.tsx +21 -0
  84. package/dist/starter/src/components/ui/InputGroup.stories.tsx +50 -0
  85. package/dist/starter/src/components/ui/InputGroup.tsx +147 -0
  86. package/dist/starter/src/components/ui/InputOTP.stories.tsx +40 -0
  87. package/dist/starter/src/components/ui/InputOTP.tsx +68 -0
  88. package/dist/starter/src/components/ui/Item.stories.tsx +61 -0
  89. package/dist/starter/src/components/ui/Item.tsx +158 -0
  90. package/dist/starter/src/components/ui/Kbd.stories.tsx +54 -0
  91. package/dist/starter/src/components/ui/Kbd.tsx +18 -0
  92. package/dist/starter/src/components/ui/Label.stories.tsx +85 -0
  93. package/dist/starter/src/components/ui/Label.tsx +40 -0
  94. package/dist/starter/src/components/ui/Menubar.stories.tsx +76 -0
  95. package/dist/starter/src/components/ui/Menubar.tsx +236 -0
  96. package/dist/starter/src/components/ui/NativeSelect.stories.tsx +42 -0
  97. package/dist/starter/src/components/ui/NativeSelect.tsx +44 -0
  98. package/dist/starter/src/components/ui/NavigationMenu.stories.tsx +78 -0
  99. package/dist/starter/src/components/ui/NavigationMenu.tsx +142 -0
  100. package/dist/starter/src/components/ui/Pagination.stories.tsx +75 -0
  101. package/dist/starter/src/components/ui/Pagination.tsx +100 -0
  102. package/dist/starter/src/components/ui/Popover.stories.tsx +51 -0
  103. package/dist/starter/src/components/ui/Popover.tsx +52 -0
  104. package/dist/starter/src/components/ui/Progress.stories.tsx +28 -0
  105. package/dist/starter/src/components/ui/Progress.tsx +24 -0
  106. package/dist/starter/src/components/ui/RadioGroup.stories.tsx +48 -0
  107. package/dist/starter/src/components/ui/RadioGroup.tsx +31 -0
  108. package/dist/starter/src/components/ui/Resizable.stories.tsx +69 -0
  109. package/dist/starter/src/components/ui/Resizable.tsx +47 -0
  110. package/dist/starter/src/components/ui/ScrollArea.stories.tsx +43 -0
  111. package/dist/starter/src/components/ui/ScrollArea.tsx +46 -0
  112. package/dist/starter/src/components/ui/Select.stories.tsx +57 -0
  113. package/dist/starter/src/components/ui/Select.tsx +162 -0
  114. package/dist/starter/src/components/ui/Separator.stories.tsx +40 -0
  115. package/dist/starter/src/components/ui/Separator.tsx +26 -0
  116. package/dist/starter/src/components/ui/Sheet.stories.tsx +66 -0
  117. package/dist/starter/src/components/ui/Sheet.tsx +107 -0
  118. package/dist/starter/src/components/ui/Sidebar.stories.tsx +94 -0
  119. package/dist/starter/src/components/ui/Sidebar.tsx +675 -0
  120. package/dist/starter/src/components/ui/Skeleton.stories.tsx +38 -0
  121. package/dist/starter/src/components/ui/Skeleton.tsx +7 -0
  122. package/dist/starter/src/components/ui/Slider.stories.tsx +21 -0
  123. package/dist/starter/src/components/ui/Slider.tsx +54 -0
  124. package/dist/starter/src/components/ui/Sonner.stories.tsx +44 -0
  125. package/dist/starter/src/components/ui/Sonner.tsx +34 -0
  126. package/dist/starter/src/components/ui/Spinner.stories.tsx +23 -0
  127. package/dist/starter/src/components/ui/Spinner.tsx +9 -0
  128. package/dist/starter/src/components/ui/Switch.stories.tsx +35 -0
  129. package/dist/starter/src/components/ui/Switch.tsx +33 -0
  130. package/dist/starter/src/components/ui/Table.stories.tsx +65 -0
  131. package/dist/starter/src/components/ui/Table.tsx +75 -0
  132. package/dist/starter/src/components/ui/Tabs.stories.tsx +51 -0
  133. package/dist/starter/src/components/ui/Tabs.tsx +69 -0
  134. package/dist/starter/src/components/ui/Textarea.stories.tsx +24 -0
  135. package/dist/starter/src/components/ui/Textarea.tsx +18 -0
  136. package/dist/starter/src/components/ui/Toast.stories.tsx +112 -0
  137. package/dist/starter/src/components/ui/Toast.tsx +114 -0
  138. package/dist/starter/src/components/ui/Toaster.tsx +28 -0
  139. package/dist/starter/src/components/ui/Toggle.stories.tsx +40 -0
  140. package/dist/starter/src/components/ui/Toggle.tsx +41 -0
  141. package/dist/starter/src/components/ui/ToggleGroup.stories.tsx +58 -0
  142. package/dist/starter/src/components/ui/ToggleGroup.tsx +80 -0
  143. package/dist/starter/src/components/ui/Tooltip.stories.tsx +40 -0
  144. package/dist/starter/src/components/ui/Tooltip.tsx +42 -0
  145. package/dist/starter/src/hooks/use-mobile.ts +19 -0
  146. package/dist/starter/src/hooks/use-toast.ts +186 -0
  147. package/dist/starter/src/index.css +123 -0
  148. package/dist/starter/src/lib/utils.ts +6 -0
  149. package/dist/starter/src/main.tsx +5 -0
  150. package/dist/starter/tsconfig.app.json +25 -0
  151. package/dist/starter/tsconfig.json +4 -0
  152. package/dist/starter/vite.config.ts +16 -0
  153. package/dist/tsconfig.tsbuildinfo +1 -0
  154. package/package.json +37 -0
@@ -0,0 +1,219 @@
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
+ function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
10
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
11
+ }
12
+
13
+ function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
14
+ return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
15
+ }
16
+
17
+ function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
18
+ return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
19
+ }
20
+
21
+ function DropdownMenuContent({
22
+ className,
23
+ sideOffset = 4,
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Portal>
28
+ <DropdownMenuPrimitive.Content
29
+ data-slot="dropdown-menu-content"
30
+ sideOffset={sideOffset}
31
+ className={cn(
32
+ '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',
33
+ className,
34
+ )}
35
+ {...props}
36
+ />
37
+ </DropdownMenuPrimitive.Portal>
38
+ );
39
+ }
40
+
41
+ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
42
+ return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
43
+ }
44
+
45
+ function DropdownMenuItem({
46
+ className,
47
+ inset,
48
+ variant = 'default',
49
+ ...props
50
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
51
+ inset?: boolean;
52
+ variant?: 'default' | 'destructive';
53
+ }) {
54
+ return (
55
+ <DropdownMenuPrimitive.Item
56
+ data-slot="dropdown-menu-item"
57
+ data-inset={inset}
58
+ data-variant={variant}
59
+ className={cn(
60
+ "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",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function DropdownMenuCheckboxItem({
69
+ className,
70
+ children,
71
+ checked,
72
+ ...props
73
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
74
+ return (
75
+ <DropdownMenuPrimitive.CheckboxItem
76
+ data-slot="dropdown-menu-checkbox-item"
77
+ className={cn(
78
+ "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",
79
+ className,
80
+ )}
81
+ checked={checked}
82
+ {...props}
83
+ >
84
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
85
+ <DropdownMenuPrimitive.ItemIndicator>
86
+ <CheckIcon className="size-4" />
87
+ </DropdownMenuPrimitive.ItemIndicator>
88
+ </span>
89
+ {children}
90
+ </DropdownMenuPrimitive.CheckboxItem>
91
+ );
92
+ }
93
+
94
+ function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
95
+ return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
96
+ }
97
+
98
+ function DropdownMenuRadioItem({
99
+ className,
100
+ children,
101
+ ...props
102
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
103
+ return (
104
+ <DropdownMenuPrimitive.RadioItem
105
+ data-slot="dropdown-menu-radio-item"
106
+ className={cn(
107
+ "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",
108
+ className,
109
+ )}
110
+ {...props}
111
+ >
112
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
113
+ <DropdownMenuPrimitive.ItemIndicator>
114
+ <CircleIcon className="size-2 fill-current" />
115
+ </DropdownMenuPrimitive.ItemIndicator>
116
+ </span>
117
+ {children}
118
+ </DropdownMenuPrimitive.RadioItem>
119
+ );
120
+ }
121
+
122
+ function DropdownMenuLabel({
123
+ className,
124
+ inset,
125
+ ...props
126
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
127
+ inset?: boolean;
128
+ }) {
129
+ return (
130
+ <DropdownMenuPrimitive.Label
131
+ data-slot="dropdown-menu-label"
132
+ data-inset={inset}
133
+ className={cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', className)}
134
+ {...props}
135
+ />
136
+ );
137
+ }
138
+
139
+ function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
140
+ return (
141
+ <DropdownMenuPrimitive.Separator
142
+ data-slot="dropdown-menu-separator"
143
+ className={cn('bg-border -mx-1 my-1 h-px', className)}
144
+ {...props}
145
+ />
146
+ );
147
+ }
148
+
149
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
150
+ return (
151
+ <span
152
+ data-slot="dropdown-menu-shortcut"
153
+ className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
154
+ {...props}
155
+ />
156
+ );
157
+ }
158
+
159
+ function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
160
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
161
+ }
162
+
163
+ function DropdownMenuSubTrigger({
164
+ className,
165
+ inset,
166
+ children,
167
+ ...props
168
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
169
+ inset?: boolean;
170
+ }) {
171
+ return (
172
+ <DropdownMenuPrimitive.SubTrigger
173
+ data-slot="dropdown-menu-sub-trigger"
174
+ data-inset={inset}
175
+ className={cn(
176
+ "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",
177
+ className,
178
+ )}
179
+ {...props}
180
+ >
181
+ {children}
182
+ <ChevronRightIcon className="ml-auto size-4" />
183
+ </DropdownMenuPrimitive.SubTrigger>
184
+ );
185
+ }
186
+
187
+ function DropdownMenuSubContent({
188
+ className,
189
+ ...props
190
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
191
+ return (
192
+ <DropdownMenuPrimitive.SubContent
193
+ data-slot="dropdown-menu-sub-content"
194
+ className={cn(
195
+ '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',
196
+ className,
197
+ )}
198
+ {...props}
199
+ />
200
+ );
201
+ }
202
+
203
+ export {
204
+ DropdownMenu,
205
+ DropdownMenuPortal,
206
+ DropdownMenuTrigger,
207
+ DropdownMenuContent,
208
+ DropdownMenuGroup,
209
+ DropdownMenuLabel,
210
+ DropdownMenuItem,
211
+ DropdownMenuCheckboxItem,
212
+ DropdownMenuRadioGroup,
213
+ DropdownMenuRadioItem,
214
+ DropdownMenuSeparator,
215
+ DropdownMenuShortcut,
216
+ DropdownMenuSub,
217
+ DropdownMenuSubTrigger,
218
+ DropdownMenuSubContent,
219
+ };
@@ -0,0 +1,35 @@
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: 'Empty',
7
+ component: Empty,
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof Empty>;
11
+
12
+ export const Default: Story = {
13
+ render: () => (
14
+ <Empty>
15
+ <EmptyHeader>
16
+ <EmptyTitle>No results found</EmptyTitle>
17
+ <EmptyDescription>Try adjusting your search or filter to find what you are looking for.</EmptyDescription>
18
+ </EmptyHeader>
19
+ </Empty>
20
+ ),
21
+ };
22
+
23
+ export const WithIcon: Story = {
24
+ render: () => (
25
+ <Empty>
26
+ <EmptyHeader>
27
+ <EmptyMedia variant="icon">
28
+ <InboxIcon />
29
+ </EmptyMedia>
30
+ <EmptyTitle>Your inbox is empty</EmptyTitle>
31
+ <EmptyDescription>New messages will appear here when you receive them.</EmptyDescription>
32
+ </EmptyHeader>
33
+ </Empty>
34
+ ),
35
+ };
@@ -0,0 +1,85 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority';
2
+
3
+ import { cn } from '@/lib/utils';
4
+
5
+ function Empty({ className, ...props }: React.ComponentProps<'div'>) {
6
+ return (
7
+ <div
8
+ data-slot="empty"
9
+ className={cn(
10
+ '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',
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {
19
+ return (
20
+ <div
21
+ data-slot="empty-header"
22
+ className={cn('flex max-w-sm flex-col items-center gap-2 text-center', className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ const emptyMediaVariants = cva(
29
+ 'flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0',
30
+ {
31
+ variants: {
32
+ variant: {
33
+ default: 'bg-transparent',
34
+ icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: 'default',
39
+ },
40
+ },
41
+ );
42
+
43
+ function EmptyMedia({
44
+ className,
45
+ variant = 'default',
46
+ ...props
47
+ }: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {
48
+ return (
49
+ <div
50
+ data-slot="empty-icon"
51
+ data-variant={variant}
52
+ className={cn(emptyMediaVariants({ variant, className }))}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
59
+ return <div data-slot="empty-title" className={cn('text-lg font-medium tracking-tight', className)} {...props} />;
60
+ }
61
+
62
+ function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
63
+ return (
64
+ <div
65
+ data-slot="empty-description"
66
+ className={cn(
67
+ 'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
68
+ className,
69
+ )}
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+
75
+ function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
76
+ return (
77
+ <div
78
+ data-slot="empty-content"
79
+ className={cn('flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance', className)}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia };
@@ -0,0 +1,47 @@
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: 'Field',
7
+ component: Field,
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof Field>;
11
+
12
+ export const Default: Story = {
13
+ render: () => (
14
+ <Field>
15
+ <FieldLabel htmlFor="email">Email</FieldLabel>
16
+ <FieldContent>
17
+ <Input id="email" type="email" placeholder="you@example.com" />
18
+ <FieldDescription>We will never share your email.</FieldDescription>
19
+ </FieldContent>
20
+ </Field>
21
+ ),
22
+ };
23
+
24
+ export const WithError: Story = {
25
+ render: () => (
26
+ <Field data-invalid="true">
27
+ <FieldLabel htmlFor="email-err">Email</FieldLabel>
28
+ <FieldContent>
29
+ <Input id="email-err" type="email" defaultValue="invalid-email" aria-invalid="true" />
30
+ <FieldDescription>Enter a valid email address.</FieldDescription>
31
+ <FieldError>Please enter a valid email address.</FieldError>
32
+ </FieldContent>
33
+ </Field>
34
+ ),
35
+ };
36
+
37
+ export const Horizontal: Story = {
38
+ render: () => (
39
+ <Field orientation="horizontal">
40
+ <FieldLabel htmlFor="name-h">Full Name</FieldLabel>
41
+ <FieldContent>
42
+ <Input id="name-h" placeholder="John Doe" />
43
+ <FieldDescription>Your first and last name.</FieldDescription>
44
+ </FieldContent>
45
+ </Field>
46
+ ),
47
+ };
@@ -0,0 +1,226 @@
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
+ function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
11
+ return (
12
+ <fieldset
13
+ data-slot="field-set"
14
+ className={cn(
15
+ 'flex flex-col gap-6',
16
+ 'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function FieldLegend({
25
+ className,
26
+ variant = 'legend',
27
+ ...props
28
+ }: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
29
+ return (
30
+ <legend
31
+ data-slot="field-legend"
32
+ data-variant={variant}
33
+ className={cn('mb-3 font-medium', 'data-[variant=legend]:text-base', 'data-[variant=label]:text-sm', className)}
34
+ {...props}
35
+ />
36
+ );
37
+ }
38
+
39
+ function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
40
+ return (
41
+ <div
42
+ data-slot="field-group"
43
+ className={cn(
44
+ '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',
45
+ className,
46
+ )}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ const fieldVariants = cva('group/field flex w-full gap-3 data-[invalid=true]:text-destructive', {
53
+ variants: {
54
+ orientation: {
55
+ vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
56
+ horizontal: [
57
+ 'flex-row items-center',
58
+ '[&>[data-slot=field-label]]:flex-auto',
59
+ 'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
60
+ ],
61
+ responsive: [
62
+ 'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto',
63
+ '@md/field-group:[&>[data-slot=field-label]]:flex-auto',
64
+ '@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
65
+ ],
66
+ },
67
+ },
68
+ defaultVariants: {
69
+ orientation: 'vertical',
70
+ },
71
+ });
72
+
73
+ function Field({
74
+ className,
75
+ orientation = 'vertical',
76
+ ...props
77
+ }: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
78
+ return (
79
+ <div
80
+ role="group"
81
+ data-slot="field"
82
+ data-orientation={orientation}
83
+ className={cn(fieldVariants({ orientation }), className)}
84
+ {...props}
85
+ />
86
+ );
87
+ }
88
+
89
+ function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
90
+ return (
91
+ <div
92
+ data-slot="field-content"
93
+ className={cn('group/field-content flex flex-1 flex-col gap-1.5 leading-snug', className)}
94
+ {...props}
95
+ />
96
+ );
97
+ }
98
+
99
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
100
+ return (
101
+ <Label
102
+ data-slot="field-label"
103
+ className={cn(
104
+ 'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
105
+ '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',
106
+ 'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
107
+ className,
108
+ )}
109
+ {...props}
110
+ />
111
+ );
112
+ }
113
+
114
+ function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
115
+ return (
116
+ <div
117
+ data-slot="field-label"
118
+ className={cn(
119
+ 'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
120
+ className,
121
+ )}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
128
+ return (
129
+ <p
130
+ data-slot="field-description"
131
+ className={cn(
132
+ 'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
133
+ 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',
134
+ '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
135
+ className,
136
+ )}
137
+ {...props}
138
+ />
139
+ );
140
+ }
141
+
142
+ function FieldSeparator({
143
+ children,
144
+ className,
145
+ ...props
146
+ }: React.ComponentProps<'div'> & {
147
+ children?: React.ReactNode;
148
+ }) {
149
+ return (
150
+ <div
151
+ data-slot="field-separator"
152
+ data-content={!!children}
153
+ className={cn('relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2', className)}
154
+ {...props}
155
+ >
156
+ <Separator className="absolute inset-0 top-1/2" />
157
+ {children && (
158
+ <span
159
+ className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
160
+ data-slot="field-separator-content"
161
+ >
162
+ {children}
163
+ </span>
164
+ )}
165
+ </div>
166
+ );
167
+ }
168
+
169
+ function FieldError({
170
+ className,
171
+ children,
172
+ errors,
173
+ ...props
174
+ }: React.ComponentProps<'div'> & {
175
+ errors?: Array<{ message?: string } | undefined>;
176
+ }) {
177
+ const content = useMemo(() => {
178
+ if (children) {
179
+ return children;
180
+ }
181
+
182
+ if (!errors?.length) {
183
+ return null;
184
+ }
185
+
186
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
187
+
188
+ if (uniqueErrors?.length == 1) {
189
+ return uniqueErrors[0]?.message;
190
+ }
191
+
192
+ return (
193
+ <ul className="ml-4 flex list-disc flex-col gap-1">
194
+ {uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
195
+ </ul>
196
+ );
197
+ }, [children, errors]);
198
+
199
+ if (!content) {
200
+ return null;
201
+ }
202
+
203
+ return (
204
+ <div
205
+ role="alert"
206
+ data-slot="field-error"
207
+ className={cn('text-destructive text-sm font-normal', className)}
208
+ {...props}
209
+ >
210
+ {content}
211
+ </div>
212
+ );
213
+ }
214
+
215
+ export {
216
+ Field,
217
+ FieldLabel,
218
+ FieldDescription,
219
+ FieldError,
220
+ FieldGroup,
221
+ FieldLegend,
222
+ FieldSeparator,
223
+ FieldSet,
224
+ FieldContent,
225
+ FieldTitle,
226
+ };
@@ -0,0 +1,44 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useForm } from 'react-hook-form';
3
+ import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/Form';
4
+ import { Input } from '@/components/ui/Input';
5
+ import { Button } from '@/components/ui/Button';
6
+
7
+ const meta: Meta<typeof Form> = {
8
+ title: 'Form',
9
+ component: Form,
10
+ };
11
+ export default meta;
12
+ type Story = StoryObj<typeof Form>;
13
+
14
+ function FormDemo() {
15
+ const form = useForm({
16
+ defaultValues: { username: '' },
17
+ });
18
+
19
+ return (
20
+ <Form {...form}>
21
+ <form onSubmit={form.handleSubmit(() => {})} className="w-full max-w-sm space-y-6">
22
+ <FormField
23
+ control={form.control}
24
+ name="username"
25
+ render={({ field }) => (
26
+ <FormItem>
27
+ <FormLabel>Username</FormLabel>
28
+ <FormControl>
29
+ <Input placeholder="Enter username" {...field} />
30
+ </FormControl>
31
+ <FormDescription>This is your public display name.</FormDescription>
32
+ <FormMessage />
33
+ </FormItem>
34
+ )}
35
+ />
36
+ <Button type="submit">Submit</Button>
37
+ </form>
38
+ </Form>
39
+ );
40
+ }
41
+
42
+ export const Default: Story = {
43
+ render: () => <FormDemo />,
44
+ };