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,51 @@
1
+ {
2
+ "name": "fp-admin",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "oxlint",
10
+ "check": "node tools/check-design.mjs && npm run lint && npm run build",
11
+ "new-module": "node tools/new-module.mjs",
12
+ "preview": "vite preview",
13
+ "fp:update": "npx create-freakpunch@latest update"
14
+ },
15
+ "dependencies": {
16
+ "@base-ui/react": "^1.9.0",
17
+ "@fontsource-variable/inter": "^5.3.0",
18
+ "@hookform/resolvers": "^6.0.2",
19
+ "@tailwindcss/vite": "^4.3.3",
20
+ "@tanstack/react-query": "^5.105.0",
21
+ "@tanstack/react-table": "^9.3.1",
22
+ "class-variance-authority": "^0.7.1",
23
+ "cmdk": "^1.1.1",
24
+ "cn": "^0.5.0",
25
+ "date-fns": "^4.4.0",
26
+ "i18next": "^26.5.11",
27
+ "lucide-react": "^1.63.0",
28
+ "next-themes": "^0.4.6",
29
+ "react": "^19.3.1",
30
+ "react-day-picker": "^10.0.2",
31
+ "react-dom": "^19.3.1",
32
+ "react-hook-form": "^7.91.0",
33
+ "react-i18next": "^17.1.9",
34
+ "react-router": "^8.5.1",
35
+ "recharts": "^3.8.0",
36
+ "shadcn": "^4.23.0",
37
+ "sonner": "^2.0.8",
38
+ "tailwindcss": "^4.3.3",
39
+ "tw-animate-css": "^1.4.0",
40
+ "zod": "^4.6.7"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^24.19.2",
44
+ "@types/react": "^19.3.0",
45
+ "@types/react-dom": "^19.3.0",
46
+ "@vitejs/plugin-react": "^6.1.2",
47
+ "oxlint": "^1.89.0",
48
+ "typescript": "~7.0.2",
49
+ "vite": "^8.3.5"
50
+ }
51
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#0a0a0a"/><text x="16" y="22" font-family="Inter,system-ui,sans-serif" font-size="17" font-weight="700" fill="#fff" text-anchor="middle">f</text></svg>
@@ -0,0 +1,2 @@
1
+ User-agent: *
2
+ Disallow: /
@@ -0,0 +1,50 @@
1
+ import type { AppConfig, AuthMode } from '@/core/config/types'
2
+
3
+ /** This panel's settings. The company name and logo come from the setup wizard and Settings. */
4
+ export const appConfig = {
5
+ name: 'Admin',
6
+ /** A preset key from src/core/theme/brand.ts, or a custom { color, foreground } pair. */
7
+ brand: 'indigo',
8
+
9
+ workspaces: [{ id: 'main', name: 'Admin' }],
10
+
11
+ /** Sidebar group order; labels are translation keys. Modules attach nav items to these ids. */
12
+ navGroups: [
13
+ { id: 'overview', label: 'common:nav.groups.overview' },
14
+ { id: 'catalog', label: 'common:nav.groups.catalog' },
15
+ { id: 'content', label: 'common:nav.groups.content' },
16
+ { id: 'system', label: 'common:nav.groups.system' },
17
+ ],
18
+
19
+ /** Turn modules on/off. `npm run new-module` adds them here. */
20
+ modules: {
21
+ // fp:modules
22
+ },
23
+
24
+ /** No marketing page: `/` opens the panel. */
25
+ landing: false,
26
+
27
+ /** Built-in areas, off until a project needs them. Setting one to true adds its pages and menu items. */
28
+ features: {
29
+ notifications: false,
30
+ users: false,
31
+ roles: false,
32
+ securityPolicies: false,
33
+ modules: false,
34
+ integrations: false,
35
+ auditLog: false,
36
+ dangerZone: false,
37
+ },
38
+
39
+ /** Default ISO 4217 currency for the setup wizard. */
40
+ currency: 'USD',
41
+
42
+ /** Real REST API base URL. */
43
+ apiBaseUrl: import.meta.env.VITE_API_URL ?? '/api',
44
+
45
+ /** mock (no backend, data stays in this browser) | bearer (JWT) | cookie (Laravel Sanctum). See BACKEND.md. */
46
+ auth: {
47
+ mode: (import.meta.env.VITE_AUTH_MODE ?? 'mock') as AuthMode,
48
+ csrfUrl: import.meta.env.VITE_CSRF_URL ?? '/sanctum/csrf-cookie',
49
+ },
50
+ } satisfies AppConfig
@@ -0,0 +1,24 @@
1
+ import type { Permission } from '@/core/auth/permissions'
2
+
3
+ /**
4
+ * Project-specific access rules. The Owner and Admin automatically get every
5
+ * custom permission; other roles get what is listed under `permissions`.
6
+ * Labels live here (EN + TR) so core translation files stay untouched.
7
+ *
8
+ * Example:
9
+ * customPermissions = [{ id: 'orders.refund', label: { en: 'Refund orders', tr: 'Sipariş iade et' } }] as const
10
+ * customRoles = {
11
+ * support: {
12
+ * label: { en: 'Support', tr: 'Destek' },
13
+ * description: { en: 'Handles orders and refunds.', tr: 'Siparişleri ve iadeleri yönetir.' },
14
+ * permissions: ['dashboard.view', 'orders.view', 'orders.refund'],
15
+ * },
16
+ * }
17
+ */
18
+ export type LocalizedText = { en: string; tr: string }
19
+
20
+ export type CustomRole = { label: LocalizedText; description: LocalizedText; permissions: readonly Permission[] }
21
+
22
+ export const customPermissions = [] as const satisfies readonly { id: string; label: LocalizedText }[]
23
+
24
+ export const customRoles = {} satisfies Record<string, CustomRole>
@@ -0,0 +1,181 @@
1
+ import * as React from "react"
2
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ import { Button } from "@/components/ui/button"
6
+
7
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
8
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
9
+ }
10
+
11
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
12
+ return (
13
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
14
+ )
15
+ }
16
+
17
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
18
+ return (
19
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
20
+ )
21
+ }
22
+
23
+ function AlertDialogOverlay({
24
+ className,
25
+ ...props
26
+ }: AlertDialogPrimitive.Backdrop.Props) {
27
+ return (
28
+ <AlertDialogPrimitive.Backdrop
29
+ data-slot="alert-dialog-overlay"
30
+ className={cn(
31
+ "fixed inset-0 isolate z-50 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function AlertDialogContent({
40
+ className,
41
+ size = "default",
42
+ ...props
43
+ }: AlertDialogPrimitive.Popup.Props & {
44
+ size?: "default" | "sm"
45
+ }) {
46
+ return (
47
+ <AlertDialogPortal>
48
+ <AlertDialogOverlay />
49
+ <AlertDialogPrimitive.Popup
50
+ data-slot="alert-dialog-content"
51
+ data-size={size}
52
+ className={cn(
53
+ "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-4xl bg-popover p-6 text-popover-foreground shadow-(--shadow-dialog) ring-1 ring-border duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md 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",
54
+ className
55
+ )}
56
+ {...props}
57
+ />
58
+ </AlertDialogPortal>
59
+ )
60
+ }
61
+
62
+ function AlertDialogHeader({
63
+ className,
64
+ ...props
65
+ }: React.ComponentProps<"div">) {
66
+ return (
67
+ <div
68
+ data-slot="alert-dialog-header"
69
+ className={cn(
70
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
71
+ className
72
+ )}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ function AlertDialogFooter({
79
+ className,
80
+ ...props
81
+ }: React.ComponentProps<"div">) {
82
+ return (
83
+ <div
84
+ data-slot="alert-dialog-footer"
85
+ className={cn(
86
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
87
+ className
88
+ )}
89
+ {...props}
90
+ />
91
+ )
92
+ }
93
+
94
+ function AlertDialogMedia({
95
+ className,
96
+ ...props
97
+ }: React.ComponentProps<"div">) {
98
+ return (
99
+ <div
100
+ data-slot="alert-dialog-media"
101
+ className={cn(
102
+ "mb-2.5 inline-flex size-12 items-center justify-center rounded-lg bg-fill-2 sm:group-data-[size=default]/alert-dialog-content:row-span-2 sm:group-data-[size=default]/alert-dialog-content:mb-0 *:[svg:not([class*='size-'])]:size-5",
103
+ className
104
+ )}
105
+ {...props}
106
+ />
107
+ )
108
+ }
109
+
110
+ function AlertDialogTitle({
111
+ className,
112
+ ...props
113
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
114
+ return (
115
+ <AlertDialogPrimitive.Title
116
+ data-slot="alert-dialog-title"
117
+ className={cn(
118
+ "font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ )
124
+ }
125
+
126
+ function AlertDialogDescription({
127
+ className,
128
+ ...props
129
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
130
+ return (
131
+ <AlertDialogPrimitive.Description
132
+ data-slot="alert-dialog-description"
133
+ className={cn(
134
+ "text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
135
+ className
136
+ )}
137
+ {...props}
138
+ />
139
+ )
140
+ }
141
+
142
+ function AlertDialogAction({
143
+ className,
144
+ ...props
145
+ }: React.ComponentProps<typeof Button>) {
146
+ return (
147
+ <Button data-slot="alert-dialog-action" className={className} {...props} />
148
+ )
149
+ }
150
+
151
+ function AlertDialogCancel({
152
+ className,
153
+ variant = "outline",
154
+ size = "default",
155
+ ...props
156
+ }: AlertDialogPrimitive.Close.Props &
157
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
158
+ return (
159
+ <AlertDialogPrimitive.Close
160
+ data-slot="alert-dialog-cancel"
161
+ className={className}
162
+ render={<Button variant={variant} size={size} />}
163
+ {...props}
164
+ />
165
+ )
166
+ }
167
+
168
+ export {
169
+ AlertDialog,
170
+ AlertDialogAction,
171
+ AlertDialogCancel,
172
+ AlertDialogContent,
173
+ AlertDialogDescription,
174
+ AlertDialogFooter,
175
+ AlertDialogHeader,
176
+ AlertDialogMedia,
177
+ AlertDialogOverlay,
178
+ AlertDialogPortal,
179
+ AlertDialogTitle,
180
+ AlertDialogTrigger,
181
+ }
@@ -0,0 +1,116 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function Avatar({
8
+ className,
9
+ size = "default",
10
+ shape = "round",
11
+ ...props
12
+ }: AvatarPrimitive.Root.Props & {
13
+ size?: "default" | "sm" | "lg" | "xs" | "xl"
14
+ shape?: "round" | "square"
15
+ }) {
16
+ return (
17
+ <AvatarPrimitive.Root
18
+ data-slot="avatar"
19
+ data-size={size}
20
+ data-shape={shape}
21
+ className={cn(
22
+ "group/avatar relative flex size-8 shrink-0 rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
23
+ "data-[shape=square]:rounded-md data-[shape=square]:data-[size=lg]:rounded-lg data-[shape=square]:data-[size=sm]:rounded-sm data-[size=xl]:size-12 data-[shape=square]:data-[size=xl]:rounded-lg data-[size=xs]:size-5 data-[shape=square]:data-[size=xs]:rounded-sm",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
32
+ return (
33
+ <AvatarPrimitive.Image
34
+ data-slot="avatar-image"
35
+ className={cn(
36
+ "aspect-square size-full rounded-[inherit] object-cover",
37
+ className
38
+ )}
39
+ {...props}
40
+ />
41
+ )
42
+ }
43
+
44
+ function AvatarFallback({
45
+ className,
46
+ ...props
47
+ }: AvatarPrimitive.Fallback.Props) {
48
+ return (
49
+ <AvatarPrimitive.Fallback
50
+ data-slot="avatar-fallback"
51
+ className={cn(
52
+ "flex size-full items-center justify-center rounded-[inherit] bg-fill-2 text-sm text-muted-foreground group-data-[size=sm]/avatar:text-2xs group-data-[size=xs]/avatar:text-2xs",
53
+ "[&>svg]:size-4 group-data-[size=lg]/avatar:[&>svg]:size-5 group-data-[size=xl]/avatar:[&>svg]:size-5 group-data-[size=xs]/avatar:[&>svg]:size-3",
54
+ className
55
+ )}
56
+ {...props}
57
+ />
58
+ )
59
+ }
60
+
61
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
62
+ return (
63
+ <span
64
+ data-slot="avatar-badge"
65
+ className={cn(
66
+ "absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
67
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
68
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
69
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
70
+ "group-data-[size=xs]/avatar:size-2 group-data-[size=xs]/avatar:ring-1 group-data-[size=xs]/avatar:[&>svg]:hidden",
71
+ "group-data-[size=xl]/avatar:size-3 group-data-[size=xl]/avatar:[&>svg]:size-2",
72
+ className
73
+ )}
74
+ {...props}
75
+ />
76
+ )
77
+ }
78
+
79
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
80
+ return (
81
+ <div
82
+ data-slot="avatar-group"
83
+ className={cn(
84
+ "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
85
+ className
86
+ )}
87
+ {...props}
88
+ />
89
+ )
90
+ }
91
+
92
+ function AvatarGroupCount({
93
+ className,
94
+ ...props
95
+ }: React.ComponentProps<"div">) {
96
+ return (
97
+ <div
98
+ data-slot="avatar-group-count"
99
+ className={cn(
100
+ "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
101
+ "group-has-data-[size=sm]/avatar-group:text-2xs group-has-data-[size=xl]/avatar-group:size-12 group-has-data-[size=xs]/avatar-group:size-5 group-has-data-[size=xs]/avatar-group:text-2xs group-has-data-[size=xl]/avatar-group:[&>svg]:size-5 group-has-data-[size=xs]/avatar-group:[&>svg]:size-3",
102
+ className
103
+ )}
104
+ {...props}
105
+ />
106
+ )
107
+ }
108
+
109
+ export {
110
+ Avatar,
111
+ AvatarImage,
112
+ AvatarFallback,
113
+ AvatarGroup,
114
+ AvatarGroupCount,
115
+ AvatarBadge,
116
+ }
@@ -0,0 +1,57 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props"
2
+ import { useRender } from "@base-ui/react/use-render"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full py-0.5 text-2xs font-medium whitespace-nowrap outline-1 -outline-offset-1 outline-transparent transition-all outline-solid focus-visible:ring-3 focus-visible:ring-ring/30 focus-visible:outline-ring aria-invalid:ring-destructive/20 aria-invalid:outline-destructive dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
12
+ secondary: "bg-fill-2 text-foreground",
13
+ destructive:
14
+ "bg-destructive/10 text-destructive-foreground focus-visible:ring-destructive/20 focus-visible:outline-destructive/50 [a]:hover:bg-destructive/15",
15
+ outline:
16
+ "bg-control text-foreground outline-1 -outline-offset-1 outline-border [a]:hover:bg-fill-2",
17
+ ghost: "text-foreground hover:bg-fill-2",
18
+ link: "text-primary underline underline-offset-4",
19
+ },
20
+ size: {
21
+ default:
22
+ "h-5 min-w-5 px-2 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
23
+ lg: "h-6 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&>svg]:size-4!",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ size: "default",
29
+ },
30
+ }
31
+ )
32
+
33
+ function Badge({
34
+ className,
35
+ variant = "default",
36
+ size = "default",
37
+ render,
38
+ ...props
39
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
40
+ return useRender({
41
+ defaultTagName: "span",
42
+ props: mergeProps<"span">(
43
+ {
44
+ className: cn(badgeVariants({ variant, size }), className),
45
+ },
46
+ props
47
+ ),
48
+ render,
49
+ state: {
50
+ slot: "badge",
51
+ variant,
52
+ size,
53
+ },
54
+ })
55
+ }
56
+
57
+ export { Badge, badgeVariants }
@@ -0,0 +1,130 @@
1
+ import * as React from "react"
2
+ import { mergeProps } from "@base-ui/react/merge-props"
3
+ import { useRender } from "@base-ui/react/use-render"
4
+ import { cn } from "@/lib/utils"
5
+ import { useTranslation } from "react-i18next"
6
+ import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
7
+
8
+ function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
9
+ const { t } = useTranslation()
10
+ return (
11
+ <nav
12
+ aria-label={t("shell.breadcrumb")}
13
+ data-slot="breadcrumb"
14
+ className={className}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
21
+ return (
22
+ <ol
23
+ data-slot="breadcrumb-list"
24
+ className={cn(
25
+ "flex flex-wrap items-center gap-2 text-sm wrap-break-word text-muted-foreground",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
34
+ return (
35
+ <li
36
+ data-slot="breadcrumb-item"
37
+ className={cn(
38
+ "inline-flex items-center gap-1.5 has-data-[slot=breadcrumb-ellipsis]:-my-0.5 [&_svg:not([class*='size-'])]:size-4",
39
+ className
40
+ )}
41
+ {...props}
42
+ />
43
+ )
44
+ }
45
+
46
+ function BreadcrumbLink({
47
+ className,
48
+ render,
49
+ ...props
50
+ }: useRender.ComponentProps<"a">) {
51
+ return useRender({
52
+ defaultTagName: "a",
53
+ props: mergeProps<"a">(
54
+ {
55
+ className: cn("transition-colors hover:text-foreground", className),
56
+ },
57
+ props
58
+ ),
59
+ render,
60
+ state: {
61
+ slot: "breadcrumb-link",
62
+ },
63
+ })
64
+ }
65
+
66
+ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
67
+ return (
68
+ <span
69
+ data-slot="breadcrumb-page"
70
+ role="link"
71
+ aria-disabled="true"
72
+ aria-current="page"
73
+ className={cn("font-normal text-foreground", className)}
74
+ {...props}
75
+ />
76
+ )
77
+ }
78
+
79
+ function BreadcrumbSeparator({
80
+ children,
81
+ className,
82
+ ...props
83
+ }: React.ComponentProps<"li">) {
84
+ return (
85
+ <li
86
+ data-slot="breadcrumb-separator"
87
+ role="presentation"
88
+ aria-hidden="true"
89
+ className={cn(
90
+ "inline-flex min-w-4 items-center justify-center text-2xs font-medium [&>svg]:size-4",
91
+ className
92
+ )}
93
+ {...props}
94
+ >
95
+ {children ?? <ChevronRightIcon className="rtl:-scale-x-100" />}
96
+ </li>
97
+ )
98
+ }
99
+
100
+ function BreadcrumbEllipsis({
101
+ className,
102
+ ...props
103
+ }: React.ComponentProps<"span">) {
104
+ const { t } = useTranslation()
105
+ return (
106
+ <span
107
+ data-slot="breadcrumb-ellipsis"
108
+ role="presentation"
109
+ aria-hidden="true"
110
+ className={cn(
111
+ "flex size-6 items-center justify-center text-muted-foreground transition-colors group-hover/button:text-foreground in-aria-expanded:text-foreground",
112
+ className
113
+ )}
114
+ {...props}
115
+ >
116
+ <MoreHorizontalIcon className="size-4" />
117
+ <span className="sr-only">{t("a11y.more")}</span>
118
+ </span>
119
+ )
120
+ }
121
+
122
+ export {
123
+ Breadcrumb,
124
+ BreadcrumbList,
125
+ BreadcrumbItem,
126
+ BreadcrumbLink,
127
+ BreadcrumbPage,
128
+ BreadcrumbSeparator,
129
+ BreadcrumbEllipsis,
130
+ }
@@ -0,0 +1,58 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const buttonVariants = cva(
6
+ "group/button inline-flex shrink-0 items-center justify-center text-xs font-medium whitespace-nowrap outline-1 -outline-offset-1 outline-transparent transition-all outline-solid select-none focus-visible:ring-2 focus-visible:ring-ring/30 focus-visible:outline-ring active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-2 aria-invalid:ring-destructive/20 aria-invalid:outline-destructive dark:aria-invalid:ring-destructive/40 dark:aria-invalid:outline-destructive/50 [&_[data-badge]]:inline-flex [&_[data-badge]]:h-5 [&_[data-badge]]:min-w-5 [&_[data-badge]]:shrink-0 [&_[data-badge]]:items-center [&_[data-badge]]:justify-center [&_[data-badge]]:rounded-xs [&_[data-badge]]:px-1 [&_[data-badge]]:text-2xs [&_[data-badge]]:font-medium [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ "bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted-foreground [&_[data-badge]]:bg-primary-foreground/20",
12
+ outline:
13
+ "bg-control text-foreground outline-control-border hover:bg-control-hover disabled:bg-control-hover aria-expanded:bg-control-hover [&_[data-badge]]:bg-fill-2",
14
+ secondary:
15
+ "bg-fill-2 text-foreground hover:bg-fill-3 disabled:bg-fill-2 aria-expanded:bg-fill-3 [&_[data-badge]]:bg-fill-2",
16
+ ghost:
17
+ "text-foreground hover:bg-fill-2 aria-expanded:bg-fill-2 [&_[data-badge]]:bg-fill-2",
18
+ destructive:
19
+ "text-destructive-foreground outline-destructive/50 hover:bg-fill-1 focus-visible:ring-destructive/20 focus-visible:outline-destructive disabled:bg-fill-1 aria-expanded:bg-fill-1 [&_[data-badge]]:bg-destructive/10",
20
+ link: "text-primary underline-offset-4 hover:underline [&_[data-badge]]:bg-primary/10",
21
+ },
22
+ size: {
23
+ default:
24
+ "h-8 gap-1.5 rounded-lg px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
25
+ xs: "h-6 gap-1 rounded-md px-2 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_[data-badge]]:h-4 [&_[data-badge]]:min-w-4 [&_[data-badge]]:px-0.5 [&_svg:not([class*='size-'])]:size-3.5",
26
+ sm: "h-7 gap-1.5 rounded-md px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
27
+ lg: "h-9 gap-1.5 rounded-lg px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
28
+ xl: "h-10 gap-2 rounded-lg px-3.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
29
+ icon: "size-8 rounded-lg",
30
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3.5",
31
+ "icon-sm": "size-7 rounded-md",
32
+ "icon-lg": "size-9 rounded-lg",
33
+ "icon-xl": "size-10 rounded-lg",
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ variant: "default",
38
+ size: "default",
39
+ },
40
+ }
41
+ )
42
+
43
+ function Button({
44
+ className,
45
+ variant = "default",
46
+ size = "default",
47
+ ...props
48
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
49
+ return (
50
+ <ButtonPrimitive
51
+ data-slot="button"
52
+ className={cn(buttonVariants({ variant, size, className }))}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ export { Button, buttonVariants }