@nocobase/app-template-default 0.0.0-stage → 1.0.0-beta.57

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 (250) hide show
  1. package/.cursor/mcp.json +8 -0
  2. package/.mcp.json +8 -0
  3. package/.prettierignore +20 -0
  4. package/.vscode/mcp.json +8 -0
  5. package/AGENTS.md +432 -0
  6. package/CLAUDE.md +9 -0
  7. package/Dockerfile +132 -0
  8. package/Dockerfile.dockerignore +45 -0
  9. package/README.MD +481 -0
  10. package/cli/plugins.ts +14 -0
  11. package/client/agent-annotations-host.ts +19 -0
  12. package/client/app.ts +38 -0
  13. package/client/components/back-button.tsx +49 -0
  14. package/client/components/breadcrumbs.tsx +167 -0
  15. package/client/components/inbox-badge.ts +49 -0
  16. package/client/components/inbox-button.tsx +132 -0
  17. package/client/components/inbox-header-button.tsx +25 -0
  18. package/client/components/loading.tsx +35 -0
  19. package/client/components/page-container.tsx +24 -0
  20. package/client/components/page-header.tsx +33 -0
  21. package/client/components/route-child-page.tsx +121 -0
  22. package/client/components/route-dialog.tsx +10 -0
  23. package/client/components/route-drawer.tsx +10 -0
  24. package/client/components/route-overlay.tsx +190 -0
  25. package/client/components/ui/alert.tsx +75 -0
  26. package/client/components/ui/badge.tsx +51 -0
  27. package/client/components/ui/breadcrumb.tsx +121 -0
  28. package/client/components/ui/button.tsx +58 -0
  29. package/client/components/ui/collapsible.tsx +19 -0
  30. package/client/components/ui/context-menu.tsx +271 -0
  31. package/client/components/ui/dialog.tsx +160 -0
  32. package/client/components/ui/dropdown-menu.tsx +270 -0
  33. package/client/components/ui/empty.tsx +103 -0
  34. package/client/components/ui/field.tsx +236 -0
  35. package/client/components/ui/input-group.tsx +158 -0
  36. package/client/components/ui/input.tsx +20 -0
  37. package/client/components/ui/kbd.tsx +26 -0
  38. package/client/components/ui/label.tsx +18 -0
  39. package/client/components/ui/popover.tsx +89 -0
  40. package/client/components/ui/select.tsx +200 -0
  41. package/client/components/ui/separator.tsx +22 -0
  42. package/client/components/ui/sheet.tsx +137 -0
  43. package/client/components/ui/sidebar.tsx +723 -0
  44. package/client/components/ui/skeleton.tsx +13 -0
  45. package/client/components/ui/spinner.tsx +20 -0
  46. package/client/components/ui/tabs.tsx +79 -0
  47. package/client/components/ui/textarea.tsx +17 -0
  48. package/client/components/ui/toast.tsx +231 -0
  49. package/client/components/ui/tooltip.tsx +63 -0
  50. package/client/components/use-route-overlay.ts +26 -0
  51. package/client/config/api.ts +13 -0
  52. package/client/config/app.ts +8 -0
  53. package/client/config/auth.ts +18 -0
  54. package/client/config/index.ts +17 -0
  55. package/client/extensions/nocobase-auth-forms/field-props.ts +13 -0
  56. package/client/extensions/nocobase-auth-forms/form-parts.tsx +101 -0
  57. package/client/extensions/nocobase-auth-forms/password-input.tsx +41 -0
  58. package/client/extensions/nocobase-auth-forms/password-login-form.tsx +123 -0
  59. package/client/extensions/nocobase-auth-forms/password-registration-form.tsx +193 -0
  60. package/client/extensions/nocobase-auth-forms/password-reset-form.tsx +132 -0
  61. package/client/extensions/nocobase-auth-forms/password-reset-request-form.tsx +99 -0
  62. package/client/extensions/nocobase-auth-methods/auth-methods.tsx +170 -0
  63. package/client/extensions/nocobase-auth-split-layout/auth-split-layout.tsx +126 -0
  64. package/client/extensions/nocobase-device-approval/device-approval.tsx +277 -0
  65. package/client/extensions/nocobase-device-approval/locales/en-US.ts +55 -0
  66. package/client/extensions/nocobase-device-approval/locales/zh-CN.ts +27 -0
  67. package/client/extensions/nocobase-device-approval/use-device-approval.ts +176 -0
  68. package/client/extensions/nocobase-file-component-ui/README.md +13 -0
  69. package/client/extensions/nocobase-file-component-ui/components/file-list.tsx +147 -0
  70. package/client/extensions/nocobase-file-component-ui/components/file-preview-dialog.tsx +263 -0
  71. package/client/extensions/nocobase-file-component-ui/components/file-preview-field.tsx +68 -0
  72. package/client/extensions/nocobase-file-component-ui/components/file-thumbnail.tsx +73 -0
  73. package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +386 -0
  74. package/client/extensions/nocobase-file-component-ui/components/previewers/file-preview-content.tsx +255 -0
  75. package/client/extensions/nocobase-file-component-ui/components/previewers/office-open-xml-preview.tsx +226 -0
  76. package/client/extensions/nocobase-file-component-ui/index.ts +8 -0
  77. package/client/extensions/nocobase-file-component-ui/lib/file-preview.ts +194 -0
  78. package/client/extensions/nocobase-file-component-ui/lib/file-url.ts +22 -0
  79. package/client/extensions/nocobase-file-component-ui/lib/message-key.ts +18 -0
  80. package/client/extensions/nocobase-file-component-ui/types.ts +69 -0
  81. package/client/extensions/nocobase-inbox/decision-actions-bar.tsx +140 -0
  82. package/client/extensions/nocobase-inbox/frame.ts +31 -0
  83. package/client/extensions/nocobase-inbox/inbox-actions.ts +21 -0
  84. package/client/extensions/nocobase-inbox/inbox-detail.tsx +386 -0
  85. package/client/extensions/nocobase-inbox/inbox-item.tsx +274 -0
  86. package/client/extensions/nocobase-inbox/inbox-list.tsx +433 -0
  87. package/client/extensions/nocobase-inbox/inbox-page.tsx +545 -0
  88. package/client/extensions/nocobase-inbox/locales/en-US.ts +111 -0
  89. package/client/extensions/nocobase-inbox/locales/zh-CN.ts +57 -0
  90. package/client/extensions/nocobase-inbox/model.ts +428 -0
  91. package/client/extensions/nocobase-inbox/registry-scope.tsx +49 -0
  92. package/client/extensions/nocobase-inbox/registry.ts +303 -0
  93. package/client/extensions/nocobase-inbox/source.ts +117 -0
  94. package/client/hooks/use-mobile.ts +21 -0
  95. package/client/index.tsx +60 -0
  96. package/client/layouts/app-layout.tsx +105 -0
  97. package/client/layouts/components/app-brand.tsx +49 -0
  98. package/client/layouts/components/app-sidebar.tsx +196 -0
  99. package/client/layouts/components/header-actions.tsx +50 -0
  100. package/client/layouts/components/language-switcher.tsx +95 -0
  101. package/client/layouts/components/layout-header.tsx +17 -0
  102. package/client/layouts/components/navigation-menu.tsx +411 -0
  103. package/client/layouts/components/sidebar-footer.tsx +107 -0
  104. package/client/layouts/components/surface-empty.tsx +35 -0
  105. package/client/layouts/components/user-menu.tsx +127 -0
  106. package/client/layouts/index.ts +2 -0
  107. package/client/layouts/settings-layout.tsx +172 -0
  108. package/client/layouts/use-sidebar-preference.ts +57 -0
  109. package/client/lib/constants.ts +5 -0
  110. package/client/lib/toaster.ts +56 -0
  111. package/client/lib/utils.ts +1 -0
  112. package/client/locales/en-US.ts +178 -0
  113. package/client/locales/index.ts +9 -0
  114. package/client/locales/zh-CN.ts +161 -0
  115. package/client/pages/auth/device.tsx +41 -0
  116. package/client/pages/auth/forgot-password.tsx +39 -0
  117. package/client/pages/auth/login.tsx +66 -0
  118. package/client/pages/auth/register.tsx +46 -0
  119. package/client/pages/auth/reset-password.tsx +38 -0
  120. package/client/pages/auth/shared.tsx +111 -0
  121. package/client/pages/home.tsx +16 -0
  122. package/client/pages/inbox.tsx +12 -0
  123. package/client/plugins.ts +41 -0
  124. package/client/react-providers.ts +27 -0
  125. package/client/route-overrides.ts +9 -0
  126. package/client/routes.ts +75 -0
  127. package/client/routing/app-router.tsx +127 -0
  128. package/client/routing/client-route.tsx +129 -0
  129. package/client/routing/route-context.tsx +85 -0
  130. package/client/routing/route-navigation.ts +153 -0
  131. package/client/routing/route-tree.tsx +42 -0
  132. package/client/routing/standalone-page-layout.tsx +15 -0
  133. package/client/runtime.ts +26 -0
  134. package/client/service-provider.ts +40 -0
  135. package/client/source-extensions.ts +19 -0
  136. package/client/startup.tsx +22 -0
  137. package/client/styles.css +122 -0
  138. package/client/theme/index.ts +4 -0
  139. package/client/theme/theme-context.ts +12 -0
  140. package/client/theme/theme-preferences.ts +84 -0
  141. package/client/theme/theme-presets.ts +7 -0
  142. package/client/theme/theme-provider.tsx +106 -0
  143. package/client/theme/theme-settings.tsx +176 -0
  144. package/client/theme/themes/compact.css +140 -0
  145. package/client/theme/themes/default.css +117 -0
  146. package/client/vite-env.d.ts +5 -0
  147. package/components.json +27 -0
  148. package/config.example.yml +172 -0
  149. package/database/main/migrations/.gitkeep +0 -0
  150. package/database/main/migrations/202610060001_create_device_code.ts +37 -0
  151. package/database/main/seeds/.gitkeep +0 -0
  152. package/database/tsconfig.json +4 -0
  153. package/ecosystem.config.js +35 -0
  154. package/eslint.config.js +24 -0
  155. package/index.html +100 -0
  156. package/package.json +200 -3
  157. package/playwright.config.ts +10 -0
  158. package/public/assets/favicon.ico +0 -0
  159. package/public/assets/logo-dark.png +0 -0
  160. package/public/assets/logo-mark-dark.png +0 -0
  161. package/public/assets/logo-mark.png +0 -0
  162. package/public/assets/logo.png +0 -0
  163. package/server/app.ts +50 -0
  164. package/server/config/ai.ts +27 -0
  165. package/server/config/api.ts +8 -0
  166. package/server/config/app.ts +40 -0
  167. package/server/config/auth.ts +36 -0
  168. package/server/config/authorization.ts +18 -0
  169. package/server/config/caching.ts +21 -0
  170. package/server/config/database.ts +22 -0
  171. package/server/config/drive.ts +30 -0
  172. package/server/config/i18n.ts +17 -0
  173. package/server/config/index.ts +73 -0
  174. package/server/config/jobs.ts +27 -0
  175. package/server/config/logging.ts +23 -0
  176. package/server/config/notification.ts +11 -0
  177. package/server/config/queue.ts +28 -0
  178. package/server/config/scheduler.ts +23 -0
  179. package/server/config/secrets.ts +10 -0
  180. package/server/config/server.ts +26 -0
  181. package/server/config/session.ts +60 -0
  182. package/server/config/snowflake.ts +21 -0
  183. package/server/config/spa.ts +24 -0
  184. package/server/config/users.ts +11 -0
  185. package/server/config/workflow.ts +24 -0
  186. package/server/config.ts +24 -0
  187. package/server/embedded.ts +23 -0
  188. package/server/locales/en-US.ts +10 -0
  189. package/server/locales/index.ts +10 -0
  190. package/server/locales/zh-CN.ts +5 -0
  191. package/server/plugins.ts +41 -0
  192. package/server/providers/index.ts +5 -0
  193. package/server/routes/index.ts +6 -0
  194. package/server/runtime.ts +24 -0
  195. package/server/standalone.ts +29 -0
  196. package/server/tsconfig.json +4 -0
  197. package/tailwind.config.mjs +59 -0
  198. package/tests/components/app-layout-routes.test.tsx +132 -0
  199. package/tests/components/app-title.test.tsx +57 -0
  200. package/tests/components/auth-i18n.test.tsx +87 -0
  201. package/tests/components/back-button.test.tsx +80 -0
  202. package/tests/components/file-preview.test.tsx +101 -0
  203. package/tests/components/header-hover.test.tsx +87 -0
  204. package/tests/components/inbox.test.tsx +139 -0
  205. package/tests/components/language-switcher.test.tsx +298 -0
  206. package/tests/components/layout-containers.test.tsx +15 -0
  207. package/tests/components/layout-i18n.test.tsx +187 -0
  208. package/tests/components/loading.test.tsx +25 -0
  209. package/tests/components/navigation-menu.test.tsx +355 -0
  210. package/tests/components/page-harness.test.tsx +318 -0
  211. package/tests/components/primitive-labels.test.tsx +80 -0
  212. package/tests/components/sidebar-permissions.test.tsx +110 -0
  213. package/tests/components/sidebar-preference.test.tsx +42 -0
  214. package/tests/fixtures/theme-tokens.html +12 -0
  215. package/tests/fixtures/theme-tokens.tsx +111 -0
  216. package/tests/logic/account-permissions.test.tsx +204 -0
  217. package/tests/logic/app-locale-coverage.test.ts +136 -0
  218. package/tests/logic/app-server.test.ts +1589 -0
  219. package/tests/logic/asset-url-build.test.ts +106 -0
  220. package/tests/logic/client-auth.test.tsx +122 -0
  221. package/tests/logic/client-plugin-registry.test.ts +63 -0
  222. package/tests/logic/client-route-recovery.test.tsx +80 -0
  223. package/tests/logic/client-routes.test.ts +126 -0
  224. package/tests/logic/client-runtime.test.ts +112 -0
  225. package/tests/logic/client-settings.test.tsx +841 -0
  226. package/tests/logic/client-shell.test.tsx +466 -0
  227. package/tests/logic/client-theme.test.tsx +441 -0
  228. package/tests/logic/config.test.ts +197 -0
  229. package/tests/logic/dev-client-watch.test.ts +108 -0
  230. package/tests/logic/dev-routes-production-build.test.ts +168 -0
  231. package/tests/logic/device-code-migration.test.ts +70 -0
  232. package/tests/logic/lifecycle.test.ts +221 -0
  233. package/tests/logic/plugin-commands.test.ts +69 -0
  234. package/tests/logic/plugin-page-authz.test.ts +76 -0
  235. package/tests/logic/route-navigation.test.ts +66 -0
  236. package/tests/logic/route-overlay.test.tsx +353 -0
  237. package/tests/logic/server-imports.test.ts +88 -0
  238. package/tests/logic/tailwind-sources.test.ts +151 -0
  239. package/tests/logic/template-package-name.test.ts +88 -0
  240. package/tests/logic/theme-preferences.test.ts +86 -0
  241. package/tests/logic/theme-tokens.test.ts +248 -0
  242. package/tests/logic/toaster.test.tsx +108 -0
  243. package/tests/logic/user-menu-sign-out.test.tsx +98 -0
  244. package/tests/setup/client-config.ts +27 -0
  245. package/tsconfig.json +11 -0
  246. package/tsconfig.node.json +12 -0
  247. package/tsconfig.server.json +19 -0
  248. package/vite.config.ts +92 -0
  249. package/vitest.config.ts +37 -0
  250. package/README.md +0 -3
@@ -0,0 +1,103 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority';
2
+ import { cn } from 'cn';
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 rounded-xl border-dashed 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-2', className)}
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+
27
+ const emptyMediaVariants = cva(
28
+ 'mb-2 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: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-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-sm font-medium tracking-tight',
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/relaxed 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-2.5 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 'cn';
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(
13
+ 'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function FieldLegend({
22
+ className,
23
+ variant = 'legend',
24
+ ...props
25
+ }: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
26
+ return (
27
+ <legend
28
+ data-slot='field-legend'
29
+ data-variant={variant}
30
+ className={cn(
31
+ 'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',
32
+ className,
33
+ )}
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-5 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(
53
+ 'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',
54
+ {
55
+ variants: {
56
+ orientation: {
57
+ vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',
58
+ horizontal:
59
+ '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-px',
60
+ responsive:
61
+ '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-px',
62
+ },
63
+ },
64
+ defaultVariants: {
65
+ orientation: 'vertical',
66
+ },
67
+ },
68
+ );
69
+
70
+ function Field({
71
+ className,
72
+ orientation = 'vertical',
73
+ ...props
74
+ }: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
75
+ return (
76
+ <div
77
+ role='group'
78
+ data-slot='field'
79
+ data-orientation={orientation}
80
+ className={cn(fieldVariants({ orientation }), className)}
81
+ {...props}
82
+ />
83
+ );
84
+ }
85
+
86
+ function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
87
+ return (
88
+ <div
89
+ data-slot='field-content'
90
+ className={cn(
91
+ 'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',
92
+ className,
93
+ )}
94
+ {...props}
95
+ />
96
+ );
97
+ }
98
+
99
+ function FieldLabel({
100
+ className,
101
+ ...props
102
+ }: React.ComponentProps<typeof Label>) {
103
+ return (
104
+ <Label
105
+ data-slot='field-label'
106
+ className={cn(
107
+ 'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 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/50 *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',
108
+ 'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',
109
+ className,
110
+ )}
111
+ {...props}
112
+ />
113
+ );
114
+ }
115
+
116
+ function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
117
+ return (
118
+ <div
119
+ data-slot='field-label'
120
+ className={cn(
121
+ 'flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50',
122
+ className,
123
+ )}
124
+ {...props}
125
+ />
126
+ );
127
+ }
128
+
129
+ function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
130
+ return (
131
+ <p
132
+ data-slot='field-description'
133
+ className={cn(
134
+ 'text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5',
135
+ 'last:mt-0 nth-last-2:-mt-1',
136
+ '[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
137
+ className,
138
+ )}
139
+ {...props}
140
+ />
141
+ );
142
+ }
143
+
144
+ function FieldSeparator({
145
+ children,
146
+ className,
147
+ ...props
148
+ }: React.ComponentProps<'div'> & {
149
+ children?: React.ReactNode;
150
+ }) {
151
+ return (
152
+ <div
153
+ data-slot='field-separator'
154
+ data-content={!!children}
155
+ className={cn(
156
+ 'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
157
+ className,
158
+ )}
159
+ {...props}
160
+ >
161
+ <Separator className='absolute inset-0 top-1/2' />
162
+ {children && (
163
+ <span
164
+ className='relative mx-auto block w-fit bg-background px-2 text-muted-foreground'
165
+ data-slot='field-separator-content'
166
+ >
167
+ {children}
168
+ </span>
169
+ )}
170
+ </div>
171
+ );
172
+ }
173
+
174
+ function FieldError({
175
+ className,
176
+ children,
177
+ errors,
178
+ ...props
179
+ }: React.ComponentProps<'div'> & {
180
+ errors?: Array<{ message?: string } | undefined>;
181
+ }) {
182
+ const content = useMemo(() => {
183
+ if (children) {
184
+ return children;
185
+ }
186
+
187
+ if (!errors?.length) {
188
+ return null;
189
+ }
190
+
191
+ const uniqueErrors = [
192
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
193
+ ];
194
+
195
+ if (uniqueErrors?.length == 1) {
196
+ return uniqueErrors[0]?.message;
197
+ }
198
+
199
+ return (
200
+ <ul className='ml-4 flex list-disc flex-col gap-1'>
201
+ {uniqueErrors.map(
202
+ (error, index) =>
203
+ error?.message && <li key={index}>{error.message}</li>,
204
+ )}
205
+ </ul>
206
+ );
207
+ }, [children, errors]);
208
+
209
+ if (!content) {
210
+ return null;
211
+ }
212
+
213
+ return (
214
+ <div
215
+ role='alert'
216
+ data-slot='field-error'
217
+ className={cn('text-sm font-normal text-destructive', className)}
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
+ };
@@ -0,0 +1,158 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { cn } from 'cn';
6
+
7
+ import { Button } from '#components/ui/button';
8
+ import { Input } from '#components/ui/input';
9
+ import { Textarea } from '#components/ui/textarea';
10
+
11
+ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
12
+ return (
13
+ <div
14
+ data-slot='input-group'
15
+ role='group'
16
+ className={cn(
17
+ 'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ const inputGroupAddonVariants = cva(
26
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
27
+ {
28
+ variants: {
29
+ align: {
30
+ 'inline-start':
31
+ 'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',
32
+ 'inline-end':
33
+ 'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',
34
+ 'block-start':
35
+ 'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',
36
+ 'block-end':
37
+ 'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',
38
+ },
39
+ },
40
+ defaultVariants: {
41
+ align: 'inline-start',
42
+ },
43
+ },
44
+ );
45
+
46
+ function InputGroupAddon({
47
+ className,
48
+ align = 'inline-start',
49
+ ...props
50
+ }: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
51
+ return (
52
+ <div
53
+ role='group'
54
+ data-slot='input-group-addon'
55
+ data-align={align}
56
+ className={cn(inputGroupAddonVariants({ align }), className)}
57
+ onClick={(e) => {
58
+ if ((e.target as HTMLElement).closest('button')) {
59
+ return;
60
+ }
61
+ e.currentTarget.parentElement?.querySelector('input')?.focus();
62
+ }}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ const inputGroupButtonVariants = cva(
69
+ 'flex items-center gap-2 text-sm shadow-none',
70
+ {
71
+ variants: {
72
+ size: {
73
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
74
+ sm: '',
75
+ 'icon-xs':
76
+ 'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',
77
+ 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
78
+ },
79
+ },
80
+ defaultVariants: {
81
+ size: 'xs',
82
+ },
83
+ },
84
+ );
85
+
86
+ function InputGroupButton({
87
+ className,
88
+ type = 'button',
89
+ variant = 'ghost',
90
+ size = 'xs',
91
+ ...props
92
+ }: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &
93
+ VariantProps<typeof inputGroupButtonVariants> & {
94
+ type?: 'button' | 'submit' | 'reset';
95
+ }) {
96
+ return (
97
+ <Button
98
+ type={type}
99
+ data-size={size}
100
+ variant={variant}
101
+ className={cn(inputGroupButtonVariants({ size }), className)}
102
+ {...props}
103
+ />
104
+ );
105
+ }
106
+
107
+ function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
108
+ return (
109
+ <span
110
+ className={cn(
111
+ "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
112
+ className,
113
+ )}
114
+ {...props}
115
+ />
116
+ );
117
+ }
118
+
119
+ function InputGroupInput({
120
+ className,
121
+ ...props
122
+ }: React.ComponentProps<'input'>) {
123
+ return (
124
+ <Input
125
+ data-slot='input-group-control'
126
+ className={cn(
127
+ 'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',
128
+ className,
129
+ )}
130
+ {...props}
131
+ />
132
+ );
133
+ }
134
+
135
+ function InputGroupTextarea({
136
+ className,
137
+ ...props
138
+ }: React.ComponentProps<'textarea'>) {
139
+ return (
140
+ <Textarea
141
+ data-slot='input-group-control'
142
+ className={cn(
143
+ 'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',
144
+ className,
145
+ )}
146
+ {...props}
147
+ />
148
+ );
149
+ }
150
+
151
+ export {
152
+ InputGroup,
153
+ InputGroupAddon,
154
+ InputGroupButton,
155
+ InputGroupText,
156
+ InputGroupInput,
157
+ InputGroupTextarea,
158
+ };
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ import { Input as InputPrimitive } from '@base-ui/react/input';
3
+
4
+ import { cn } from 'cn';
5
+
6
+ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
7
+ return (
8
+ <InputPrimitive
9
+ type={type}
10
+ data-slot='input'
11
+ className={cn(
12
+ 'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ export { Input };
@@ -0,0 +1,26 @@
1
+ import { cn } from 'cn';
2
+
3
+ function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {
4
+ return (
5
+ <kbd
6
+ data-slot='kbd'
7
+ className={cn(
8
+ "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
9
+ className,
10
+ )}
11
+ {...props}
12
+ />
13
+ );
14
+ }
15
+
16
+ function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {
17
+ return (
18
+ <kbd
19
+ data-slot='kbd-group'
20
+ className={cn('inline-flex items-center gap-1', className)}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ export { Kbd, KbdGroup };
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from 'cn';
4
+
5
+ function Label({ className, ...props }: React.ComponentProps<'label'>) {
6
+ return (
7
+ <label
8
+ data-slot='label'
9
+ className={cn(
10
+ 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ export { Label };
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Popover as PopoverPrimitive } from '@base-ui/react/popover';
5
+ import { cn } from 'cn';
6
+
7
+ function Popover({ ...props }: PopoverPrimitive.Root.Props) {
8
+ return <PopoverPrimitive.Root data-slot='popover' {...props} />;
9
+ }
10
+
11
+ function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
12
+ return <PopoverPrimitive.Trigger data-slot='popover-trigger' {...props} />;
13
+ }
14
+
15
+ function PopoverContent({
16
+ className,
17
+ align = 'center',
18
+ alignOffset = 0,
19
+ side = 'bottom',
20
+ sideOffset = 4,
21
+ ...props
22
+ }: PopoverPrimitive.Popup.Props &
23
+ Pick<
24
+ PopoverPrimitive.Positioner.Props,
25
+ 'align' | 'alignOffset' | 'side' | 'sideOffset'
26
+ >) {
27
+ return (
28
+ <PopoverPrimitive.Portal>
29
+ <PopoverPrimitive.Positioner
30
+ align={align}
31
+ alignOffset={alignOffset}
32
+ side={side}
33
+ sideOffset={sideOffset}
34
+ className='isolate z-50'
35
+ >
36
+ <PopoverPrimitive.Popup
37
+ data-slot='popover-content'
38
+ className={cn(
39
+ 'z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 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:fade-out-0 data-closed:zoom-out-95',
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ </PopoverPrimitive.Positioner>
45
+ </PopoverPrimitive.Portal>
46
+ );
47
+ }
48
+
49
+ function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
50
+ return (
51
+ <div
52
+ data-slot='popover-header'
53
+ className={cn('flex flex-col gap-0.5 text-sm', className)}
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+
59
+ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
60
+ return (
61
+ <PopoverPrimitive.Title
62
+ data-slot='popover-title'
63
+ className={cn('font-heading font-medium', className)}
64
+ {...props}
65
+ />
66
+ );
67
+ }
68
+
69
+ function PopoverDescription({
70
+ className,
71
+ ...props
72
+ }: PopoverPrimitive.Description.Props) {
73
+ return (
74
+ <PopoverPrimitive.Description
75
+ data-slot='popover-description'
76
+ className={cn('text-muted-foreground', className)}
77
+ {...props}
78
+ />
79
+ );
80
+ }
81
+
82
+ export {
83
+ Popover,
84
+ PopoverContent,
85
+ PopoverDescription,
86
+ PopoverHeader,
87
+ PopoverTitle,
88
+ PopoverTrigger,
89
+ };