create-vexcms 0.0.3

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 (116) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2409 -0
  3. package/package.json +37 -0
  4. package/templates/base-nextjs/.prettierignore +26 -0
  5. package/templates/base-nextjs/.prettierrc +7 -0
  6. package/templates/base-nextjs/README.md +256 -0
  7. package/templates/base-nextjs/_gitignore +2 -0
  8. package/templates/base-nextjs/components.json +24 -0
  9. package/templates/base-nextjs/convex/_generated/api.d.ts +125 -0
  10. package/templates/base-nextjs/convex/_generated/api.js +23 -0
  11. package/templates/base-nextjs/convex/_generated/dataModel.d.ts +60 -0
  12. package/templates/base-nextjs/convex/_generated/server.d.ts +143 -0
  13. package/templates/base-nextjs/convex/_generated/server.js +93 -0
  14. package/templates/base-nextjs/convex/auth/adapter/index.ts +230 -0
  15. package/templates/base-nextjs/convex/auth/adapter/utils.ts +547 -0
  16. package/templates/base-nextjs/convex/auth/api.ts +8 -0
  17. package/templates/base-nextjs/convex/auth/config.ts +10 -0
  18. package/templates/base-nextjs/convex/auth/db.ts +303 -0
  19. package/templates/base-nextjs/convex/auth/index.ts +14 -0
  20. package/templates/base-nextjs/convex/auth/options.ts +63 -0
  21. package/templates/base-nextjs/convex/auth/plugins/index.ts +20 -0
  22. package/templates/base-nextjs/convex/auth/sessions.ts +60 -0
  23. package/templates/base-nextjs/convex/auth.config.ts +7 -0
  24. package/templates/base-nextjs/convex/convex.config.ts +5 -0
  25. package/templates/base-nextjs/convex/http.ts +28 -0
  26. package/templates/base-nextjs/convex/schema.ts +3 -0
  27. package/templates/base-nextjs/convex/vex/auth.ts +38 -0
  28. package/templates/base-nextjs/convex/vex/collections.ts +321 -0
  29. package/templates/base-nextjs/convex/vex/firstUser.ts +134 -0
  30. package/templates/base-nextjs/convex/vex/helpers.ts +33 -0
  31. package/templates/base-nextjs/convex/vex/impersonation.ts +51 -0
  32. package/templates/base-nextjs/convex/vex/media.ts +246 -0
  33. package/templates/base-nextjs/convex/vex/migrate.ts +74 -0
  34. package/templates/base-nextjs/convex/vex/model/collections.ts +196 -0
  35. package/templates/base-nextjs/convex/vex/model/media.ts +87 -0
  36. package/templates/base-nextjs/convex/vex/model/versions.ts +254 -0
  37. package/templates/base-nextjs/convex/vex/previewSnapshot.ts +53 -0
  38. package/templates/base-nextjs/convex/vex/versions.ts +588 -0
  39. package/templates/base-nextjs/eslint.config.mjs +164 -0
  40. package/templates/base-nextjs/next.config.ts +10 -0
  41. package/templates/base-nextjs/package.json +67 -0
  42. package/templates/base-nextjs/postcss.config.mjs +7 -0
  43. package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
  44. package/templates/base-nextjs/public/file.svg +1 -0
  45. package/templates/base-nextjs/public/globe.svg +1 -0
  46. package/templates/base-nextjs/public/next.svg +1 -0
  47. package/templates/base-nextjs/public/vercel.svg +1 -0
  48. package/templates/base-nextjs/public/window.svg +1 -0
  49. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/page.tsx +9 -0
  50. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +23 -0
  51. package/templates/base-nextjs/src/app/(frontend)/@auth/default.tsx +3 -0
  52. package/templates/base-nextjs/src/app/(frontend)/@auth/page.tsx +3 -0
  53. package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/page.tsx +9 -0
  54. package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +11 -0
  55. package/templates/base-nextjs/src/app/(frontend)/layout.tsx +14 -0
  56. package/templates/base-nextjs/src/app/(frontend)/page.tsx +116 -0
  57. package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +33 -0
  58. package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +38 -0
  59. package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +101 -0
  60. package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +15 -0
  61. package/templates/base-nextjs/src/app/admin/layout.tsx +33 -0
  62. package/templates/base-nextjs/src/app/api/auth/[...all]/route.ts +6 -0
  63. package/templates/base-nextjs/src/app/favicon.ico +0 -0
  64. package/templates/base-nextjs/src/app/globals.css +130 -0
  65. package/templates/base-nextjs/src/app/layout.tsx +30 -0
  66. package/templates/base-nextjs/src/auth/client.tsx +41 -0
  67. package/templates/base-nextjs/src/auth/permissions.ts +102 -0
  68. package/templates/base-nextjs/src/auth/server.ts +16 -0
  69. package/templates/base-nextjs/src/auth/serverUtils.ts +64 -0
  70. package/templates/base-nextjs/src/auth/types.ts +3 -0
  71. package/templates/base-nextjs/src/components/component-example.tsx +474 -0
  72. package/templates/base-nextjs/src/components/example.tsx +52 -0
  73. package/templates/base-nextjs/src/components/providers/client.tsx +9 -0
  74. package/templates/base-nextjs/src/components/providers/convex.tsx +32 -0
  75. package/templates/base-nextjs/src/components/providers/server.tsx +15 -0
  76. package/templates/base-nextjs/src/components/providers/theme.tsx +11 -0
  77. package/templates/base-nextjs/src/components/ui/alert-dialog.tsx +162 -0
  78. package/templates/base-nextjs/src/components/ui/badge.tsx +52 -0
  79. package/templates/base-nextjs/src/components/ui/button.tsx +60 -0
  80. package/templates/base-nextjs/src/components/ui/card.tsx +92 -0
  81. package/templates/base-nextjs/src/components/ui/combobox.tsx +271 -0
  82. package/templates/base-nextjs/src/components/ui/dialog.tsx +135 -0
  83. package/templates/base-nextjs/src/components/ui/dropdown-menu.tsx +246 -0
  84. package/templates/base-nextjs/src/components/ui/field.tsx +224 -0
  85. package/templates/base-nextjs/src/components/ui/input-group.tsx +146 -0
  86. package/templates/base-nextjs/src/components/ui/input.tsx +20 -0
  87. package/templates/base-nextjs/src/components/ui/label.tsx +20 -0
  88. package/templates/base-nextjs/src/components/ui/select.tsx +189 -0
  89. package/templates/base-nextjs/src/components/ui/separator.tsx +21 -0
  90. package/templates/base-nextjs/src/components/ui/textarea.tsx +18 -0
  91. package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +67 -0
  92. package/templates/base-nextjs/src/db/constants/auth.ts +8 -0
  93. package/templates/base-nextjs/src/db/constants/index.ts +44 -0
  94. package/templates/base-nextjs/src/db/types.ts +6 -0
  95. package/templates/base-nextjs/src/env.mjs +48 -0
  96. package/templates/base-nextjs/src/lib/utils.ts +6 -0
  97. package/templates/base-nextjs/src/proxy.ts +23 -0
  98. package/templates/base-nextjs/src/vexcms/access.ts +28 -0
  99. package/templates/base-nextjs/src/vexcms/auth.ts +4 -0
  100. package/templates/base-nextjs/src/vexcms/collections/index.ts +1 -0
  101. package/templates/base-nextjs/src/vexcms/collections/media.ts +15 -0
  102. package/templates/base-nextjs/src/vexcms/collections/users.ts +46 -0
  103. package/templates/base-nextjs/tsconfig.json +60 -0
  104. package/templates/base-nextjs/vex.config.ts +23 -0
  105. package/templates/marketing-site/convex/pages.ts +46 -0
  106. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +58 -0
  107. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +69 -0
  108. package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +71 -0
  109. package/templates/marketing-site/src/db/constants/index.ts +52 -0
  110. package/templates/marketing-site/src/vexcms/collections/footers.ts +28 -0
  111. package/templates/marketing-site/src/vexcms/collections/headers.ts +35 -0
  112. package/templates/marketing-site/src/vexcms/collections/index.ts +7 -0
  113. package/templates/marketing-site/src/vexcms/collections/pages.ts +40 -0
  114. package/templates/marketing-site/src/vexcms/collections/site_settings.ts +31 -0
  115. package/templates/marketing-site/src/vexcms/collections/themes.ts +37 -0
  116. package/templates/marketing-site/vex.config.ts +32 -0
@@ -0,0 +1,135 @@
1
+ "use client"
2
+
3
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
4
+ import { XIcon } from "lucide-react"
5
+ import * as React from "react"
6
+
7
+ import { Button } from "~/components/ui/button"
8
+ import { cn } from "~/lib/utils"
9
+
10
+ function Dialog({ ...props }: DialogPrimitive.Root.Props) {
11
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
12
+ }
13
+
14
+ function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
15
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
16
+ }
17
+
18
+ function DialogContent({
19
+ children,
20
+ className,
21
+ showCloseButton = true,
22
+ ...props
23
+ }: DialogPrimitive.Popup.Props & {
24
+ showCloseButton?: boolean
25
+ }) {
26
+ return (
27
+ <DialogPortal>
28
+ <DialogOverlay />
29
+ <DialogPrimitive.Popup
30
+ className={cn(
31
+ "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-6 rounded-xl p-6 text-sm ring-1 duration-100 sm:max-w-md fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
32
+ className
33
+ )}
34
+ data-slot="dialog-content"
35
+ {...props}
36
+ >
37
+ {children}
38
+ {showCloseButton && (
39
+ <DialogPrimitive.Close
40
+ data-slot="dialog-close"
41
+ render={<Button className="absolute top-4 right-4" size="icon-sm" variant="ghost" />}
42
+ >
43
+ <XIcon />
44
+ <span className="sr-only">Close</span>
45
+ </DialogPrimitive.Close>
46
+ )}
47
+ </DialogPrimitive.Popup>
48
+ </DialogPortal>
49
+ )
50
+ }
51
+
52
+ function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) {
53
+ return (
54
+ <DialogPrimitive.Description
55
+ className={cn(
56
+ "text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3",
57
+ className
58
+ )}
59
+ data-slot="dialog-description"
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ function DialogFooter({
66
+ children,
67
+ className,
68
+ showCloseButton = false,
69
+ ...props
70
+ }: React.ComponentProps<"div"> & {
71
+ showCloseButton?: boolean
72
+ }) {
73
+ return (
74
+ <div
75
+ className={cn("gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
76
+ data-slot="dialog-footer"
77
+ {...props}
78
+ >
79
+ {children}
80
+ {showCloseButton && (
81
+ <DialogPrimitive.Close render={<Button variant="outline" />}>Close</DialogPrimitive.Close>
82
+ )}
83
+ </div>
84
+ )
85
+ }
86
+
87
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
88
+ return (
89
+ <div className={cn("gap-2 flex flex-col", className)} data-slot="dialog-header" {...props} />
90
+ )
91
+ }
92
+
93
+ function DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {
94
+ return (
95
+ <DialogPrimitive.Backdrop
96
+ className={cn(
97
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
98
+ className
99
+ )}
100
+ data-slot="dialog-overlay"
101
+ {...props}
102
+ />
103
+ )
104
+ }
105
+
106
+ function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
107
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
108
+ }
109
+
110
+ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
111
+ return (
112
+ <DialogPrimitive.Title
113
+ className={cn("leading-none font-medium", className)}
114
+ data-slot="dialog-title"
115
+ {...props}
116
+ />
117
+ )
118
+ }
119
+
120
+ function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
121
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
122
+ }
123
+
124
+ export {
125
+ Dialog,
126
+ DialogClose,
127
+ DialogContent,
128
+ DialogDescription,
129
+ DialogFooter,
130
+ DialogHeader,
131
+ DialogOverlay,
132
+ DialogPortal,
133
+ DialogTitle,
134
+ DialogTrigger,
135
+ }
@@ -0,0 +1,246 @@
1
+ "use client"
2
+
3
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu"
4
+ import { CheckIcon, ChevronRightIcon } from "lucide-react"
5
+ import * as React from "react"
6
+
7
+ import { cn } from "~/lib/utils"
8
+
9
+ function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
10
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
11
+ }
12
+
13
+ function DropdownMenuCheckboxItem({
14
+ checked,
15
+ children,
16
+ className,
17
+ ...props
18
+ }: MenuPrimitive.CheckboxItem.Props) {
19
+ return (
20
+ <MenuPrimitive.CheckboxItem
21
+ checked={checked}
22
+ className={cn(
23
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
24
+ className
25
+ )}
26
+ data-slot="dropdown-menu-checkbox-item"
27
+ {...props}
28
+ >
29
+ <span
30
+ className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
31
+ data-slot="dropdown-menu-checkbox-item-indicator"
32
+ >
33
+ <MenuPrimitive.CheckboxItemIndicator>
34
+ <CheckIcon />
35
+ </MenuPrimitive.CheckboxItemIndicator>
36
+ </span>
37
+ {children}
38
+ </MenuPrimitive.CheckboxItem>
39
+ )
40
+ }
41
+
42
+ function DropdownMenuContent({
43
+ align = "start",
44
+ alignOffset = 0,
45
+ className,
46
+ side = "bottom",
47
+ sideOffset = 4,
48
+ ...props
49
+ }: MenuPrimitive.Popup.Props &
50
+ Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
51
+ return (
52
+ <MenuPrimitive.Portal>
53
+ <MenuPrimitive.Positioner
54
+ align={align}
55
+ alignOffset={alignOffset}
56
+ className="isolate z-50 outline-none"
57
+ side={side}
58
+ sideOffset={sideOffset}
59
+ >
60
+ <MenuPrimitive.Popup
61
+ className={cn(
62
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-md p-1 shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
63
+ className
64
+ )}
65
+ data-slot="dropdown-menu-content"
66
+ {...props}
67
+ />
68
+ </MenuPrimitive.Positioner>
69
+ </MenuPrimitive.Portal>
70
+ )
71
+ }
72
+
73
+ function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
74
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
75
+ }
76
+
77
+ function DropdownMenuItem({
78
+ className,
79
+ inset,
80
+ variant = "default",
81
+ ...props
82
+ }: MenuPrimitive.Item.Props & {
83
+ inset?: boolean
84
+ variant?: "default" | "destructive"
85
+ }) {
86
+ return (
87
+ <MenuPrimitive.Item
88
+ className={cn(
89
+ "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 not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
90
+ className
91
+ )}
92
+ data-inset={inset}
93
+ data-slot="dropdown-menu-item"
94
+ data-variant={variant}
95
+ {...props}
96
+ />
97
+ )
98
+ }
99
+
100
+ function DropdownMenuLabel({
101
+ className,
102
+ inset,
103
+ ...props
104
+ }: MenuPrimitive.GroupLabel.Props & {
105
+ inset?: boolean
106
+ }) {
107
+ return (
108
+ <MenuPrimitive.GroupLabel
109
+ className={cn(
110
+ "text-muted-foreground px-2 py-1.5 text-xs font-medium data-[inset]:pl-8",
111
+ className
112
+ )}
113
+ data-inset={inset}
114
+ data-slot="dropdown-menu-label"
115
+ {...props}
116
+ />
117
+ )
118
+ }
119
+
120
+ function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
121
+ return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
122
+ }
123
+
124
+ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
125
+ return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
126
+ }
127
+
128
+ function DropdownMenuRadioItem({ children, className, ...props }: MenuPrimitive.RadioItem.Props) {
129
+ return (
130
+ <MenuPrimitive.RadioItem
131
+ className={cn(
132
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
133
+ className
134
+ )}
135
+ data-slot="dropdown-menu-radio-item"
136
+ {...props}
137
+ >
138
+ <span
139
+ className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
140
+ data-slot="dropdown-menu-radio-item-indicator"
141
+ >
142
+ <MenuPrimitive.RadioItemIndicator>
143
+ <CheckIcon />
144
+ </MenuPrimitive.RadioItemIndicator>
145
+ </span>
146
+ {children}
147
+ </MenuPrimitive.RadioItem>
148
+ )
149
+ }
150
+
151
+ function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
152
+ return (
153
+ <MenuPrimitive.Separator
154
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
155
+ data-slot="dropdown-menu-separator"
156
+ {...props}
157
+ />
158
+ )
159
+ }
160
+
161
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
162
+ return (
163
+ <span
164
+ className={cn(
165
+ "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
166
+ className
167
+ )}
168
+ data-slot="dropdown-menu-shortcut"
169
+ {...props}
170
+ />
171
+ )
172
+ }
173
+
174
+ function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
175
+ return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
176
+ }
177
+
178
+ function DropdownMenuSubContent({
179
+ align = "start",
180
+ alignOffset = -3,
181
+ className,
182
+ side = "right",
183
+ sideOffset = 0,
184
+ ...props
185
+ }: React.ComponentProps<typeof DropdownMenuContent>) {
186
+ return (
187
+ <DropdownMenuContent
188
+ align={align}
189
+ alignOffset={alignOffset}
190
+ className={cn(
191
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-md p-1 shadow-lg ring-1 duration-100 w-auto",
192
+ className
193
+ )}
194
+ data-slot="dropdown-menu-sub-content"
195
+ side={side}
196
+ sideOffset={sideOffset}
197
+ {...props}
198
+ />
199
+ )
200
+ }
201
+
202
+ function DropdownMenuSubTrigger({
203
+ children,
204
+ className,
205
+ inset,
206
+ ...props
207
+ }: MenuPrimitive.SubmenuTrigger.Props & {
208
+ inset?: boolean
209
+ }) {
210
+ return (
211
+ <MenuPrimitive.SubmenuTrigger
212
+ className={cn(
213
+ "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
214
+ className
215
+ )}
216
+ data-inset={inset}
217
+ data-slot="dropdown-menu-sub-trigger"
218
+ {...props}
219
+ >
220
+ {children}
221
+ <ChevronRightIcon className="ml-auto" />
222
+ </MenuPrimitive.SubmenuTrigger>
223
+ )
224
+ }
225
+
226
+ function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
227
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
228
+ }
229
+
230
+ export {
231
+ DropdownMenu,
232
+ DropdownMenuCheckboxItem,
233
+ DropdownMenuContent,
234
+ DropdownMenuGroup,
235
+ DropdownMenuItem,
236
+ DropdownMenuLabel,
237
+ DropdownMenuPortal,
238
+ DropdownMenuRadioGroup,
239
+ DropdownMenuRadioItem,
240
+ DropdownMenuSeparator,
241
+ DropdownMenuShortcut,
242
+ DropdownMenuSub,
243
+ DropdownMenuSubContent,
244
+ DropdownMenuSubTrigger,
245
+ DropdownMenuTrigger,
246
+ }
@@ -0,0 +1,224 @@
1
+ "use client"
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { useMemo } from "react"
5
+
6
+ import { Label } from "~/components/ui/label"
7
+ import { Separator } from "~/components/ui/separator"
8
+ import { cn } from "~/lib/utils"
9
+
10
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
11
+ return (
12
+ <div
13
+ className={cn(
14
+ "gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col",
15
+ className
16
+ )}
17
+ data-slot="field-group"
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function FieldLegend({
24
+ className,
25
+ variant = "legend",
26
+ ...props
27
+ }: React.ComponentProps<"legend"> & { variant?: "label" | "legend" }) {
28
+ return (
29
+ <legend
30
+ className={cn(
31
+ "mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
32
+ className
33
+ )}
34
+ data-slot="field-legend"
35
+ data-variant={variant}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
42
+ return (
43
+ <fieldset
44
+ className={cn(
45
+ "gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col",
46
+ className
47
+ )}
48
+ data-slot="field-set"
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ const fieldVariants = cva("data-[invalid=true]:text-destructive gap-3 group/field flex w-full", {
55
+ defaultVariants: {
56
+ orientation: "vertical",
57
+ },
58
+ variants: {
59
+ orientation: {
60
+ horizontal:
61
+ "flex-row items-center [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
62
+ responsive:
63
+ "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
64
+ vertical: "flex-col [&>*]:w-full [&>.sr-only]:w-auto",
65
+ },
66
+ },
67
+ })
68
+
69
+ function Field({
70
+ className,
71
+ orientation = "vertical",
72
+ ...props
73
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
74
+ return (
75
+ <div
76
+ className={cn(fieldVariants({ orientation }), className)}
77
+ data-orientation={orientation}
78
+ data-slot="field"
79
+ role="group"
80
+ {...props}
81
+ />
82
+ )
83
+ }
84
+
85
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
86
+ return (
87
+ <div
88
+ className={cn("gap-1 group/field-content flex flex-1 flex-col leading-snug", className)}
89
+ data-slot="field-content"
90
+ {...props}
91
+ />
92
+ )
93
+ }
94
+
95
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
96
+ return (
97
+ <p
98
+ className={cn(
99
+ "text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
100
+ "last:mt-0 nth-last-2:-mt-1",
101
+ "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
102
+ className
103
+ )}
104
+ data-slot="field-description"
105
+ {...props}
106
+ />
107
+ )
108
+ }
109
+
110
+ function FieldError({
111
+ children,
112
+ className,
113
+ errors,
114
+ ...props
115
+ }: React.ComponentProps<"div"> & {
116
+ errors?: Array<undefined | { message?: string }>
117
+ }) {
118
+ const content = useMemo(() => {
119
+ if (children) {
120
+ return children
121
+ }
122
+
123
+ if (!errors?.length) {
124
+ return null
125
+ }
126
+
127
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()]
128
+
129
+ if (uniqueErrors?.length == 1) {
130
+ return uniqueErrors[0]?.message
131
+ }
132
+
133
+ return (
134
+ <ul className="ml-4 flex list-disc flex-col gap-1">
135
+ {uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
136
+ </ul>
137
+ )
138
+ }, [children, errors])
139
+
140
+ if (!content) {
141
+ return null
142
+ }
143
+
144
+ return (
145
+ <div
146
+ className={cn("text-destructive text-sm font-normal", className)}
147
+ data-slot="field-error"
148
+ role="alert"
149
+ {...props}
150
+ >
151
+ {content}
152
+ </div>
153
+ )
154
+ }
155
+
156
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
157
+ return (
158
+ <Label
159
+ className={cn(
160
+ "has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 gap-2 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit leading-snug",
161
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
162
+ className
163
+ )}
164
+ data-slot="field-label"
165
+ {...props}
166
+ />
167
+ )
168
+ }
169
+
170
+ function FieldSeparator({
171
+ children,
172
+ className,
173
+ ...props
174
+ }: React.ComponentProps<"div"> & {
175
+ children?: React.ReactNode
176
+ }) {
177
+ return (
178
+ <div
179
+ className={cn(
180
+ "-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative",
181
+ className
182
+ )}
183
+ data-content={!!children}
184
+ data-slot="field-separator"
185
+ {...props}
186
+ >
187
+ <Separator className="absolute inset-0 top-1/2" />
188
+ {children && (
189
+ <span
190
+ className="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
191
+ data-slot="field-separator-content"
192
+ >
193
+ {children}
194
+ </span>
195
+ )}
196
+ </div>
197
+ )
198
+ }
199
+
200
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
201
+ return (
202
+ <div
203
+ className={cn(
204
+ "gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug",
205
+ className
206
+ )}
207
+ data-slot="field-label"
208
+ {...props}
209
+ />
210
+ )
211
+ }
212
+
213
+ export {
214
+ Field,
215
+ FieldContent,
216
+ FieldDescription,
217
+ FieldError,
218
+ FieldGroup,
219
+ FieldLabel,
220
+ FieldLegend,
221
+ FieldSeparator,
222
+ FieldSet,
223
+ FieldTitle,
224
+ }