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,741 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { mergeProps } from "@base-ui/react/merge-props"
5
+ import { useRender } from "@base-ui/react/use-render"
6
+ import { cva, type VariantProps } from "class-variance-authority"
7
+ import { cn } from "@/lib/utils"
8
+ import { useTranslation } from "react-i18next"
9
+
10
+ import { useIsMobile } from "@/hooks/use-mobile"
11
+ import { Button } from "@/components/ui/button"
12
+ import { Input } from "@/components/ui/input"
13
+ import { Separator } from "@/components/ui/separator"
14
+ import {
15
+ Sheet,
16
+ SheetContent,
17
+ SheetDescription,
18
+ SheetHeader,
19
+ SheetTitle,
20
+ } from "@/components/ui/sheet"
21
+ import { Skeleton } from "@/components/ui/skeleton"
22
+ import {
23
+ Tooltip,
24
+ TooltipContent,
25
+ TooltipTrigger,
26
+ } from "@/components/ui/tooltip"
27
+ import { PanelLeftIcon } from "lucide-react"
28
+
29
+ const SIDEBAR_COOKIE_NAME = "sidebar_state"
30
+ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
31
+ const SIDEBAR_WIDTH = "16.25rem"
32
+ const SIDEBAR_WIDTH_MOBILE = "18rem"
33
+ const SIDEBAR_WIDTH_ICON = "3rem"
34
+ const SIDEBAR_KEYBOARD_SHORTCUT = "b"
35
+
36
+ type SidebarContextProps = {
37
+ state: "expanded" | "collapsed"
38
+ open: boolean
39
+ setOpen: (open: boolean) => void
40
+ openMobile: boolean
41
+ setOpenMobile: (open: boolean) => void
42
+ isMobile: boolean
43
+ toggleSidebar: () => void
44
+ }
45
+
46
+ const SidebarContext = React.createContext<SidebarContextProps | null>(null)
47
+
48
+ function useSidebar() {
49
+ const context = React.useContext(SidebarContext)
50
+ if (!context) {
51
+ throw new Error("useSidebar must be used within a SidebarProvider.")
52
+ }
53
+
54
+ return context
55
+ }
56
+
57
+ function SidebarProvider({
58
+ defaultOpen = true,
59
+ open: openProp,
60
+ onOpenChange: setOpenProp,
61
+ className,
62
+ style,
63
+ children,
64
+ ...props
65
+ }: React.ComponentProps<"div"> & {
66
+ defaultOpen?: boolean
67
+ open?: boolean
68
+ onOpenChange?: (open: boolean) => void
69
+ }) {
70
+ const isMobile = useIsMobile()
71
+ const [openMobile, setOpenMobile] = React.useState(false)
72
+
73
+ // This is the internal state of the sidebar.
74
+ // We use openProp and setOpenProp for control from outside the component.
75
+ const [_open, _setOpen] = React.useState(defaultOpen)
76
+ const open = openProp ?? _open
77
+ const setOpen = React.useCallback(
78
+ (value: boolean | ((value: boolean) => boolean)) => {
79
+ const openState = typeof value === "function" ? value(open) : value
80
+ if (setOpenProp) {
81
+ setOpenProp(openState)
82
+ } else {
83
+ _setOpen(openState)
84
+ }
85
+
86
+ // This sets the cookie to keep the sidebar state.
87
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
88
+ },
89
+ [setOpenProp, open]
90
+ )
91
+
92
+ // Helper to toggle the sidebar.
93
+ const toggleSidebar = React.useCallback(() => {
94
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
95
+ }, [isMobile, setOpen, setOpenMobile])
96
+
97
+ // Adds a keyboard shortcut to toggle the sidebar.
98
+ React.useEffect(() => {
99
+ const handleKeyDown = (event: KeyboardEvent) => {
100
+ if (event.defaultPrevented || event.repeat) return
101
+ if (
102
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
103
+ (event.metaKey || event.ctrlKey)
104
+ ) {
105
+ event.preventDefault()
106
+ toggleSidebar()
107
+ }
108
+ }
109
+
110
+ window.addEventListener("keydown", handleKeyDown)
111
+ return () => window.removeEventListener("keydown", handleKeyDown)
112
+ }, [toggleSidebar])
113
+
114
+ // We add a state so that we can do data-state="expanded" or "collapsed".
115
+ // This makes it easier to style the sidebar with Tailwind classes.
116
+ const state = open ? "expanded" : "collapsed"
117
+
118
+ const contextValue = React.useMemo<SidebarContextProps>(
119
+ () => ({
120
+ state,
121
+ open,
122
+ setOpen,
123
+ isMobile,
124
+ openMobile,
125
+ setOpenMobile,
126
+ toggleSidebar,
127
+ }),
128
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
129
+ )
130
+
131
+ return (
132
+ <SidebarContext.Provider value={contextValue}>
133
+ <div
134
+ data-slot="sidebar-wrapper"
135
+ style={
136
+ {
137
+ "--sidebar-width": SIDEBAR_WIDTH,
138
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
139
+ ...style,
140
+ } as React.CSSProperties
141
+ }
142
+ className={cn(
143
+ "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
144
+ className
145
+ )}
146
+ {...props}
147
+ >
148
+ {children}
149
+ </div>
150
+ </SidebarContext.Provider>
151
+ )
152
+ }
153
+
154
+ function Sidebar({
155
+ side = "left",
156
+ variant = "sidebar",
157
+ collapsible = "offcanvas",
158
+ className,
159
+ children,
160
+ dir,
161
+ ...props
162
+ }: React.ComponentProps<"div"> & {
163
+ side?: "left" | "right"
164
+ variant?: "sidebar" | "floating" | "inset"
165
+ collapsible?: "offcanvas" | "icon" | "none"
166
+ }) {
167
+ const { t } = useTranslation()
168
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
169
+
170
+ if (collapsible === "none") {
171
+ return (
172
+ <div
173
+ data-slot="sidebar"
174
+ className={cn(
175
+ "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
176
+ className
177
+ )}
178
+ {...props}
179
+ >
180
+ {children}
181
+ </div>
182
+ )
183
+ }
184
+
185
+ if (isMobile) {
186
+ return (
187
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
188
+ <SheetContent
189
+ dir={dir}
190
+ data-sidebar="sidebar"
191
+ data-slot="sidebar"
192
+ data-mobile="true"
193
+ className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground data-[side=left]:w-(--sidebar-width) data-[side=right]:w-(--sidebar-width) [&>button]:hidden"
194
+ style={
195
+ {
196
+ "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
197
+ } as React.CSSProperties
198
+ }
199
+ side={side}
200
+ >
201
+ <SheetHeader className="sr-only">
202
+ <SheetTitle>{t("a11y.sidebar")}</SheetTitle>
203
+ <SheetDescription>{t("a11y.sidebarDescription")}</SheetDescription>
204
+ </SheetHeader>
205
+ <div className="flex h-full w-full flex-col">{children}</div>
206
+ </SheetContent>
207
+ </Sheet>
208
+ )
209
+ }
210
+
211
+ return (
212
+ <div
213
+ className="group peer hidden text-sidebar-foreground md:block"
214
+ data-state={state}
215
+ data-collapsible={state === "collapsed" ? collapsible : ""}
216
+ data-variant={variant}
217
+ data-side={side}
218
+ data-slot="sidebar"
219
+ >
220
+ {/* This is what handles the sidebar gap on desktop */}
221
+ <div
222
+ data-slot="sidebar-gap"
223
+ className={cn(
224
+ "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
225
+ "group-data-[collapsible=offcanvas]:w-0",
226
+ "group-data-[side=right]:rotate-180",
227
+ variant === "floating" || variant === "inset"
228
+ ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
229
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
230
+ )}
231
+ />
232
+ <div
233
+ data-slot="sidebar-container"
234
+ data-side={side}
235
+ className={cn(
236
+ "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
237
+ // Adjust the padding for floating and inset variants.
238
+ variant === "floating" || variant === "inset"
239
+ ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
240
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
241
+ className
242
+ )}
243
+ {...props}
244
+ >
245
+ <div
246
+ data-sidebar="sidebar"
247
+ data-slot="sidebar-inner"
248
+ className="flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-2xl group-data-[variant=floating]:shadow-[0_1px_2px_0_oklch(0_0_0/4%)] group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-border"
249
+ >
250
+ {children}
251
+ </div>
252
+ </div>
253
+ </div>
254
+ )
255
+ }
256
+
257
+ function SidebarTrigger({
258
+ className,
259
+ onClick,
260
+ ...props
261
+ }: React.ComponentProps<typeof Button>) {
262
+ const { t } = useTranslation()
263
+ const { toggleSidebar } = useSidebar()
264
+
265
+ return (
266
+ <Button
267
+ data-sidebar="trigger"
268
+ data-slot="sidebar-trigger"
269
+ variant="ghost"
270
+ size="icon"
271
+ className={className}
272
+ onClick={(event) => {
273
+ onClick?.(event)
274
+ toggleSidebar()
275
+ }}
276
+ {...props}
277
+ >
278
+ <PanelLeftIcon className="rtl:-scale-x-100" />
279
+ <span className="sr-only">{t("shell.toggleSidebar")}</span>
280
+ </Button>
281
+ )
282
+ }
283
+
284
+ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
285
+ const { t } = useTranslation()
286
+ const { toggleSidebar } = useSidebar()
287
+
288
+ return (
289
+ <button
290
+ data-sidebar="rail"
291
+ data-slot="sidebar-rail"
292
+ aria-label={t("shell.toggleSidebar")}
293
+ tabIndex={-1}
294
+ onClick={toggleSidebar}
295
+ title={t("shell.toggleSidebar")}
296
+ className={cn(
297
+ "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
298
+ "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
299
+ "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
300
+ "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
301
+ "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
302
+ "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
303
+ className
304
+ )}
305
+ {...props}
306
+ />
307
+ )
308
+ }
309
+
310
+ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
311
+ return (
312
+ <main
313
+ data-slot="sidebar-inset"
314
+ className={cn(
315
+ "relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-2xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
316
+ className
317
+ )}
318
+ {...props}
319
+ />
320
+ )
321
+ }
322
+
323
+ function SidebarInput({
324
+ className,
325
+ ...props
326
+ }: React.ComponentProps<typeof Input>) {
327
+ return (
328
+ <Input
329
+ data-slot="sidebar-input"
330
+ data-sidebar="input"
331
+ className={cn("h-8 w-full shadow-none", className)}
332
+ {...props}
333
+ />
334
+ )
335
+ }
336
+
337
+ function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
338
+ return (
339
+ <div
340
+ data-slot="sidebar-header"
341
+ data-sidebar="header"
342
+ className={cn(
343
+ "flex flex-col gap-2 border-b border-sidebar-border p-2 pb-1.75 [&.border-b-0[class~='pb-1.75']]:pb-2",
344
+ className
345
+ )}
346
+ {...props}
347
+ />
348
+ )
349
+ }
350
+
351
+ function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
352
+ return (
353
+ <div
354
+ data-slot="sidebar-footer"
355
+ data-sidebar="footer"
356
+ className={cn(
357
+ "flex flex-col gap-2 border-t border-sidebar-border p-2 pt-1.75 [&.border-t-0[class~='pt-1.75']]:pt-2",
358
+ className
359
+ )}
360
+ {...props}
361
+ />
362
+ )
363
+ }
364
+
365
+ function SidebarSeparator({
366
+ className,
367
+ ...props
368
+ }: React.ComponentProps<typeof Separator>) {
369
+ return (
370
+ <Separator
371
+ data-slot="sidebar-separator"
372
+ data-sidebar="separator"
373
+ className={cn("mx-2 w-auto bg-sidebar-border", className)}
374
+ {...props}
375
+ />
376
+ )
377
+ }
378
+
379
+ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
380
+ return (
381
+ <div
382
+ data-slot="sidebar-content"
383
+ data-sidebar="content"
384
+ className={cn(
385
+ "no-scrollbar flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-x-hidden",
386
+ className
387
+ )}
388
+ {...props}
389
+ />
390
+ )
391
+ }
392
+
393
+ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
394
+ return (
395
+ <div
396
+ data-slot="sidebar-group"
397
+ data-sidebar="group"
398
+ className={cn(
399
+ "relative flex w-full min-w-0 flex-col p-2 group-data-[collapsible=icon]:py-0",
400
+ className
401
+ )}
402
+ {...props}
403
+ />
404
+ )
405
+ }
406
+
407
+ function SidebarGroupLabel({
408
+ className,
409
+ render,
410
+ size = "default",
411
+ ...props
412
+ }: useRender.ComponentProps<"div"> &
413
+ React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
414
+ return useRender({
415
+ defaultTagName: "div",
416
+ props: mergeProps<"div">(
417
+ {
418
+ className: cn(
419
+ "flex h-8 shrink-0 items-center rounded-lg px-2 text-sm font-medium text-muted-foreground outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-3 focus-visible:inset-ring focus-visible:ring-ring/30 focus-visible:inset-ring-sidebar-ring data-[size=sm]:text-2xs [&:is(a,button)]:hover:bg-fill-1 [&:is(a,button)]:active:bg-fill-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground",
420
+ className
421
+ ),
422
+ },
423
+ props
424
+ ),
425
+ render,
426
+ state: {
427
+ slot: "sidebar-group-label",
428
+ sidebar: "group-label",
429
+ size,
430
+ },
431
+ })
432
+ }
433
+
434
+ function SidebarGroupAction({
435
+ className,
436
+ render,
437
+ ...props
438
+ }: useRender.ComponentProps<"button"> & React.ComponentProps<"button">) {
439
+ return useRender({
440
+ defaultTagName: "button",
441
+ props: mergeProps<"button">(
442
+ {
443
+ className: cn(
444
+ "absolute top-3.5 right-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-muted-foreground outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-fill-1 hover:text-sidebar-foreground focus-visible:ring-3 focus-visible:inset-ring focus-visible:ring-ring/30 focus-visible:inset-ring-sidebar-ring md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
445
+ className
446
+ ),
447
+ },
448
+ props
449
+ ),
450
+ render,
451
+ state: {
452
+ slot: "sidebar-group-action",
453
+ sidebar: "group-action",
454
+ },
455
+ })
456
+ }
457
+
458
+ function SidebarGroupContent({
459
+ className,
460
+ ...props
461
+ }: React.ComponentProps<"div">) {
462
+ return (
463
+ <div
464
+ data-slot="sidebar-group-content"
465
+ data-sidebar="group-content"
466
+ className={cn("w-full text-sm", className)}
467
+ {...props}
468
+ />
469
+ )
470
+ }
471
+
472
+ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
473
+ return (
474
+ <ul
475
+ data-slot="sidebar-menu"
476
+ data-sidebar="menu"
477
+ className={cn("flex w-full min-w-0 flex-col", className)}
478
+ {...props}
479
+ />
480
+ )
481
+ }
482
+
483
+ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
484
+ return (
485
+ <li
486
+ data-slot="sidebar-menu-item"
487
+ data-sidebar="menu-item"
488
+ className={cn("group/menu-item relative", className)}
489
+ {...props}
490
+ />
491
+ )
492
+ }
493
+
494
+ const sidebarMenuButtonVariants = cva(
495
+ "peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-lg px-2 py-1.5 text-left text-sm whitespace-nowrap outline-hidden transition-[width,height,padding] duration-200 group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-fill-1 focus-visible:bg-sidebar focus-visible:ring-3 focus-visible:inset-ring focus-visible:ring-ring/30 focus-visible:inset-ring-sidebar-ring active:bg-fill-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-fill-2 data-active:font-medium data-active:hover:bg-fill-3 [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate [&>svg]:text-muted-foreground hover:[&>svg]:text-sidebar-foreground data-active:[&>svg]:text-sidebar-foreground",
496
+ {
497
+ variants: {
498
+ variant: {
499
+ default: "",
500
+ outline: "bg-background shadow-[0_0_0_1px_var(--sidebar-border)]",
501
+ },
502
+ size: {
503
+ default: "h-8",
504
+ sm: "h-7 text-2xs",
505
+ lg: "h-12 gap-2.5 group-data-[collapsible=icon]:p-0!",
506
+ },
507
+ },
508
+ defaultVariants: {
509
+ variant: "default",
510
+ size: "default",
511
+ },
512
+ }
513
+ )
514
+
515
+ function SidebarMenuButton({
516
+ render,
517
+ isActive = false,
518
+ variant = "default",
519
+ size = "default",
520
+ tooltip,
521
+ className,
522
+ ...props
523
+ }: useRender.ComponentProps<"button"> &
524
+ React.ComponentProps<"button"> & {
525
+ isActive?: boolean
526
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>
527
+ } & VariantProps<typeof sidebarMenuButtonVariants>) {
528
+ const { isMobile, state } = useSidebar()
529
+ const comp = useRender({
530
+ defaultTagName: "button",
531
+ props: mergeProps<"button">(
532
+ {
533
+ className: cn(sidebarMenuButtonVariants({ variant, size }), className),
534
+ },
535
+ props
536
+ ),
537
+ render: !tooltip ? render : <TooltipTrigger render={render} />,
538
+ state: {
539
+ slot: "sidebar-menu-button",
540
+ sidebar: "menu-button",
541
+ size,
542
+ active: isActive,
543
+ },
544
+ })
545
+
546
+ if (!tooltip) {
547
+ return comp
548
+ }
549
+
550
+ if (typeof tooltip === "string") {
551
+ tooltip = {
552
+ children: tooltip,
553
+ }
554
+ }
555
+
556
+ return (
557
+ <Tooltip>
558
+ {comp}
559
+ <TooltipContent
560
+ side="right"
561
+ align="center"
562
+ hidden={state !== "collapsed" || isMobile}
563
+ {...tooltip}
564
+ />
565
+ </Tooltip>
566
+ )
567
+ }
568
+
569
+ function SidebarMenuAction({
570
+ className,
571
+ render,
572
+ showOnHover = false,
573
+ ...props
574
+ }: useRender.ComponentProps<"button"> &
575
+ React.ComponentProps<"button"> & {
576
+ showOnHover?: boolean
577
+ }) {
578
+ return useRender({
579
+ defaultTagName: "button",
580
+ props: mergeProps<"button">(
581
+ {
582
+ className: cn(
583
+ "absolute top-1.5 right-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-muted-foreground outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-fill-1 hover:text-sidebar-foreground focus-visible:ring-3 focus-visible:inset-ring focus-visible:ring-ring/30 focus-visible:inset-ring-sidebar-ring md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
584
+ showOnHover &&
585
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-foreground aria-expanded:opacity-100 md:opacity-0",
586
+ className
587
+ ),
588
+ },
589
+ props
590
+ ),
591
+ render,
592
+ state: {
593
+ slot: "sidebar-menu-action",
594
+ sidebar: "menu-action",
595
+ },
596
+ })
597
+ }
598
+
599
+ function SidebarMenuBadge({
600
+ className,
601
+ ...props
602
+ }: React.ComponentProps<"div">) {
603
+ return (
604
+ <div
605
+ data-slot="sidebar-menu-badge"
606
+ data-sidebar="menu-badge"
607
+ className={cn(
608
+ "pointer-events-none absolute right-2 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-2xs font-medium text-muted-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-foreground",
609
+ className
610
+ )}
611
+ {...props}
612
+ />
613
+ )
614
+ }
615
+
616
+ function SidebarMenuSkeleton({
617
+ className,
618
+ showIcon = false,
619
+ ...props
620
+ }: React.ComponentProps<"div"> & {
621
+ showIcon?: boolean
622
+ }) {
623
+ const id = React.useId()
624
+ const hash = [...id].reduce(
625
+ (value, char) => Math.imul(value ^ char.charCodeAt(0), 16777619),
626
+ 2166136261
627
+ )
628
+ const width = `${50 + ((hash >>> 0) % 40)}%`
629
+
630
+ return (
631
+ <div
632
+ data-slot="sidebar-menu-skeleton"
633
+ data-sidebar="menu-skeleton"
634
+ className={cn("flex h-8 items-center gap-2 rounded-lg px-2", className)}
635
+ {...props}
636
+ >
637
+ {showIcon && (
638
+ <Skeleton
639
+ className="size-4 rounded-md"
640
+ data-sidebar="menu-skeleton-icon"
641
+ />
642
+ )}
643
+ <Skeleton
644
+ className="h-4 max-w-(--skeleton-width) flex-1"
645
+ data-sidebar="menu-skeleton-text"
646
+ style={
647
+ {
648
+ "--skeleton-width": width,
649
+ } as React.CSSProperties
650
+ }
651
+ />
652
+ </div>
653
+ )
654
+ }
655
+
656
+ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
657
+ return (
658
+ <ul
659
+ data-slot="sidebar-menu-sub"
660
+ data-sidebar="menu-sub"
661
+ className={cn(
662
+ "mt-0.5 ml-3.75 flex min-w-0 flex-col gap-1 border-l border-sidebar-border py-0.5 pl-2 group-data-[collapsible=icon]:hidden",
663
+ className
664
+ )}
665
+ {...props}
666
+ />
667
+ )
668
+ }
669
+
670
+ function SidebarMenuSubItem({
671
+ className,
672
+ ...props
673
+ }: React.ComponentProps<"li">) {
674
+ return (
675
+ <li
676
+ data-slot="sidebar-menu-sub-item"
677
+ data-sidebar="menu-sub-item"
678
+ className={cn("group/menu-sub-item relative", className)}
679
+ {...props}
680
+ />
681
+ )
682
+ }
683
+
684
+ function SidebarMenuSubButton({
685
+ render,
686
+ size = "md",
687
+ isActive = false,
688
+ className,
689
+ ...props
690
+ }: useRender.ComponentProps<"a"> &
691
+ React.ComponentProps<"a"> & {
692
+ size?: "sm" | "md"
693
+ isActive?: boolean
694
+ }) {
695
+ return useRender({
696
+ defaultTagName: "a",
697
+ props: mergeProps<"a">(
698
+ {
699
+ className: cn(
700
+ "flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground outline-hidden group-data-[collapsible=icon]:hidden hover:bg-fill-1 focus-visible:ring-3 focus-visible:inset-ring focus-visible:ring-ring/30 focus-visible:inset-ring-sidebar-ring active:bg-fill-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-2xs data-active:bg-fill-2 data-active:font-medium data-active:hover:bg-fill-3 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground",
701
+ className
702
+ ),
703
+ },
704
+ props
705
+ ),
706
+ render,
707
+ state: {
708
+ slot: "sidebar-menu-sub-button",
709
+ sidebar: "menu-sub-button",
710
+ size,
711
+ active: isActive,
712
+ },
713
+ })
714
+ }
715
+
716
+ export {
717
+ Sidebar,
718
+ SidebarContent,
719
+ SidebarFooter,
720
+ SidebarGroup,
721
+ SidebarGroupAction,
722
+ SidebarGroupContent,
723
+ SidebarGroupLabel,
724
+ SidebarHeader,
725
+ SidebarInput,
726
+ SidebarInset,
727
+ SidebarMenu,
728
+ SidebarMenuAction,
729
+ SidebarMenuBadge,
730
+ SidebarMenuButton,
731
+ SidebarMenuItem,
732
+ SidebarMenuSkeleton,
733
+ SidebarMenuSub,
734
+ SidebarMenuSubButton,
735
+ SidebarMenuSubItem,
736
+ SidebarProvider,
737
+ SidebarRail,
738
+ SidebarSeparator,
739
+ SidebarTrigger,
740
+ useSidebar,
741
+ }