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,263 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ import { Button } from "@/components/ui/button"
6
+ import { Input } from "@/components/ui/input"
7
+ import { Textarea } from "@/components/ui/textarea"
8
+
9
+ function InputGroup({
10
+ className,
11
+ size = "default",
12
+ ...props
13
+ }: React.ComponentProps<"div"> & { size?: "default" | "lg" | "xl" }) {
14
+ return (
15
+ <div
16
+ data-slot="input-group"
17
+ data-size={size}
18
+ role="group"
19
+ className={cn(
20
+ "group/input-group relative flex h-(--ig-h) w-full min-w-0 items-center rounded-(--ig-radius) bg-field outline-1 -outline-offset-1 outline-field-border transition-[color,box-shadow,outline-color] duration-200 outline-solid [--ig-block:--spacing(1.5)] [--ig-body:--spacing(2.5)] [--ig-h:--spacing(8)] [--ig-radius:var(--radius-lg)] has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot=input-group-control]:focus-visible]:outline-ring in-data-[slot=combobox-content]:has-[[data-slot=input-group-control]:focus-visible]:ring-0 in-data-[slot=combobox-content]:has-[[data-slot=input-group-control]:focus-visible]:outline-field-border has-[[data-slot][aria-invalid=true]]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:outline-destructive has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-wrap has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-wrap has-[>textarea]:h-auto data-[size=lg]:[--ig-block:--spacing(2)] data-[size=lg]:[--ig-body:--spacing(3)] data-[size=lg]:[--ig-h:--spacing(9)] data-[size=xl]:[--ig-block:--spacing(2)] data-[size=xl]:[--ig-body:--spacing(3)] data-[size=xl]:[--ig-h:--spacing(10)] dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 dark:has-[[data-slot][aria-invalid=true]]:outline-destructive/50 has-[>[data-align=inline-end]>svg:only-child]:[&>[data-slot=input-group-control]]:pr-2 has-[>[data-align=inline-start]>svg:only-child]:[&>[data-slot=input-group-control]]:pl-2",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ const inputGroupAddonVariants = cva(
29
+ "flex h-auto cursor-text items-center justify-center gap-2 text-xs font-medium text-muted-foreground select-none [--ig-item:--spacing(4)] [--ig-pad:calc((var(--ig-h)-var(--ig-item))/2)] group-data-[disabled=true]/input-group:opacity-50 has-[>[data-size=icon-sm]]:[--ig-item:--spacing(7)] has-[>[data-size=icon-xs]]:[--ig-item:--spacing(6)] has-[>[data-size=sm]]:[--ig-item:--spacing(7)] has-[>[data-size=text]]:[--ig-item:--spacing(5)] has-[>[data-size=xs]]:[--ig-item:--spacing(6)] has-[>button.h-10:not([data-size])]:[--ig-item:--spacing(10)] has-[>button.h-7:not([data-size])]:[--ig-item:--spacing(7)] has-[>button.h-8:not([data-size])]:[--ig-item:--spacing(8)] has-[>button.h-9:not([data-size])]:[--ig-item:--spacing(9)] has-[>button.size-10:not([data-size])]:[--ig-item:--spacing(10)] has-[>button.size-7:not([data-size])]:[--ig-item:--spacing(7)] has-[>button.size-8:not([data-size])]:[--ig-item:--spacing(8)] has-[>button.size-9:not([data-size])]:[--ig-item:--spacing(9)] has-[>button:not([data-size])]:[--ig-item:--spacing(6)] has-[>kbd]:[--ig-item:--spacing(5)] [&:not(:has(>:not([data-slot=input-group-text])))]:[--ig-pad:max(calc((var(--ig-h)-var(--ig-item))/2),--spacing(2.5))] [&>svg:not([class*='size-'])]:size-4",
30
+ {
31
+ variants: {
32
+ align: {
33
+ "inline-start":
34
+ "order-first pl-(--ig-pad) has-[>svg:only-child]:pl-(--ig-body)",
35
+ "inline-end":
36
+ "order-last pr-(--ig-pad) has-[>svg:only-child]:pr-(--ig-body)",
37
+ "block-start":
38
+ "order-first w-full flex-none justify-start px-(--ig-body) pt-(--ig-block) pb-0",
39
+ "block-end":
40
+ "order-last w-full flex-none justify-start px-(--ig-body) pt-0 pb-(--ig-block)",
41
+ },
42
+ divider: {
43
+ true: "border-border",
44
+ false: "",
45
+ },
46
+ },
47
+ compoundVariants: [
48
+ {
49
+ align: "inline-start",
50
+ divider: true,
51
+ class: "min-h-(--ig-h) border-r pr-[calc(var(--ig-pad)-1px)]",
52
+ },
53
+ {
54
+ align: "inline-end",
55
+ divider: true,
56
+ class: "min-h-(--ig-h) border-l pl-[calc(var(--ig-pad)-1px)]",
57
+ },
58
+ {
59
+ align: "block-start",
60
+ divider: true,
61
+ class: "border-b pb-[calc(var(--ig-block)-1px)]",
62
+ },
63
+ {
64
+ align: "block-end",
65
+ divider: true,
66
+ class: "border-t pt-[calc(var(--ig-block)-1px)]",
67
+ },
68
+ ],
69
+ defaultVariants: {
70
+ align: "inline-start",
71
+ divider: false,
72
+ },
73
+ }
74
+ )
75
+
76
+ function InputGroupAddon({
77
+ className,
78
+ align = "inline-start",
79
+ divider = false,
80
+ style,
81
+ ref: refProp,
82
+ onClick,
83
+ ...props
84
+ }: React.ComponentProps<"div"> &
85
+ VariantProps<typeof inputGroupAddonVariants> & {
86
+ style?: React.CSSProperties & { "--ig-item"?: string | number }
87
+ }) {
88
+ const ref = React.useRef<HTMLDivElement>(null)
89
+ const setRef = React.useCallback(
90
+ (node: HTMLDivElement | null) => {
91
+ ref.current = node
92
+ if (typeof refProp === "function") return refProp(node)
93
+ if (refProp) refProp.current = node
94
+ },
95
+ [refProp]
96
+ )
97
+ const customItem = style?.["--ig-item"]
98
+
99
+ React.useEffect(() => {
100
+ const el = ref.current
101
+ if (
102
+ !el ||
103
+ customItem !== undefined ||
104
+ align === "block-start" ||
105
+ align === "block-end"
106
+ )
107
+ return
108
+ const measure = () => {
109
+ let max = 0
110
+ for (const child of el.children) {
111
+ if (child.matches("[data-slot=input-group-text]")) continue
112
+ const h =
113
+ child instanceof HTMLElement
114
+ ? child.offsetHeight
115
+ : child.clientHeight || child.getBoundingClientRect().height
116
+ if (h > max) max = h
117
+ }
118
+ if (max > 0) el.style.setProperty("--ig-item", `${max}px`)
119
+ else el.style.removeProperty("--ig-item")
120
+ }
121
+ if (typeof ResizeObserver === "undefined") {
122
+ measure()
123
+ return
124
+ }
125
+ const resize = new ResizeObserver(measure)
126
+ const observeChildren = () => {
127
+ resize.disconnect()
128
+ for (const child of el.children) resize.observe(child)
129
+ measure()
130
+ }
131
+ const mutation = new MutationObserver(observeChildren)
132
+ mutation.observe(el, { childList: true })
133
+ observeChildren()
134
+ return () => {
135
+ resize.disconnect()
136
+ mutation.disconnect()
137
+ }
138
+ }, [align, customItem])
139
+
140
+ return (
141
+ <div
142
+ ref={setRef}
143
+ role="group"
144
+ data-slot="input-group-addon"
145
+ data-align={align}
146
+ data-divider={divider || undefined}
147
+ style={style}
148
+ className={cn(inputGroupAddonVariants({ align, divider }), className)}
149
+ onClick={(e) => {
150
+ onClick?.(e)
151
+ if (
152
+ e.defaultPrevented ||
153
+ (e.target as Element).closest(
154
+ "a, button, input, select, textarea, [role=checkbox], [role=switch]"
155
+ )
156
+ ) {
157
+ return
158
+ }
159
+ const group = e.currentTarget.parentElement
160
+ if (!group) return
161
+ const control =
162
+ group.querySelector<HTMLElement>("[data-slot=input-group-control]") ??
163
+ group.querySelector<HTMLElement>(
164
+ "input:not([type=hidden],[aria-hidden=true]), textarea"
165
+ )
166
+ control?.focus()
167
+ }}
168
+ {...props}
169
+ />
170
+ )
171
+ }
172
+
173
+ const inputGroupButtonVariants = cva("flex items-center", {
174
+ variants: {
175
+ size: {
176
+ 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 [&>svg:not([class*='size-'])]:size-3.5",
177
+ 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",
178
+ "icon-xs":
179
+ "size-6 rounded-md p-0 has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 has-[>svg]:p-0 [&>svg:not([class*='size-'])]:size-3.5",
180
+ "icon-sm":
181
+ "size-7 rounded-md p-0 has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 has-[>svg]:p-0",
182
+ text: "h-auto gap-1 rounded-xs p-0 text-xs hover:bg-transparent hover:opacity-70 disabled:text-foreground has-data-[icon=inline-end]:pr-0 has-data-[icon=inline-start]:pl-0 aria-expanded:bg-transparent [&>svg:not([class*='size-'])]:size-3.5",
183
+ },
184
+ },
185
+ defaultVariants: {
186
+ size: "xs",
187
+ },
188
+ })
189
+
190
+ function InputGroupButton({
191
+ className,
192
+ type = "button",
193
+ variant = "ghost",
194
+ size = "xs",
195
+ ...props
196
+ }: Omit<React.ComponentProps<typeof Button>, "size" | "type"> &
197
+ VariantProps<typeof inputGroupButtonVariants> & {
198
+ type?: "button" | "submit" | "reset"
199
+ }) {
200
+ return (
201
+ <Button
202
+ type={type}
203
+ data-size={size}
204
+ variant={variant}
205
+ className={cn(inputGroupButtonVariants({ size }), className)}
206
+ {...props}
207
+ />
208
+ )
209
+ }
210
+
211
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
212
+ return (
213
+ <span
214
+ data-slot="input-group-text"
215
+ className={cn(
216
+ "flex items-center gap-2 text-xs text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
217
+ className
218
+ )}
219
+ {...props}
220
+ />
221
+ )
222
+ }
223
+
224
+ function InputGroupInput({
225
+ className,
226
+ ...props
227
+ }: React.ComponentProps<"input">) {
228
+ return (
229
+ <Input
230
+ data-slot="input-group-control"
231
+ className={cn(
232
+ "flex-1 rounded-none bg-transparent px-(--ig-body) shadow-none ring-0 outline-0 group-data-[size=lg]/input-group:h-9 group-data-[size=xl]/input-group:h-10 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
233
+ className
234
+ )}
235
+ {...props}
236
+ />
237
+ )
238
+ }
239
+
240
+ function InputGroupTextarea({
241
+ className,
242
+ ...props
243
+ }: React.ComponentProps<"textarea">) {
244
+ return (
245
+ <Textarea
246
+ data-slot="input-group-control"
247
+ className={cn(
248
+ "flex-1 resize-none rounded-none bg-transparent px-(--ig-body) py-2.5 shadow-none ring-0 outline-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
249
+ className
250
+ )}
251
+ {...props}
252
+ />
253
+ )
254
+ }
255
+
256
+ export {
257
+ InputGroup,
258
+ InputGroupAddon,
259
+ InputGroupButton,
260
+ InputGroupText,
261
+ InputGroupInput,
262
+ InputGroupTextarea,
263
+ }
@@ -0,0 +1,45 @@
1
+ import * as React from "react"
2
+ import { Input as InputPrimitive } from "@base-ui/react/input"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const inputVariants = cva(
7
+ "w-full min-w-0 rounded-lg bg-field py-1 text-xs outline-1 -outline-offset-1 outline-field-border transition-[color,box-shadow,outline-color] duration-200 outline-solid file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring/30 focus-visible:outline-ring disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:text-destructive-foreground 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",
8
+ {
9
+ variants: {
10
+ size: {
11
+ default: "h-8 px-2.5",
12
+ lg: "h-9 px-3",
13
+ xl: "h-10 px-3",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ size: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ type InputSize = NonNullable<VariantProps<typeof inputVariants>["size"]>
23
+
24
+ function Input({
25
+ className,
26
+ type,
27
+ size,
28
+ ...props
29
+ }: Omit<React.ComponentProps<"input">, "size"> & {
30
+ size?: number | InputSize
31
+ }) {
32
+ const variant = typeof size === "string" ? size : "default"
33
+ return (
34
+ <InputPrimitive
35
+ type={type}
36
+ size={typeof size === "number" ? size : undefined}
37
+ data-slot="input"
38
+ data-size={variant}
39
+ className={cn(inputVariants({ size: variant }), className)}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+
45
+ export { Input }
@@ -0,0 +1,31 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Kbd({
4
+ className,
5
+ size = "default",
6
+ ...props
7
+ }: React.ComponentProps<"kbd"> & { size?: "default" | "sm" }) {
8
+ return (
9
+ <kbd
10
+ data-slot="kbd"
11
+ data-size={size}
12
+ className={cn(
13
+ "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-xs bg-fill-2 px-1 font-sans text-2xs font-medium text-muted-foreground select-none in-data-[slot=badge]:h-4 in-data-[slot=badge]:min-w-4 in-data-[slot=badge]:px-0.5 in-data-[slot=tooltip-content]:h-4 in-data-[slot=tooltip-content]:min-w-4 in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:px-0.5 in-data-[slot=tooltip-content]:text-background data-[size=sm]:h-4 data-[size=sm]:min-w-4 data-[size=sm]:px-0.5 dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
22
+ return (
23
+ <kbd
24
+ data-slot="kbd-group"
25
+ className={cn("inline-flex items-center gap-0.5", className)}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ export { Kbd, KbdGroup }
@@ -0,0 +1,17 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ function Label({ className, ...props }: React.ComponentProps<"label">) {
5
+ return (
6
+ <label
7
+ data-slot="label"
8
+ className={cn(
9
+ "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 peer-data-disabled:cursor-not-allowed peer-data-disabled:opacity-50",
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ export { Label }
@@ -0,0 +1,134 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+ import { useTranslation } from "react-i18next"
4
+
5
+ import { Button } from "@/components/ui/button"
6
+ import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
7
+
8
+ function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
9
+ const { t } = useTranslation()
10
+ return (
11
+ <nav
12
+ role="navigation"
13
+ aria-label={t("a11y.pagination")}
14
+ data-slot="pagination"
15
+ className={cn("mx-auto flex w-full justify-center", className)}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function PaginationContent({
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<"ul">) {
25
+ return (
26
+ <ul
27
+ data-slot="pagination-content"
28
+ className={cn("flex items-center gap-1", className)}
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ function PaginationItem({ ...props }: React.ComponentProps<"li">) {
35
+ return <li data-slot="pagination-item" {...props} />
36
+ }
37
+
38
+ type PaginationLinkProps = {
39
+ isActive?: boolean
40
+ } & Pick<React.ComponentProps<typeof Button>, "size"> &
41
+ React.ComponentProps<"a">
42
+
43
+ function PaginationLink({
44
+ className,
45
+ isActive,
46
+ size = "icon",
47
+ ...props
48
+ }: PaginationLinkProps) {
49
+ return (
50
+ <Button
51
+ variant={isActive ? "outline" : "ghost"}
52
+ size={size}
53
+ className={className}
54
+ nativeButton={false}
55
+ render={
56
+ <a
57
+ aria-current={isActive ? "page" : undefined}
58
+ data-slot="pagination-link"
59
+ data-active={isActive}
60
+ {...props}
61
+ />
62
+ }
63
+ />
64
+ )
65
+ }
66
+
67
+ function PaginationPrevious({
68
+ className,
69
+ text,
70
+ ...props
71
+ }: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
72
+ const { t } = useTranslation()
73
+ return (
74
+ <PaginationLink
75
+ aria-label={t("a11y.previousPage")}
76
+ size="default"
77
+ className={className}
78
+ {...props}
79
+ >
80
+ <ChevronLeftIcon data-icon="inline-start" className="rtl:-scale-x-100" />
81
+ <span className="hidden sm:block">{text ?? t("a11y.previous")}</span>
82
+ </PaginationLink>
83
+ )
84
+ }
85
+
86
+ function PaginationNext({
87
+ className,
88
+ text,
89
+ ...props
90
+ }: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
91
+ const { t } = useTranslation()
92
+ return (
93
+ <PaginationLink
94
+ aria-label={t("a11y.nextPage")}
95
+ size="default"
96
+ className={className}
97
+ {...props}
98
+ >
99
+ <span className="hidden sm:block">{text ?? t("a11y.next")}</span>
100
+ <ChevronRightIcon data-icon="inline-end" className="rtl:-scale-x-100" />
101
+ </PaginationLink>
102
+ )
103
+ }
104
+
105
+ function PaginationEllipsis({
106
+ className,
107
+ ...props
108
+ }: React.ComponentProps<"span">) {
109
+ const { t } = useTranslation()
110
+ return (
111
+ <span
112
+ aria-hidden
113
+ data-slot="pagination-ellipsis"
114
+ className={cn(
115
+ "flex size-8 items-center justify-center [&_svg:not([class*='size-'])]:size-4",
116
+ className
117
+ )}
118
+ {...props}
119
+ >
120
+ <MoreHorizontalIcon />
121
+ <span className="sr-only">{t("a11y.morePages")}</span>
122
+ </span>
123
+ )
124
+ }
125
+
126
+ export {
127
+ Pagination,
128
+ PaginationContent,
129
+ PaginationEllipsis,
130
+ PaginationItem,
131
+ PaginationLink,
132
+ PaginationNext,
133
+ PaginationPrevious,
134
+ }
@@ -0,0 +1,87 @@
1
+ import * as React from "react"
2
+ import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Popover({ ...props }: PopoverPrimitive.Root.Props) {
6
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />
7
+ }
8
+
9
+ function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
10
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
11
+ }
12
+
13
+ function PopoverContent({
14
+ className,
15
+ align = "center",
16
+ alignOffset = 0,
17
+ side = "bottom",
18
+ sideOffset = 4,
19
+ ...props
20
+ }: PopoverPrimitive.Popup.Props &
21
+ Pick<
22
+ PopoverPrimitive.Positioner.Props,
23
+ "align" | "alignOffset" | "side" | "sideOffset"
24
+ >) {
25
+ return (
26
+ <PopoverPrimitive.Portal>
27
+ <PopoverPrimitive.Positioner
28
+ align={align}
29
+ alignOffset={alignOffset}
30
+ side={side}
31
+ sideOffset={sideOffset}
32
+ className="isolate z-50"
33
+ >
34
+ <PopoverPrimitive.Popup
35
+ data-slot="popover-content"
36
+ className={cn(
37
+ "z-50 flex w-72 origin-(--transform-origin) flex-col gap-3 rounded-14 bg-popover p-4 text-sm text-popover-foreground shadow-(--shadow-popover) ring-1 ring-border 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",
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ </PopoverPrimitive.Positioner>
43
+ </PopoverPrimitive.Portal>
44
+ )
45
+ }
46
+
47
+ function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
48
+ return (
49
+ <div
50
+ data-slot="popover-header"
51
+ className={cn("flex flex-col gap-1 text-sm not-last:mb-1", className)}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
58
+ return (
59
+ <PopoverPrimitive.Title
60
+ data-slot="popover-title"
61
+ className={cn("text-base font-medium", className)}
62
+ {...props}
63
+ />
64
+ )
65
+ }
66
+
67
+ function PopoverDescription({
68
+ className,
69
+ ...props
70
+ }: PopoverPrimitive.Description.Props) {
71
+ return (
72
+ <PopoverPrimitive.Description
73
+ data-slot="popover-description"
74
+ className={cn("text-muted-foreground", className)}
75
+ {...props}
76
+ />
77
+ )
78
+ }
79
+
80
+ export {
81
+ Popover,
82
+ PopoverContent,
83
+ PopoverDescription,
84
+ PopoverHeader,
85
+ PopoverTitle,
86
+ PopoverTrigger,
87
+ }
@@ -0,0 +1,35 @@
1
+ import { Radio as RadioPrimitive } from "@base-ui/react/radio"
2
+ import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group"
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
6
+ return (
7
+ <RadioGroupPrimitive
8
+ data-slot="radio-group"
9
+ className={cn("grid w-full gap-3", className)}
10
+ {...props}
11
+ />
12
+ )
13
+ }
14
+
15
+ function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
16
+ return (
17
+ <RadioPrimitive.Root
18
+ data-slot="radio-group-item"
19
+ className={cn(
20
+ "group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-transparent bg-fill-3 outline-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-checked:text-primary-foreground data-checked:focus-visible:border-primary data-checked:aria-invalid:border-primary dark:data-checked:aria-invalid:border-primary",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ <RadioPrimitive.Indicator
26
+ data-slot="radio-group-indicator"
27
+ className="flex size-4 items-center justify-center"
28
+ >
29
+ <span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
30
+ </RadioPrimitive.Indicator>
31
+ </RadioPrimitive.Root>
32
+ )
33
+ }
34
+
35
+ export { RadioGroup, RadioGroupItem }