@trackany-device/components 1.0.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 (289) hide show
  1. package/package.json +185 -0
  2. package/src/assets/logo.png +0 -0
  3. package/src/assets/map/arrows/map-arrow-blue.png +0 -0
  4. package/src/assets/map/arrows/map-arrow-green.png +0 -0
  5. package/src/assets/map/arrows/map-arrow-purple.png +0 -0
  6. package/src/assets/map/arrows/map-arrow-red.png +0 -0
  7. package/src/assets/map/flags/flag-blue.png +0 -0
  8. package/src/assets/map/flags/flag-green.png +0 -0
  9. package/src/assets/map/flags/flag-red.png +0 -0
  10. package/src/assets/map/flags/flag-yellow.png +0 -0
  11. package/src/assets/map/pins/map-pin-blue.png +0 -0
  12. package/src/assets/map/pins/map-pin-green.png +0 -0
  13. package/src/assets/map/pins/map-pin-purple.png +0 -0
  14. package/src/assets/map/pins/map-pin-red.png +0 -0
  15. package/src/components/Card.tsx +9 -0
  16. package/src/components/alert-error.tsx +24 -0
  17. package/src/components/app-content.tsx +22 -0
  18. package/src/components/app-header.tsx +153 -0
  19. package/src/components/app-logo-icon.tsx +13 -0
  20. package/src/components/app-logo.tsx +21 -0
  21. package/src/components/app-shell.tsx +19 -0
  22. package/src/components/app-sidebar-header.tsx +68 -0
  23. package/src/components/app-sidebar.tsx +106 -0
  24. package/src/components/appearance-tabs.tsx +46 -0
  25. package/src/components/breadcrumbs.tsx +50 -0
  26. package/src/components/cms/blurred-image.tsx +111 -0
  27. package/src/components/cms/section-bg.tsx +473 -0
  28. package/src/components/cms/section-button.tsx +127 -0
  29. package/src/components/cms/sections/banner-5050-section.tsx +135 -0
  30. package/src/components/cms/sections/blogs-listing-section.tsx +270 -0
  31. package/src/components/cms/sections/cards-grid-section.tsx +185 -0
  32. package/src/components/cms/sections/contact-form-section.tsx +157 -0
  33. package/src/components/cms/sections/cta-section.tsx +101 -0
  34. package/src/components/cms/sections/featured-blog-slider-section.tsx +256 -0
  35. package/src/components/cms/sections/featured-products-grid-section.tsx +173 -0
  36. package/src/components/cms/sections/featured-solutions-grid-section.tsx +183 -0
  37. package/src/components/cms/sections/hero-section.tsx +180 -0
  38. package/src/components/cms/sections/solutions-with-filter-section.tsx +234 -0
  39. package/src/components/cms/sections/text-section.tsx +77 -0
  40. package/src/components/cutout-image.tsx +228 -0
  41. package/src/components/devices/devices-mini-map.tsx +275 -0
  42. package/src/components/docs/docs-shell.tsx +280 -0
  43. package/src/components/fleet-hero-animated.tsx +383 -0
  44. package/src/components/input-error.tsx +17 -0
  45. package/src/components/keenicons/assets/duotone/Read Me.txt +7 -0
  46. package/src/components/keenicons/assets/duotone/demo-files/demo.css +160 -0
  47. package/src/components/keenicons/assets/duotone/demo-files/demo.js +32 -0
  48. package/src/components/keenicons/assets/duotone/demo.html +12424 -0
  49. package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.svg +1109 -0
  50. package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.ttf +0 -0
  51. package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.woff +0 -0
  52. package/src/components/keenicons/assets/duotone/selection.json +17313 -0
  53. package/src/components/keenicons/assets/duotone/style.css +4931 -0
  54. package/src/components/keenicons/assets/filled/Read Me.txt +7 -0
  55. package/src/components/keenicons/assets/filled/demo-files/demo.css +160 -0
  56. package/src/components/keenicons/assets/filled/demo-files/demo.js +32 -0
  57. package/src/components/keenicons/assets/filled/demo.html +12370 -0
  58. package/src/components/keenicons/assets/filled/fonts/keenicons-filled.svg +1082 -0
  59. package/src/components/keenicons/assets/filled/fonts/keenicons-filled.ttf +0 -0
  60. package/src/components/keenicons/assets/filled/fonts/keenicons-filled.woff +0 -0
  61. package/src/components/keenicons/assets/filled/selection.json +17096 -0
  62. package/src/components/keenicons/assets/filled/style.css +4769 -0
  63. package/src/components/keenicons/assets/outline/Read Me.txt +7 -0
  64. package/src/components/keenicons/assets/outline/demo-files/demo.css +160 -0
  65. package/src/components/keenicons/assets/outline/demo-files/demo.js +32 -0
  66. package/src/components/keenicons/assets/outline/demo.html +11356 -0
  67. package/src/components/keenicons/assets/outline/fonts/keenicons-outline.svg +575 -0
  68. package/src/components/keenicons/assets/outline/fonts/keenicons-outline.ttf +0 -0
  69. package/src/components/keenicons/assets/outline/fonts/keenicons-outline.woff +0 -0
  70. package/src/components/keenicons/assets/outline/selection.json +13054 -0
  71. package/src/components/keenicons/assets/outline/style.css +1721 -0
  72. package/src/components/keenicons/assets/solid/Read Me.txt +7 -0
  73. package/src/components/keenicons/assets/solid/demo-files/demo.css +160 -0
  74. package/src/components/keenicons/assets/solid/demo-files/demo.js +32 -0
  75. package/src/components/keenicons/assets/solid/demo.html +11356 -0
  76. package/src/components/keenicons/assets/solid/fonts/keenicons-solid.svg +575 -0
  77. package/src/components/keenicons/assets/solid/fonts/keenicons-solid.ttf +0 -0
  78. package/src/components/keenicons/assets/solid/fonts/keenicons-solid.woff +0 -0
  79. package/src/components/keenicons/assets/solid/selection.json +13048 -0
  80. package/src/components/keenicons/assets/solid/style.css +1721 -0
  81. package/src/components/keenicons/assets/styles.css +4 -0
  82. package/src/components/keenicons/index.ts +2 -0
  83. package/src/components/keenicons/keenicons.tsx +16 -0
  84. package/src/components/keenicons/types.ts +7 -0
  85. package/src/components/nav-footer.tsx +49 -0
  86. package/src/components/nav-main.tsx +53 -0
  87. package/src/components/nav-user.tsx +59 -0
  88. package/src/components/notification-bell.tsx +190 -0
  89. package/src/components/products/product-card.tsx +159 -0
  90. package/src/components/text-link.tsx +23 -0
  91. package/src/components/ui/accordion-menu.tsx +322 -0
  92. package/src/components/ui/accordion.tsx +133 -0
  93. package/src/components/ui/alert-dialog.tsx +82 -0
  94. package/src/components/ui/alert.tsx +63 -0
  95. package/src/components/ui/avatar-group.tsx +129 -0
  96. package/src/components/ui/avatar.tsx +67 -0
  97. package/src/components/ui/badge.tsx +230 -0
  98. package/src/components/ui/breadcrumb.tsx +88 -0
  99. package/src/components/ui/button.tsx +412 -0
  100. package/src/components/ui/calendar.tsx +56 -0
  101. package/src/components/ui/card.tsx +147 -0
  102. package/src/components/ui/chart.tsx +290 -0
  103. package/src/components/ui/checkbox.tsx +47 -0
  104. package/src/components/ui/code.tsx +45 -0
  105. package/src/components/ui/collapsible.tsx +31 -0
  106. package/src/components/ui/command-palette.tsx +189 -0
  107. package/src/components/ui/command.tsx +138 -0
  108. package/src/components/ui/cookie-banner.tsx +220 -0
  109. package/src/components/ui/copy-button.tsx +60 -0
  110. package/src/components/ui/data-grid-column-filter.tsx +124 -0
  111. package/src/components/ui/data-grid-column-header.tsx +284 -0
  112. package/src/components/ui/data-grid-column-visibility.tsx +38 -0
  113. package/src/components/ui/data-grid-pagination.tsx +206 -0
  114. package/src/components/ui/data-grid-table-dnd-rows.tsx +147 -0
  115. package/src/components/ui/data-grid-table-dnd.tsx +175 -0
  116. package/src/components/ui/data-grid-table.tsx +500 -0
  117. package/src/components/ui/data-grid.tsx +193 -0
  118. package/src/components/ui/data-list.tsx +76 -0
  119. package/src/components/ui/datefield.tsx +91 -0
  120. package/src/components/ui/dialog.tsx +139 -0
  121. package/src/components/ui/divider.tsx +41 -0
  122. package/src/components/ui/drawer.tsx +59 -0
  123. package/src/components/ui/dropdown-menu.tsx +224 -0
  124. package/src/components/ui/empty-state.tsx +54 -0
  125. package/src/components/ui/file-upload.tsx +152 -0
  126. package/src/components/ui/form.tsx +88 -0
  127. package/src/components/ui/icon.tsx +14 -0
  128. package/src/components/ui/input-otp.tsx +71 -0
  129. package/src/components/ui/input.tsx +155 -0
  130. package/src/components/ui/kbd.tsx +26 -0
  131. package/src/components/ui/label.tsx +31 -0
  132. package/src/components/ui/navigation-menu.tsx +168 -0
  133. package/src/components/ui/pagination.tsx +37 -0
  134. package/src/components/ui/placeholder-pattern.tsx +21 -0
  135. package/src/components/ui/popover.tsx +50 -0
  136. package/src/components/ui/progress.tsx +65 -0
  137. package/src/components/ui/radio-group.tsx +73 -0
  138. package/src/components/ui/resizable.tsx +39 -0
  139. package/src/components/ui/scroll-area.tsx +50 -0
  140. package/src/components/ui/select.tsx +234 -0
  141. package/src/components/ui/separator.tsx +24 -0
  142. package/src/components/ui/sheet.tsx +147 -0
  143. package/src/components/ui/sidebar.tsx +721 -0
  144. package/src/components/ui/skeleton.tsx +15 -0
  145. package/src/components/ui/slider.tsx +35 -0
  146. package/src/components/ui/sonner.tsx +28 -0
  147. package/src/components/ui/sortable.tsx +724 -0
  148. package/src/components/ui/spinner.tsx +17 -0
  149. package/src/components/ui/stat-card.tsx +82 -0
  150. package/src/components/ui/stepper.tsx +410 -0
  151. package/src/components/ui/switch.tsx +68 -0
  152. package/src/components/ui/table.tsx +42 -0
  153. package/src/components/ui/tabs.tsx +196 -0
  154. package/src/components/ui/timeline.tsx +90 -0
  155. package/src/components/ui/toggle-group.tsx +73 -0
  156. package/src/components/ui/toggle.tsx +45 -0
  157. package/src/components/ui/tooltip.tsx +55 -0
  158. package/src/components/user-info.tsx +33 -0
  159. package/src/components/user-menu-content.tsx +53 -0
  160. package/src/components/web/SiteFooter.tsx +154 -0
  161. package/src/components/web/SiteHeader.tsx +159 -0
  162. package/src/components/workflows/workflow-canvas.tsx +321 -0
  163. package/src/controls/Blockquote.tsx +25 -0
  164. package/src/controls/Button.tsx +101 -0
  165. package/src/controls/Checkbox.tsx +29 -0
  166. package/src/controls/DateField.tsx +37 -0
  167. package/src/controls/FormField.tsx +20 -0
  168. package/src/controls/Heading.tsx +28 -0
  169. package/src/controls/Input.tsx +21 -0
  170. package/src/controls/Label.tsx +18 -0
  171. package/src/controls/Paragraph.tsx +39 -0
  172. package/src/controls/PasswordInput.tsx +40 -0
  173. package/src/controls/RadioGroup.tsx +70 -0
  174. package/src/controls/Select.tsx +24 -0
  175. package/src/controls/Slider.tsx +33 -0
  176. package/src/controls/Switch.tsx +31 -0
  177. package/src/controls/Textarea.tsx +22 -0
  178. package/src/elements/ConfirmPasswordForm.tsx +43 -0
  179. package/src/elements/DeviceStatusBadge.tsx +38 -0
  180. package/src/elements/DriverCard.tsx +67 -0
  181. package/src/elements/ForgotPasswordForm.tsx +64 -0
  182. package/src/elements/IncidentCard.tsx +67 -0
  183. package/src/elements/LoginForm.tsx +100 -0
  184. package/src/elements/OtpForm.tsx +71 -0
  185. package/src/elements/RegisterForm.tsx +150 -0
  186. package/src/elements/ResetPasswordForm.tsx +72 -0
  187. package/src/elements/SmsChallengeForm.tsx +104 -0
  188. package/src/elements/VehicleCard.tsx +73 -0
  189. package/src/elements/VerifyEmailForm.tsx +39 -0
  190. package/src/hooks/use-appearance.tsx +117 -0
  191. package/src/hooks/use-applied-theme.ts +98 -0
  192. package/src/hooks/use-clipboard.ts +34 -0
  193. package/src/hooks/use-current-url.ts +83 -0
  194. package/src/hooks/use-dark-mode.ts +48 -0
  195. package/src/hooks/use-flash-toast.ts +29 -0
  196. package/src/hooks/use-initials.tsx +24 -0
  197. package/src/hooks/use-mobile-navigation.ts +12 -0
  198. package/src/hooks/use-mobile.tsx +38 -0
  199. package/src/index.ts +408 -0
  200. package/src/layouts/AppLayout.tsx +60 -0
  201. package/src/layouts/AuthLayout.tsx +32 -0
  202. package/src/layouts/SettingsLayout.tsx +21 -0
  203. package/src/layouts/app/AIChatLayout.tsx +73 -0
  204. package/src/layouts/app/AsideSidebarLayout.tsx +3 -0
  205. package/src/layouts/app/CalendarSidebarLayout.tsx +69 -0
  206. package/src/layouts/app/CommunitiesNavbarLayout.tsx +3 -0
  207. package/src/layouts/app/DualNavbarSidebarLayout.tsx +3 -0
  208. package/src/layouts/app/FocusSidebarLayout.tsx +75 -0
  209. package/src/layouts/app/MailLayout.tsx +69 -0
  210. package/src/layouts/app/MegaMenuHeaderLayout.tsx +3 -0
  211. package/src/layouts/app/MegaMenuLayout.tsx +81 -0
  212. package/src/layouts/app/MegaMenuNavbarLayout.tsx +88 -0
  213. package/src/layouts/app/MegaMenuSearchNavbarLayout.tsx +3 -0
  214. package/src/layouts/app/NavbarCollapsibleLayout.tsx +88 -0
  215. package/src/layouts/app/NavbarCollapsibleLinksLayout.tsx +3 -0
  216. package/src/layouts/app/NavbarMinimalLayout.tsx +3 -0
  217. package/src/layouts/app/NavbarMinimalSidebarLayout.tsx +3 -0
  218. package/src/layouts/app/NavbarSidebarDashboardLayout.tsx +3 -0
  219. package/src/layouts/app/NavbarSidebarLayout.tsx +92 -0
  220. package/src/layouts/app/NavbarSimpleSidebarLayout.tsx +3 -0
  221. package/src/layouts/app/NavbarTitledSidebarLayout.tsx +3 -0
  222. package/src/layouts/app/PanelSidebarLayout.tsx +3 -0
  223. package/src/layouts/app/SearchNavbarSidebarLayout.tsx +3 -0
  224. package/src/layouts/app/SidebarBreadcrumbLayout.tsx +3 -0
  225. package/src/layouts/app/SidebarCleanLayout.tsx +3 -0
  226. package/src/layouts/app/SidebarCommunitiesLayout.tsx +3 -0
  227. package/src/layouts/app/SidebarContentLayout.tsx +3 -0
  228. package/src/layouts/app/SidebarDualMenuLayout.tsx +104 -0
  229. package/src/layouts/app/SidebarFixedLayout.tsx +166 -0
  230. package/src/layouts/app/SidebarFooterNavbarLayout.tsx +3 -0
  231. package/src/layouts/app/SidebarHeaderMenuLayout.tsx +3 -0
  232. package/src/layouts/app/SidebarMegaMenuLayout.tsx +4 -0
  233. package/src/layouts/app/SidebarMinimalLayout.tsx +70 -0
  234. package/src/layouts/app/SidebarMobileSearchLayout.tsx +3 -0
  235. package/src/layouts/app/SidebarMultiPanelLayout.tsx +3 -0
  236. package/src/layouts/app/SidebarPrimarySecondaryLayout.tsx +3 -0
  237. package/src/layouts/app/SidebarSearchHeaderLayout.tsx +103 -0
  238. package/src/layouts/app/SidebarSearchToolbarLayout.tsx +3 -0
  239. package/src/layouts/app/SidebarTabsDualLayout.tsx +3 -0
  240. package/src/layouts/app/SidebarTabsLayout.tsx +98 -0
  241. package/src/layouts/app/SidebarTreeLayout.tsx +3 -0
  242. package/src/layouts/app/SplitNavbarLayout.tsx +3 -0
  243. package/src/layouts/app/SplitSidebarDashboardLayout.tsx +3 -0
  244. package/src/layouts/app/SplitSidebarLayout.tsx +99 -0
  245. package/src/layouts/app/TopNavLayout.tsx +105 -0
  246. package/src/layouts/app/TopNavLinksLayout.tsx +3 -0
  247. package/src/layouts/app/WorkspaceBreadcrumbLayout.tsx +3 -0
  248. package/src/layouts/app/WorkspaceCommunitiesLayout.tsx +3 -0
  249. package/src/layouts/app/WorkspaceNavbarLayout.tsx +3 -0
  250. package/src/layouts/app/WorkspaceSidebarLayout.tsx +98 -0
  251. package/src/layouts/app/WorkspaceSidebarTitleLayout.tsx +3 -0
  252. package/src/layouts/app/app-header-layout.tsx +45 -0
  253. package/src/layouts/app/app-sidebar-layout.tsx +56 -0
  254. package/src/layouts/app/layout-context.tsx +44 -0
  255. package/src/layouts/app/layout-types.ts +47 -0
  256. package/src/layouts/app/partials/Footer.tsx +35 -0
  257. package/src/layouts/app/partials/HeaderTopbar.tsx +96 -0
  258. package/src/layouts/app/partials/Navbar.tsx +85 -0
  259. package/src/layouts/app/partials/Toolbar.tsx +47 -0
  260. package/src/layouts/app-layout.tsx +29 -0
  261. package/src/layouts/auth/AuthBrandedLayout.tsx +58 -0
  262. package/src/layouts/auth/AuthCardLayout.tsx +31 -0
  263. package/src/layouts/auth/AuthCenteredLayout.tsx +41 -0
  264. package/src/layouts/auth/AuthClassicLayout.tsx +41 -0
  265. package/src/layouts/auth/AuthSimpleLayout.tsx +33 -0
  266. package/src/layouts/auth/AuthSplitLayout.tsx +89 -0
  267. package/src/layouts/web-app-layout.tsx +162 -0
  268. package/src/layouts/web-layout.tsx +23 -0
  269. package/src/lib/datetime.ts +188 -0
  270. package/src/lib/google-maps-loader.ts +99 -0
  271. package/src/lib/location.ts +127 -0
  272. package/src/lib/lucide-icon-map.ts +132 -0
  273. package/src/lib/map-markers.ts +124 -0
  274. package/src/lib/map-styles.ts +351 -0
  275. package/src/lib/utils.ts +11 -0
  276. package/src/platform/adapters/default.tsx +156 -0
  277. package/src/platform/adapters/inertia.tsx +88 -0
  278. package/src/platform/adapters/nextjs.ts +86 -0
  279. package/src/platform/context.tsx +106 -0
  280. package/src/platform/index.ts +27 -0
  281. package/src/platform/types.ts +105 -0
  282. package/src/styles/layouts/sidebar-fixed.css +161 -0
  283. package/src/styles/themes.css +583 -0
  284. package/src/types/assets.d.ts +5 -0
  285. package/src/types/auth.ts +25 -0
  286. package/src/types/global.d.ts +13 -0
  287. package/src/types/index.ts +9 -0
  288. package/src/types/navigation.ts +15 -0
  289. package/src/types/ui.ts +32 -0
@@ -0,0 +1,224 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cn } from '../../lib/utils';
5
+ import { Check, ChevronRight, Circle } from 'lucide-react';
6
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
7
+
8
+ function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
9
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
10
+ }
11
+
12
+ function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
13
+ return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
14
+ }
15
+
16
+ function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
17
+ return <DropdownMenuPrimitive.Trigger className="select-none" data-slot="dropdown-menu-trigger" {...props} />;
18
+ }
19
+
20
+ function DropdownMenuSubTrigger({
21
+ className,
22
+ inset,
23
+ children,
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
26
+ inset?: boolean;
27
+ }) {
28
+ return (
29
+ <DropdownMenuPrimitive.SubTrigger
30
+ data-slot="dropdown-menu-sub-trigger"
31
+ className={cn(
32
+ 'flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-sm outline-hidden',
33
+ 'focus:bg-accent focus:text-foreground',
34
+ 'data-[state=open]:bg-accent data-[state=open]:text-foreground',
35
+ 'data-[here=true]:bg-accent data-[here=true]:text-foreground',
36
+ '[&>svg]:pointer-events-none [&_svg:not([role=img]):not([class*=text-])]:opacity-60 [&>svg]:size-4 [&>svg]:shrink-0',
37
+ inset && 'ps-8',
38
+ className,
39
+ )}
40
+ {...props}
41
+ >
42
+ {children}
43
+ <ChevronRight data-slot="dropdown-menu-sub-trigger-indicator" className="ms-auto size-3.5! rtl:rotate-180" />
44
+ </DropdownMenuPrimitive.SubTrigger>
45
+ );
46
+ }
47
+
48
+ function DropdownMenuSubContent({
49
+ className,
50
+ ...props
51
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
52
+ return (
53
+ <DropdownMenuPrimitive.SubContent
54
+ data-slot="dropdown-menu-sub-content"
55
+ className={cn(
56
+ 'space-y-0.5 z-50 min-w-[8rem] overflow-hidden shadow-md shadow-black/5 rounded-md border border-border bg-popover text-popover-foreground p-2 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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',
57
+ className,
58
+ )}
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ function DropdownMenuContent({
65
+ className,
66
+ sideOffset = 4,
67
+ ...props
68
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
69
+ return (
70
+ <DropdownMenuPrimitive.Portal>
71
+ <DropdownMenuPrimitive.Content
72
+ data-slot="dropdown-menu-content"
73
+ sideOffset={sideOffset}
74
+ className={cn(
75
+ 'space-y-0.5 z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-md shadow-black/5 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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',
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ </DropdownMenuPrimitive.Portal>
81
+ );
82
+ }
83
+
84
+ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
85
+ return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
86
+ }
87
+
88
+ function DropdownMenuItem({
89
+ className,
90
+ inset,
91
+ variant,
92
+ ...props
93
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
94
+ inset?: boolean;
95
+ variant?: 'destructive';
96
+ }) {
97
+ return (
98
+ <DropdownMenuPrimitive.Item
99
+ data-slot="dropdown-menu-item"
100
+ className={cn(
101
+ 'text-foreground relative flex cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-hidden transition-colors data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([role=img]):not([class*=text-])]:opacity-60 [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0',
102
+ 'focus:bg-accent focus:text-foreground',
103
+ 'data-[active=true]:bg-accent data-[active=true]:text-accent-foreground',
104
+ inset && 'ps-8',
105
+ variant === 'destructive' &&
106
+ 'text-destructive hover:text-destructive focus:text-destructive hover:bg-destructive/5 focus:bg-destructive/5 data-[active=true]:bg-destructive/5',
107
+ className,
108
+ )}
109
+ {...props}
110
+ />
111
+ );
112
+ }
113
+
114
+ function DropdownMenuCheckboxItem({
115
+ className,
116
+ children,
117
+ checked,
118
+ ...props
119
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
120
+ return (
121
+ <DropdownMenuPrimitive.CheckboxItem
122
+ data-slot="dropdown-menu-checkbox-item"
123
+ className={cn(
124
+ 'relative flex cursor-default select-none items-center rounded-md py-1.5 ps-8 pe-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
125
+ className,
126
+ )}
127
+ checked={checked}
128
+ {...props}
129
+ >
130
+ <span className="absolute start-2 flex h-3.5 w-3.5 items-center text-muted-foreground justify-center">
131
+ <DropdownMenuPrimitive.ItemIndicator>
132
+ <Check className="h-4 w-4 text-primary" />
133
+ </DropdownMenuPrimitive.ItemIndicator>
134
+ </span>
135
+ {children}
136
+ </DropdownMenuPrimitive.CheckboxItem>
137
+ );
138
+ }
139
+
140
+ function DropdownMenuRadioItem({
141
+ className,
142
+ children,
143
+ ...props
144
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
145
+ return (
146
+ <DropdownMenuPrimitive.RadioItem
147
+ data-slot="dropdown-menu-radio-item"
148
+ className={cn(
149
+ 'relative flex cursor-default select-none items-center rounded-md py-1.5 ps-6 pe-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
150
+ className,
151
+ )}
152
+ {...props}
153
+ >
154
+ <span className="absolute start-1.5 flex h-3.5 w-3.5 items-center justify-center">
155
+ <DropdownMenuPrimitive.ItemIndicator>
156
+ <Circle className="h-1.5 w-1.5 fill-primary stroke-primary" />
157
+ </DropdownMenuPrimitive.ItemIndicator>
158
+ </span>
159
+ {children}
160
+ </DropdownMenuPrimitive.RadioItem>
161
+ );
162
+ }
163
+
164
+ function DropdownMenuLabel({
165
+ className,
166
+ inset,
167
+ ...props
168
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
169
+ inset?: boolean;
170
+ }) {
171
+ return (
172
+ <DropdownMenuPrimitive.Label
173
+ data-slot="dropdown-menu-label"
174
+ className={cn('px-2 py-1.5 text-xs text-muted-foreground font-medium', inset && 'ps-8', className)}
175
+ {...props}
176
+ />
177
+ );
178
+ }
179
+
180
+ function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
181
+ return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
182
+ }
183
+
184
+ function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
185
+ return (
186
+ <DropdownMenuPrimitive.Separator
187
+ data-slot="dropdown-menu-separator"
188
+ className={cn('-mx-2 my-1.5 h-px bg-muted', className)}
189
+ {...props}
190
+ />
191
+ );
192
+ }
193
+
194
+ function DropdownMenuShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
195
+ return (
196
+ <span
197
+ data-slot="dropdown-menu-shortcut"
198
+ className={cn('ms-auto text-xs tracking-widest opacity-60', className)}
199
+ {...props}
200
+ />
201
+ );
202
+ }
203
+
204
+ function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
205
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
206
+ }
207
+
208
+ export {
209
+ DropdownMenu,
210
+ DropdownMenuCheckboxItem,
211
+ DropdownMenuContent,
212
+ DropdownMenuGroup,
213
+ DropdownMenuItem,
214
+ DropdownMenuLabel,
215
+ DropdownMenuPortal,
216
+ DropdownMenuRadioGroup,
217
+ DropdownMenuRadioItem,
218
+ DropdownMenuSeparator,
219
+ DropdownMenuShortcut,
220
+ DropdownMenuSub,
221
+ DropdownMenuSubContent,
222
+ DropdownMenuSubTrigger,
223
+ DropdownMenuTrigger,
224
+ };
@@ -0,0 +1,54 @@
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import { Inbox } from 'lucide-react';
3
+ import * as React from 'react';
4
+
5
+ import { cn } from '../../lib/utils';
6
+ import { Button } from './button';
7
+
8
+ type Props = {
9
+ icon?: LucideIcon;
10
+ title: string;
11
+ description?: string;
12
+ actionLabel?: string;
13
+ actionHref?: string;
14
+ className?: string;
15
+ children?: React.ReactNode;
16
+ };
17
+
18
+ export function EmptyState({
19
+ icon: Icon = Inbox,
20
+ title,
21
+ description,
22
+ actionLabel,
23
+ actionHref,
24
+ className,
25
+ children,
26
+ }: Props) {
27
+ return (
28
+ <div
29
+ role="status"
30
+ className={cn(
31
+ 'flex flex-col items-center justify-center rounded-lg border border-dashed bg-surface px-6 py-12 text-center',
32
+ className,
33
+ )}
34
+ >
35
+ <div className="bg-primary-subtle text-primary mb-4 flex h-12 w-12 items-center justify-center rounded-full">
36
+ <Icon aria-hidden="true" className="size-6" />
37
+ </div>
38
+ <h3 className="font-display text-lg font-semibold text-foreground">
39
+ {title}
40
+ </h3>
41
+ {description && (
42
+ <p className="mt-1 max-w-sm text-sm text-muted-foreground">
43
+ {description}
44
+ </p>
45
+ )}
46
+ {children && <div className="mt-6">{children}</div>}
47
+ {!children && actionLabel && actionHref && (
48
+ <Button asChild className="mt-6">
49
+ <a href={actionHref}>{actionLabel}</a>
50
+ </Button>
51
+ )}
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * FileUpload — drag-and-drop multi-file picker.
5
+ *
6
+ * Headless: it manages drag-state + selection internally and reports back
7
+ * via onChange(File[]). It does NOT upload — caller is responsible for
8
+ * passing the file list to an Inertia form / fetch / FormData payload.
9
+ *
10
+ * <FileUpload accept='image/*' maxSize={2 * 1024 * 1024}
11
+ * onChange={(files) => form.setData('logo', files[0])} />
12
+ */
13
+ import { Upload, X } from 'lucide-react';
14
+ import * as React from 'react';
15
+
16
+ import { Button } from './button';
17
+ import { cn } from '../../lib/utils';
18
+
19
+ type Props = {
20
+ accept?: string;
21
+ multiple?: boolean;
22
+ /** Bytes. Files larger than this are rejected silently with an error message. */
23
+ maxSize?: number;
24
+ hint?: string;
25
+ label?: string;
26
+ name?: string;
27
+ onChange?: (files: File[]) => void;
28
+ className?: string;
29
+ };
30
+
31
+ export function FileUpload({
32
+ accept,
33
+ multiple = false,
34
+ maxSize,
35
+ hint,
36
+ label = 'Drop files here, or click to browse',
37
+ name,
38
+ onChange,
39
+ className,
40
+ }: Props) {
41
+ const inputRef = React.useRef<HTMLInputElement>(null);
42
+ const [files, setFiles] = React.useState<File[]>([]);
43
+ const [dragging, setDragging] = React.useState(false);
44
+ const [error, setError] = React.useState<string | null>(null);
45
+
46
+ const handleFiles = React.useCallback(
47
+ (incoming: FileList | null) => {
48
+ if (!incoming) return;
49
+ const list = Array.from(incoming);
50
+
51
+ if (maxSize) {
52
+ const tooBig = list.find((f) => f.size > maxSize);
53
+ if (tooBig) {
54
+ setError(
55
+ `${tooBig.name} exceeds the ${(maxSize / (1024 * 1024)).toFixed(1)} MB limit.`,
56
+ );
57
+ return;
58
+ }
59
+ }
60
+
61
+ setError(null);
62
+ const next = multiple ? [...files, ...list] : list.slice(0, 1);
63
+ setFiles(next);
64
+ onChange?.(next);
65
+ },
66
+ [files, maxSize, multiple, onChange],
67
+ );
68
+
69
+ const remove = (idx: number) => {
70
+ const next = files.filter((_, i) => i !== idx);
71
+ setFiles(next);
72
+ onChange?.(next);
73
+ };
74
+
75
+ return (
76
+ <div className={cn('space-y-2', className)}>
77
+ <label
78
+ onDragOver={(e) => {
79
+ e.preventDefault();
80
+ setDragging(true);
81
+ }}
82
+ onDragLeave={() => setDragging(false)}
83
+ onDrop={(e) => {
84
+ e.preventDefault();
85
+ setDragging(false);
86
+ handleFiles(e.dataTransfer.files);
87
+ }}
88
+ className={cn(
89
+ 'flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed bg-surface px-6 py-8 text-center transition-colors',
90
+ dragging
91
+ ? 'border-primary bg-primary-subtle/40'
92
+ : 'border-border hover:border-primary/60',
93
+ )}
94
+ >
95
+ <Upload
96
+ aria-hidden="true"
97
+ className="mb-2 size-6 text-muted-foreground"
98
+ />
99
+ <span className="text-sm font-medium text-foreground">
100
+ {label}
101
+ </span>
102
+ {hint && (
103
+ <span className="mt-1 text-xs text-muted-foreground">
104
+ {hint}
105
+ </span>
106
+ )}
107
+ <input
108
+ ref={inputRef}
109
+ type="file"
110
+ name={name}
111
+ accept={accept}
112
+ multiple={multiple}
113
+ className="sr-only"
114
+ onChange={(e) => handleFiles(e.target.files)}
115
+ />
116
+ </label>
117
+
118
+ {error && (
119
+ <p role="alert" className="text-xs text-destructive">
120
+ {error}
121
+ </p>
122
+ )}
123
+
124
+ {files.length > 0 && (
125
+ <ul className="space-y-1">
126
+ {files.map((file, idx) => (
127
+ <li
128
+ key={`${file.name}-${idx}`}
129
+ className="flex items-center justify-between rounded-md border bg-card px-3 py-2 text-sm"
130
+ >
131
+ <span className="truncate font-mono text-xs text-foreground">
132
+ {file.name}
133
+ <span className="ml-2 text-muted-foreground">
134
+ {(file.size / 1024).toFixed(1)} KB
135
+ </span>
136
+ </span>
137
+ <Button
138
+ type="button"
139
+ size="icon"
140
+ variant="ghost"
141
+ aria-label={`Remove ${file.name}`}
142
+ onClick={() => remove(idx)}
143
+ >
144
+ <X aria-hidden="true" className="size-4" />
145
+ </Button>
146
+ </li>
147
+ ))}
148
+ </ul>
149
+ )}
150
+ </div>
151
+ );
152
+ }
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as LabelPrimitive from '@radix-ui/react-label';
5
+ import { Slot } from '@radix-ui/react-slot';
6
+ import { Controller, type ControllerProps, type FieldPath, type FieldValues, FormProvider, useFormContext } from 'react-hook-form';
7
+ import { cn } from '../../lib/utils';
8
+ import { Label } from '../../controls/Label';
9
+
10
+ const Form = FormProvider;
11
+
12
+ type FormFieldContextValue<
13
+ TFieldValues extends FieldValues = FieldValues,
14
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
15
+ > = { name: TName };
16
+
17
+ const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
18
+
19
+ const FormField = <
20
+ TFieldValues extends FieldValues = FieldValues,
21
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
22
+ >({ ...props }: ControllerProps<TFieldValues, TName>) => (
23
+ <FormFieldContext.Provider value={{ name: props.name }}>
24
+ <Controller {...props} />
25
+ </FormFieldContext.Provider>
26
+ );
27
+
28
+ type FormItemContextValue = { id: string };
29
+ const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
30
+
31
+ const useFormField = () => {
32
+ const fieldContext = React.useContext(FormFieldContext);
33
+ const itemContext = React.useContext(FormItemContext);
34
+ const { getFieldState, formState } = useFormContext();
35
+ const fieldState = getFieldState(fieldContext.name, formState);
36
+ const { id } = itemContext;
37
+ return {
38
+ id,
39
+ name: fieldContext.name,
40
+ formItemId: `${id}-form-item`,
41
+ formDescriptionId: `${id}-form-item-description`,
42
+ formMessageId: `${id}-form-item-message`,
43
+ ...fieldState,
44
+ };
45
+ };
46
+
47
+ function FormItem({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
48
+ const id = React.useId();
49
+ const { error } = useFormField();
50
+ return (
51
+ <FormItemContext.Provider value={{ id }}>
52
+ <div data-slot="form-item" className={cn('flex flex-col gap-2.5', className)} data-invalid={!!error} {...props} />
53
+ </FormItemContext.Provider>
54
+ );
55
+ }
56
+
57
+ function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
58
+ const { formItemId } = useFormField();
59
+ return <Label data-slot="form-label" className={cn('font-medium text-foreground', className)} htmlFor={formItemId} {...props} />;
60
+ }
61
+
62
+ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
63
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
64
+ return (
65
+ <Slot
66
+ data-slot="form-control"
67
+ id={formItemId}
68
+ aria-describedby={!error ? formDescriptionId : `${formDescriptionId} ${formMessageId}`}
69
+ aria-invalid={!!error}
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+
75
+ function FormDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
76
+ const { formDescriptionId, error } = useFormField();
77
+ if (error) return null;
78
+ return <div data-slot="form-description" id={formDescriptionId} className={cn('text-xs text-muted-foreground -mt-0.5', className)} {...props} />;
79
+ }
80
+
81
+ function FormMessage({ className, children, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
82
+ const { error, formMessageId } = useFormField();
83
+ const body = error ? String(error?.message) : children;
84
+ if (!body) return null;
85
+ return <div data-slot="form-message" id={formMessageId} className={cn('-mt-0.5 text-xs font-normal text-destructive', className)} {...props}>{body}</div>;
86
+ }
87
+
88
+ export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
@@ -0,0 +1,14 @@
1
+ import type { LucideIcon } from 'lucide-react';
2
+
3
+ interface IconProps {
4
+ iconNode?: LucideIcon | null;
5
+ className?: string;
6
+ }
7
+
8
+ export function Icon({ iconNode: IconComponent, className }: IconProps) {
9
+ if (!IconComponent) {
10
+ return null;
11
+ }
12
+
13
+ return <IconComponent className={className} />;
14
+ }
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+
3
+ import { OTPInput, OTPInputContext } from "input-otp"
4
+ import { Minus } from "lucide-react"
5
+ import * as React from "react"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ const InputOTP = React.forwardRef<
10
+ React.ElementRef<typeof OTPInput>,
11
+ React.ComponentPropsWithoutRef<typeof OTPInput>
12
+ >(({ className, containerClassName, ...props }, ref) => (
13
+ <OTPInput
14
+ ref={ref}
15
+ containerClassName={cn(
16
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
17
+ containerClassName
18
+ )}
19
+ className={cn("disabled:cursor-not-allowed", className)}
20
+ {...props}
21
+ />
22
+ ))
23
+ InputOTP.displayName = "InputOTP"
24
+
25
+ const InputOTPGroup = React.forwardRef<
26
+ React.ElementRef<"div">,
27
+ React.ComponentPropsWithoutRef<"div">
28
+ >(({ className, ...props }, ref) => (
29
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
30
+ ))
31
+ InputOTPGroup.displayName = "InputOTPGroup"
32
+
33
+ const InputOTPSlot = React.forwardRef<
34
+ React.ElementRef<"div">,
35
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
36
+ >(({ index, className, ...props }, ref) => {
37
+ const inputOTPContext = React.useContext(OTPInputContext)
38
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
39
+
40
+ return (
41
+ <div
42
+ ref={ref}
43
+ className={cn(
44
+ "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
45
+ isActive && "z-10 ring-1 ring-ring",
46
+ className
47
+ )}
48
+ {...props}
49
+ >
50
+ {char}
51
+ {hasFakeCaret && (
52
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
53
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
54
+ </div>
55
+ )}
56
+ </div>
57
+ )
58
+ })
59
+ InputOTPSlot.displayName = "InputOTPSlot"
60
+
61
+ const InputOTPSeparator = React.forwardRef<
62
+ React.ElementRef<"div">,
63
+ React.ComponentPropsWithoutRef<"div">
64
+ >(({ ...props }, ref) => (
65
+ <div ref={ref} role="separator" {...props}>
66
+ <Minus />
67
+ </div>
68
+ ))
69
+ InputOTPSeparator.displayName = "InputOTPSeparator"
70
+
71
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }