create-freakpunch 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +24 -3
  2. package/bin/create-freakpunch.mjs +227 -0
  3. package/files/README.md +19 -0
  4. package/files/app.config.ts +50 -0
  5. package/files/locales.ts +10 -0
  6. package/files/registry.ts +17 -0
  7. package/package.json +23 -6
  8. package/template/.dockerignore +17 -0
  9. package/template/.oxlintrc.json +9 -0
  10. package/template/AGENTS.md +6 -0
  11. package/template/BACKEND.md +108 -0
  12. package/template/CLAUDE.md +297 -0
  13. package/template/Dockerfile +19 -0
  14. package/template/_gitignore +26 -0
  15. package/template/components.json +27 -0
  16. package/template/design/COMPONENTS.md +145 -0
  17. package/template/design/DESIGN.md +212 -0
  18. package/template/design/PATTERNS.md +110 -0
  19. package/template/index.html +25 -0
  20. package/template/nginx.conf +32 -0
  21. package/template/package-lock.json +6484 -0
  22. package/template/package.json +51 -0
  23. package/template/public/favicon.svg +1 -0
  24. package/template/public/robots.txt +2 -0
  25. package/template/src/app.config.ts +50 -0
  26. package/template/src/app.roles.ts +24 -0
  27. package/template/src/components/ui/alert-dialog.tsx +181 -0
  28. package/template/src/components/ui/avatar.tsx +116 -0
  29. package/template/src/components/ui/badge.tsx +57 -0
  30. package/template/src/components/ui/breadcrumb.tsx +130 -0
  31. package/template/src/components/ui/button.tsx +58 -0
  32. package/template/src/components/ui/calendar.tsx +277 -0
  33. package/template/src/components/ui/card.tsx +99 -0
  34. package/template/src/components/ui/chart.tsx +370 -0
  35. package/template/src/components/ui/checkbox.tsx +28 -0
  36. package/template/src/components/ui/combobox.tsx +329 -0
  37. package/template/src/components/ui/command.tsx +191 -0
  38. package/template/src/components/ui/dialog.tsx +162 -0
  39. package/template/src/components/ui/dropdown-menu.tsx +271 -0
  40. package/template/src/components/ui/empty.tsx +103 -0
  41. package/template/src/components/ui/field.tsx +236 -0
  42. package/template/src/components/ui/input-group.tsx +263 -0
  43. package/template/src/components/ui/input.tsx +45 -0
  44. package/template/src/components/ui/kbd.tsx +31 -0
  45. package/template/src/components/ui/label.tsx +17 -0
  46. package/template/src/components/ui/pagination.tsx +134 -0
  47. package/template/src/components/ui/popover.tsx +87 -0
  48. package/template/src/components/ui/radio-group.tsx +35 -0
  49. package/template/src/components/ui/select.tsx +204 -0
  50. package/template/src/components/ui/separator.tsx +25 -0
  51. package/template/src/components/ui/sheet.tsx +139 -0
  52. package/template/src/components/ui/sidebar.tsx +741 -0
  53. package/template/src/components/ui/skeleton.tsx +13 -0
  54. package/template/src/components/ui/sonner.tsx +78 -0
  55. package/template/src/components/ui/spinner.tsx +42 -0
  56. package/template/src/components/ui/switch.tsx +29 -0
  57. package/template/src/components/ui/table.tsx +121 -0
  58. package/template/src/components/ui/tabs.tsx +191 -0
  59. package/template/src/components/ui/textarea.tsx +17 -0
  60. package/template/src/components/ui/toggle-group.tsx +94 -0
  61. package/template/src/components/ui/toggle.tsx +48 -0
  62. package/template/src/components/ui/tooltip.tsx +91 -0
  63. package/template/src/core/api/demo.ts +8 -0
  64. package/template/src/core/api/http.ts +76 -0
  65. package/template/src/core/api/mock.ts +88 -0
  66. package/template/src/core/api/rest.ts +67 -0
  67. package/template/src/core/api/types.ts +21 -0
  68. package/template/src/core/audit/audit.service.ts +94 -0
  69. package/template/src/core/audit/hooks.ts +19 -0
  70. package/template/src/core/auth/auth-store.ts +52 -0
  71. package/template/src/core/auth/auth.service.ts +86 -0
  72. package/template/src/core/auth/can.tsx +39 -0
  73. package/template/src/core/auth/dev-role-switcher.tsx +64 -0
  74. package/template/src/core/auth/local-credentials.ts +46 -0
  75. package/template/src/core/auth/permissions.ts +115 -0
  76. package/template/src/core/auth/require-auth.tsx +16 -0
  77. package/template/src/core/auth/require-permission.tsx +24 -0
  78. package/template/src/core/auth/roles.ts +9 -0
  79. package/template/src/core/auth/types.ts +20 -0
  80. package/template/src/core/auth/use-auth.ts +19 -0
  81. package/template/src/core/company/company.service.ts +94 -0
  82. package/template/src/core/company/hooks.ts +40 -0
  83. package/template/src/core/company/password-policy.ts +40 -0
  84. package/template/src/core/company/types.ts +46 -0
  85. package/template/src/core/company/use-app-name.ts +7 -0
  86. package/template/src/core/components/button-link.tsx +14 -0
  87. package/template/src/core/components/confirm-dialog.tsx +116 -0
  88. package/template/src/core/components/data-table/data-table.tsx +297 -0
  89. package/template/src/core/components/data-table/features.ts +24 -0
  90. package/template/src/core/components/data-table/table-header-cell.tsx +13 -0
  91. package/template/src/core/components/data-table/toolbar.tsx +100 -0
  92. package/template/src/core/components/data-table/use-list-url-state.ts +108 -0
  93. package/template/src/core/components/date-picker.tsx +71 -0
  94. package/template/src/core/components/file-attachments.tsx +84 -0
  95. package/template/src/core/components/form-error.tsx +9 -0
  96. package/template/src/core/components/form-page.tsx +93 -0
  97. package/template/src/core/components/image-upload.tsx +86 -0
  98. package/template/src/core/components/page.tsx +75 -0
  99. package/template/src/core/components/phone-input.tsx +58 -0
  100. package/template/src/core/components/search-select.tsx +57 -0
  101. package/template/src/core/components/segmented.tsx +41 -0
  102. package/template/src/core/components/states.tsx +97 -0
  103. package/template/src/core/components/status-badge.tsx +26 -0
  104. package/template/src/core/components/user-avatar.tsx +23 -0
  105. package/template/src/core/config/runtime.ts +16 -0
  106. package/template/src/core/config/types.ts +37 -0
  107. package/template/src/core/i18n/i18n.ts +69 -0
  108. package/template/src/core/i18n/i18next.d.ts +12 -0
  109. package/template/src/core/i18n/keys.ts +37 -0
  110. package/template/src/core/i18n/language-switcher.tsx +45 -0
  111. package/template/src/core/i18n/types.ts +17 -0
  112. package/template/src/core/i18n/use-format.ts +31 -0
  113. package/template/src/core/layout/app-shell.tsx +50 -0
  114. package/template/src/core/layout/app-sidebar.tsx +285 -0
  115. package/template/src/core/layout/command-menu.tsx +117 -0
  116. package/template/src/core/layout/top-bar.tsx +100 -0
  117. package/template/src/core/lib/csv.ts +27 -0
  118. package/template/src/core/lib/format.ts +87 -0
  119. package/template/src/core/lib/phone.ts +50 -0
  120. package/template/src/core/lib/storage.ts +27 -0
  121. package/template/src/core/modules/lazy-page.ts +9 -0
  122. package/template/src/core/modules/navigation.ts +39 -0
  123. package/template/src/core/modules/types.ts +55 -0
  124. package/template/src/core/notifications/hooks.ts +84 -0
  125. package/template/src/core/notifications/notification-center.tsx +251 -0
  126. package/template/src/core/notifications/notifications.service.ts +132 -0
  127. package/template/src/core/notifications/types.ts +70 -0
  128. package/template/src/core/preferences/preferences-store.ts +70 -0
  129. package/template/src/core/setup/setup-gate.tsx +20 -0
  130. package/template/src/core/setup/setup.service.ts +101 -0
  131. package/template/src/core/theme/brand-context.tsx +53 -0
  132. package/template/src/core/theme/brand.ts +29 -0
  133. package/template/src/core/theme/theme-context.tsx +62 -0
  134. package/template/src/core/theme/theme-options.ts +8 -0
  135. package/template/src/core/theme/theme-toggle.tsx +45 -0
  136. package/template/src/core/users/hooks.ts +51 -0
  137. package/template/src/core/users/types.ts +37 -0
  138. package/template/src/core/users/users.service.ts +167 -0
  139. package/template/src/hooks/use-mobile.ts +19 -0
  140. package/template/src/index.css +321 -0
  141. package/template/src/lib/utils.ts +1 -0
  142. package/template/src/locales/en/account.ts +180 -0
  143. package/template/src/locales/en/auth.ts +18 -0
  144. package/template/src/locales/en/common.ts +151 -0
  145. package/template/src/locales/en/company.ts +380 -0
  146. package/template/src/locales/en/dashboard.ts +46 -0
  147. package/template/src/locales/en/errors.ts +18 -0
  148. package/template/src/locales/en/notifications.ts +43 -0
  149. package/template/src/locales/en/settings.ts +25 -0
  150. package/template/src/locales/en/setup.ts +45 -0
  151. package/template/src/locales/index.ts +58 -0
  152. package/template/src/locales/tr/account.ts +184 -0
  153. package/template/src/locales/tr/auth.ts +22 -0
  154. package/template/src/locales/tr/common.ts +154 -0
  155. package/template/src/locales/tr/company.ts +384 -0
  156. package/template/src/locales/tr/dashboard.ts +50 -0
  157. package/template/src/locales/tr/errors.ts +22 -0
  158. package/template/src/locales/tr/notifications.ts +47 -0
  159. package/template/src/locales/tr/settings.ts +29 -0
  160. package/template/src/locales/tr/setup.ts +49 -0
  161. package/template/src/main.tsx +16 -0
  162. package/template/src/modules/locales.ts +10 -0
  163. package/template/src/modules/registry.ts +17 -0
  164. package/template/src/pages/dashboard/dashboard-page.tsx +55 -0
  165. package/template/src/pages/dashboard/quick-actions.tsx +37 -0
  166. package/template/src/pages/login-page.tsx +152 -0
  167. package/template/src/pages/no-access-page.tsx +11 -0
  168. package/template/src/pages/not-found-page.tsx +61 -0
  169. package/template/src/pages/settings/account/account.service.ts +128 -0
  170. package/template/src/pages/settings/account/components/mock-qr.tsx +50 -0
  171. package/template/src/pages/settings/account/components/password-strength.tsx +38 -0
  172. package/template/src/pages/settings/account/components/two-factor-dialog.tsx +140 -0
  173. package/template/src/pages/settings/account/hooks.ts +73 -0
  174. package/template/src/pages/settings/account/notifications-page.tsx +103 -0
  175. package/template/src/pages/settings/account/preferences-page.tsx +157 -0
  176. package/template/src/pages/settings/account/profile-page.tsx +242 -0
  177. package/template/src/pages/settings/account/security-page.tsx +364 -0
  178. package/template/src/pages/settings/account/types.ts +37 -0
  179. package/template/src/pages/settings/company/audit-log-page.tsx +126 -0
  180. package/template/src/pages/settings/company/branding-page.tsx +278 -0
  181. package/template/src/pages/settings/company/components/invite-dialog.tsx +106 -0
  182. package/template/src/pages/settings/company/components/read-only-notice.tsx +13 -0
  183. package/template/src/pages/settings/company/danger-zone-page.tsx +127 -0
  184. package/template/src/pages/settings/company/general-page.tsx +234 -0
  185. package/template/src/pages/settings/company/integrations/api-keys-panel.tsx +170 -0
  186. package/template/src/pages/settings/company/integrations/hooks.ts +37 -0
  187. package/template/src/pages/settings/company/integrations/integrations-page.tsx +57 -0
  188. package/template/src/pages/settings/company/integrations/integrations.service.ts +153 -0
  189. package/template/src/pages/settings/company/integrations/settings-forms.tsx +299 -0
  190. package/template/src/pages/settings/company/integrations/webhooks-panel.tsx +179 -0
  191. package/template/src/pages/settings/company/modules-page.tsx +65 -0
  192. package/template/src/pages/settings/company/roles-page.tsx +116 -0
  193. package/template/src/pages/settings/company/security-policies-page.tsx +164 -0
  194. package/template/src/pages/settings/company/users-page.tsx +262 -0
  195. package/template/src/pages/settings/settings-layout.tsx +57 -0
  196. package/template/src/pages/settings/settings-nav.ts +69 -0
  197. package/template/src/pages/settings/settings-routes.tsx +49 -0
  198. package/template/src/pages/setup-page.tsx +387 -0
  199. package/template/src/providers.tsx +68 -0
  200. package/template/src/router.tsx +59 -0
  201. package/template/tools/check-design.mjs +73 -0
  202. package/template/tools/new-module.mjs +90 -0
  203. package/template/tools/templates/module/api/__id__.mock.ts.tpl +17 -0
  204. package/template/tools/templates/module/api/__id__.service.ts.tpl +51 -0
  205. package/template/tools/templates/module/hooks.ts.tpl +57 -0
  206. package/template/tools/templates/module/index.ts.tpl +44 -0
  207. package/template/tools/templates/module/locales/en.ts.tpl +51 -0
  208. package/template/tools/templates/module/locales/tr.ts.tpl +55 -0
  209. package/template/tools/templates/module/pages/__id__-detail-page.tsx.tpl +103 -0
  210. package/template/tools/templates/module/pages/__id__-form-page.tsx.tpl +121 -0
  211. package/template/tools/templates/module/pages/__id__-list-page.tsx.tpl +234 -0
  212. package/template/tools/templates/module/types.ts.tpl +32 -0
  213. package/template/tsconfig.app.json +32 -0
  214. package/template/tsconfig.json +12 -0
  215. package/template/tsconfig.node.json +23 -0
  216. package/template/vite.config.ts +47 -0
@@ -0,0 +1,271 @@
1
+ import * as React from "react"
2
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu"
3
+ import { cn } from "@/lib/utils"
4
+ import { ChevronRightIcon, CheckIcon } from "lucide-react"
5
+
6
+ function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
7
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
8
+ }
9
+
10
+ function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
11
+ return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
12
+ }
13
+
14
+ function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
15
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
16
+ }
17
+
18
+ function DropdownMenuContent({
19
+ align = "start",
20
+ alignOffset = 0,
21
+ side = "bottom",
22
+ sideOffset = 4,
23
+ anchor,
24
+ className,
25
+ ...props
26
+ }: MenuPrimitive.Popup.Props &
27
+ Pick<
28
+ MenuPrimitive.Positioner.Props,
29
+ "align" | "alignOffset" | "side" | "sideOffset" | "anchor"
30
+ >) {
31
+ return (
32
+ <MenuPrimitive.Portal>
33
+ <MenuPrimitive.Positioner
34
+ className="isolate z-50 outline-none"
35
+ align={align}
36
+ alignOffset={alignOffset}
37
+ side={side}
38
+ sideOffset={sideOffset}
39
+ anchor={anchor}
40
+ >
41
+ <MenuPrimitive.Popup
42
+ data-slot="dropdown-menu-content"
43
+ className={cn(
44
+ "z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-14 bg-popover p-1 text-popover-foreground shadow-(--shadow-popover) ring-1 ring-border duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ </MenuPrimitive.Positioner>
50
+ </MenuPrimitive.Portal>
51
+ )
52
+ }
53
+
54
+ function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
55
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
56
+ }
57
+
58
+ function DropdownMenuLabel({
59
+ className,
60
+ inset,
61
+ ...props
62
+ }: MenuPrimitive.GroupLabel.Props & {
63
+ inset?: boolean
64
+ }) {
65
+ return (
66
+ <MenuPrimitive.GroupLabel
67
+ data-slot="dropdown-menu-label"
68
+ data-inset={inset}
69
+ className={cn(
70
+ "px-2 py-1 text-2xs text-muted-foreground data-inset:pl-9",
71
+ className
72
+ )}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ function DropdownMenuItem({
79
+ className,
80
+ inset,
81
+ variant = "default",
82
+ ...props
83
+ }: MenuPrimitive.Item.Props & {
84
+ inset?: boolean
85
+ variant?: "default" | "destructive"
86
+ }) {
87
+ return (
88
+ <MenuPrimitive.Item
89
+ data-slot="dropdown-menu-item"
90
+ data-inset={inset}
91
+ data-variant={variant}
92
+ className={cn(
93
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:not-in-data-[slot=avatar]:text-accent-foreground data-inset:pl-9 data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive-foreground dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive-foreground [&>svg:not([class*='ml-auto'])]:mx-0.5",
94
+ className
95
+ )}
96
+ {...props}
97
+ />
98
+ )
99
+ }
100
+
101
+ function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
102
+ return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
103
+ }
104
+
105
+ function DropdownMenuSubTrigger({
106
+ className,
107
+ inset,
108
+ children,
109
+ ...props
110
+ }: MenuPrimitive.SubmenuTrigger.Props & {
111
+ inset?: boolean
112
+ }) {
113
+ return (
114
+ <MenuPrimitive.SubmenuTrigger
115
+ data-slot="dropdown-menu-sub-trigger"
116
+ data-inset={inset}
117
+ className={cn(
118
+ "flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:not-in-data-[slot=avatar]:not-data-[slot=dropdown-menu-sub-trigger-icon]:text-accent-foreground data-inset:pl-9 data-popup-open:bg-accent data-popup-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&>svg:not([class*='ml-auto'])]:mx-0.5",
119
+ className
120
+ )}
121
+ {...props}
122
+ >
123
+ {children}
124
+ <ChevronRightIcon
125
+ data-slot="dropdown-menu-sub-trigger-icon"
126
+ className="ml-auto text-muted-foreground rtl:-scale-x-100"
127
+ />
128
+ </MenuPrimitive.SubmenuTrigger>
129
+ )
130
+ }
131
+
132
+ function DropdownMenuSubContent({
133
+ align = "start",
134
+ alignOffset = -4,
135
+ side = "right",
136
+ sideOffset = 1,
137
+ className,
138
+ ...props
139
+ }: React.ComponentProps<typeof DropdownMenuContent>) {
140
+ return (
141
+ <DropdownMenuContent
142
+ data-slot="dropdown-menu-sub-content"
143
+ className={cn("w-auto min-w-[96px]", className)}
144
+ align={align}
145
+ alignOffset={alignOffset}
146
+ side={side}
147
+ sideOffset={sideOffset}
148
+ {...props}
149
+ />
150
+ )
151
+ }
152
+
153
+ function DropdownMenuCheckboxItem({
154
+ className,
155
+ children,
156
+ checked,
157
+ inset,
158
+ ...props
159
+ }: MenuPrimitive.CheckboxItem.Props & {
160
+ inset?: boolean
161
+ }) {
162
+ return (
163
+ <MenuPrimitive.CheckboxItem
164
+ data-slot="dropdown-menu-checkbox-item"
165
+ data-inset={inset}
166
+ className={cn(
167
+ "relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:not-in-data-[slot=avatar]:text-accent-foreground data-inset:pl-9 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&>svg:not([class*='ml-auto'])]:mx-0.5",
168
+ className
169
+ )}
170
+ checked={checked}
171
+ {...props}
172
+ >
173
+ <span
174
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
175
+ data-slot="dropdown-menu-checkbox-item-indicator"
176
+ >
177
+ <MenuPrimitive.CheckboxItemIndicator>
178
+ <CheckIcon />
179
+ </MenuPrimitive.CheckboxItemIndicator>
180
+ </span>
181
+ {children}
182
+ </MenuPrimitive.CheckboxItem>
183
+ )
184
+ }
185
+
186
+ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
187
+ return (
188
+ <MenuPrimitive.RadioGroup
189
+ data-slot="dropdown-menu-radio-group"
190
+ {...props}
191
+ />
192
+ )
193
+ }
194
+
195
+ function DropdownMenuRadioItem({
196
+ className,
197
+ children,
198
+ inset,
199
+ ...props
200
+ }: MenuPrimitive.RadioItem.Props & {
201
+ inset?: boolean
202
+ }) {
203
+ return (
204
+ <MenuPrimitive.RadioItem
205
+ data-slot="dropdown-menu-radio-item"
206
+ data-inset={inset}
207
+ className={cn(
208
+ "relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:not-in-data-[slot=avatar]:text-accent-foreground data-inset:pl-9 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&>svg:not([class*='ml-auto'])]:mx-0.5",
209
+ className
210
+ )}
211
+ {...props}
212
+ >
213
+ <span
214
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
215
+ data-slot="dropdown-menu-radio-item-indicator"
216
+ >
217
+ <MenuPrimitive.RadioItemIndicator>
218
+ <CheckIcon />
219
+ </MenuPrimitive.RadioItemIndicator>
220
+ </span>
221
+ {children}
222
+ </MenuPrimitive.RadioItem>
223
+ )
224
+ }
225
+
226
+ function DropdownMenuSeparator({
227
+ className,
228
+ ...props
229
+ }: MenuPrimitive.Separator.Props) {
230
+ return (
231
+ <MenuPrimitive.Separator
232
+ data-slot="dropdown-menu-separator"
233
+ className={cn("-mx-1 mt-1 mb-[3px] h-px bg-border", className)}
234
+ {...props}
235
+ />
236
+ )
237
+ }
238
+
239
+ function DropdownMenuShortcut({
240
+ className,
241
+ ...props
242
+ }: React.ComponentProps<"span">) {
243
+ return (
244
+ <span
245
+ data-slot="dropdown-menu-shortcut"
246
+ className={cn(
247
+ "ml-auto text-2xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
248
+ className
249
+ )}
250
+ {...props}
251
+ />
252
+ )
253
+ }
254
+
255
+ export {
256
+ DropdownMenu,
257
+ DropdownMenuPortal,
258
+ DropdownMenuTrigger,
259
+ DropdownMenuContent,
260
+ DropdownMenuGroup,
261
+ DropdownMenuLabel,
262
+ DropdownMenuItem,
263
+ DropdownMenuCheckboxItem,
264
+ DropdownMenuRadioGroup,
265
+ DropdownMenuRadioItem,
266
+ DropdownMenuSeparator,
267
+ DropdownMenuShortcut,
268
+ DropdownMenuSub,
269
+ DropdownMenuSubTrigger,
270
+ DropdownMenuSubContent,
271
+ }
@@ -0,0 +1,103 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ function Empty({ className, ...props }: React.ComponentProps<"div">) {
5
+ return (
6
+ <div
7
+ data-slot="empty"
8
+ className={cn(
9
+ "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 p-6 text-center text-balance",
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
18
+ return (
19
+ <div
20
+ data-slot="empty-header"
21
+ className={cn("flex max-w-sm flex-col items-center gap-1", className)}
22
+ {...props}
23
+ />
24
+ )
25
+ }
26
+
27
+ const emptyMediaVariants = cva(
28
+ "mb-3 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
29
+ {
30
+ variants: {
31
+ variant: {
32
+ default: "bg-transparent",
33
+ icon: "size-8 rounded-md bg-fill-2 text-muted-foreground [&_svg:not([class*='size-'])]:size-4",
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ variant: "default",
38
+ },
39
+ }
40
+ )
41
+
42
+ function EmptyMedia({
43
+ className,
44
+ variant = "default",
45
+ ...props
46
+ }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
47
+ return (
48
+ <div
49
+ data-slot="empty-icon"
50
+ data-variant={variant}
51
+ className={cn(emptyMediaVariants({ variant, className }))}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
58
+ return (
59
+ <div
60
+ data-slot="empty-title"
61
+ className={cn(
62
+ "font-heading text-base font-medium",
63
+ className
64
+ )}
65
+ {...props}
66
+ />
67
+ )
68
+ }
69
+
70
+ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
71
+ return (
72
+ <div
73
+ data-slot="empty-description"
74
+ className={cn(
75
+ "text-sm text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
76
+ className
77
+ )}
78
+ {...props}
79
+ />
80
+ )
81
+ }
82
+
83
+ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="empty-content"
87
+ className={cn(
88
+ "flex w-full max-w-sm min-w-0 flex-col items-center gap-3 text-sm text-balance",
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ )
94
+ }
95
+
96
+ export {
97
+ Empty,
98
+ EmptyHeader,
99
+ EmptyTitle,
100
+ EmptyDescription,
101
+ EmptyContent,
102
+ EmptyMedia,
103
+ }
@@ -0,0 +1,236 @@
1
+ import { useMemo } from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ import { Label } from "@/components/ui/label"
6
+ import { Separator } from "@/components/ui/separator"
7
+
8
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
9
+ return (
10
+ <fieldset
11
+ data-slot="field-set"
12
+ className={cn("flex flex-col gap-5", className)}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function FieldLegend({
19
+ className,
20
+ variant = "legend",
21
+ ...props
22
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
23
+ return (
24
+ <legend
25
+ data-slot="field-legend"
26
+ data-variant={variant}
27
+ className={cn(
28
+ "mb-5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
29
+ className
30
+ )}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="field-group"
40
+ className={cn(
41
+ "group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ const fieldVariants = cva(
50
+ "group/field flex w-full gap-2 data-[invalid=true]:text-destructive-foreground",
51
+ {
52
+ variants: {
53
+ orientation: {
54
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
55
+ horizontal:
56
+ "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],&>[role=radio]]:mt-0.5",
57
+ responsive:
58
+ "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],&>[role=radio]]:mt-0.5",
59
+ },
60
+ },
61
+ defaultVariants: {
62
+ orientation: "vertical",
63
+ },
64
+ }
65
+ )
66
+
67
+ function Field({
68
+ className,
69
+ orientation = "vertical",
70
+ ...props
71
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
72
+ return (
73
+ <div
74
+ role="group"
75
+ data-slot="field"
76
+ data-orientation={orientation}
77
+ className={cn(fieldVariants({ orientation }), className)}
78
+ {...props}
79
+ />
80
+ )
81
+ }
82
+
83
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="field-content"
87
+ className={cn(
88
+ "group/field-content flex flex-1 flex-col gap-1 leading-5",
89
+ className
90
+ )}
91
+ {...props}
92
+ />
93
+ )
94
+ }
95
+
96
+ function FieldLabel({
97
+ className,
98
+ ...props
99
+ }: 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-5 group-data-[disabled=true]/field:opacity-50 has-data-checked:bg-fill-1 has-[>[data-slot=field]]:rounded-2xl has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-fill-1 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/30 *:data-[slot=field]:p-3.75",
105
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
106
+ className
107
+ )}
108
+ {...props}
109
+ />
110
+ )
111
+ }
112
+
113
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
114
+ return (
115
+ <div
116
+ data-slot="field-label"
117
+ className={cn(
118
+ "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ )
124
+ }
125
+
126
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
127
+ return (
128
+ <p
129
+ data-slot="field-description"
130
+ className={cn(
131
+ "text-start text-sm leading-5 font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=label]+&]:-mt-4 [[data-variant=legend]+&]:-mt-3.5",
132
+ "last:mt-0 nth-last-2:-mt-1",
133
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
134
+ className
135
+ )}
136
+ {...props}
137
+ />
138
+ )
139
+ }
140
+
141
+ function FieldSeparator({
142
+ children,
143
+ className,
144
+ ...props
145
+ }: React.ComponentProps<"div"> & {
146
+ children?: React.ReactNode
147
+ }) {
148
+ return (
149
+ <div
150
+ data-slot="field-separator"
151
+ data-content={!!children}
152
+ className={cn(
153
+ "relative h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
154
+ className
155
+ )}
156
+ {...props}
157
+ >
158
+ <Separator className="absolute inset-0 top-1/2" />
159
+ {children && (
160
+ <span
161
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
162
+ data-slot="field-separator-content"
163
+ >
164
+ {children}
165
+ </span>
166
+ )}
167
+ </div>
168
+ )
169
+ }
170
+
171
+ function FieldError({
172
+ className,
173
+ children,
174
+ errors,
175
+ ...props
176
+ }: React.ComponentProps<"div"> & {
177
+ errors?: Array<{ message?: string } | undefined>
178
+ }) {
179
+ const content = useMemo(() => {
180
+ if (children) {
181
+ return children
182
+ }
183
+
184
+ if (!errors?.length) {
185
+ return null
186
+ }
187
+
188
+ const uniqueErrors = [
189
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
190
+ ]
191
+
192
+ if (uniqueErrors?.length == 1) {
193
+ return uniqueErrors[0]?.message
194
+ }
195
+
196
+ return (
197
+ <ul className="ml-4 flex list-disc flex-col gap-1">
198
+ {uniqueErrors.map(
199
+ (error, index) =>
200
+ error?.message && <li key={index}>{error.message}</li>
201
+ )}
202
+ </ul>
203
+ )
204
+ }, [children, errors])
205
+
206
+ if (!content) {
207
+ return null
208
+ }
209
+
210
+ return (
211
+ <div
212
+ role="alert"
213
+ data-slot="field-error"
214
+ className={cn(
215
+ "text-sm leading-5 font-normal text-destructive-foreground [[data-variant=label]+&]:-mt-4 [[data-variant=legend]+&]:-mt-3.5",
216
+ className
217
+ )}
218
+ {...props}
219
+ >
220
+ {content}
221
+ </div>
222
+ )
223
+ }
224
+
225
+ export {
226
+ Field,
227
+ FieldLabel,
228
+ FieldDescription,
229
+ FieldError,
230
+ FieldGroup,
231
+ FieldLegend,
232
+ FieldSeparator,
233
+ FieldSet,
234
+ FieldContent,
235
+ FieldTitle,
236
+ }