@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,138 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { cn } from '../../lib/utils';
5
+ import { Dialog, DialogContent, DialogTitle } from './dialog';
6
+ import { type DialogProps } from '@radix-ui/react-dialog';
7
+ import { Command as CommandPrimitive } from 'cmdk';
8
+ import { Check, LucideIcon, Search } from 'lucide-react';
9
+
10
+ function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
11
+ return (
12
+ <CommandPrimitive
13
+ className={cn(
14
+ 'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ type CommandDialogProps = DialogProps & { className?: string };
23
+
24
+ const CommandDialog = ({ children, className, ...props }: CommandDialogProps) => {
25
+ return (
26
+ <Dialog {...props}>
27
+ <DialogContent className={cn('overflow-hidden p-0 shadow-lg', className)}>
28
+ <DialogTitle className="hidden" />
29
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
30
+ {children}
31
+ </Command>
32
+ </DialogContent>
33
+ </Dialog>
34
+ );
35
+ };
36
+
37
+ function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
38
+ return (
39
+ <div className="flex items-center border-border border-b px-3" cmdk-input-wrapper="" data-slot="command-input">
40
+ <Search className="me-2 h-4 w-4 shrink-0 opacity-50" />
41
+ <CommandPrimitive.Input
42
+ className={cn(
43
+ 'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-hidden text-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ </div>
49
+ );
50
+ }
51
+
52
+ function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
53
+ return (
54
+ <CommandPrimitive.List
55
+ data-slot="command-list"
56
+ className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
57
+ {...props}
58
+ />
59
+ );
60
+ }
61
+
62
+ function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
63
+ return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />;
64
+ }
65
+
66
+ function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
67
+ return (
68
+ <CommandPrimitive.Group
69
+ data-slot="command-group"
70
+ className={cn(
71
+ 'overflow-hidden p-1.5 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
72
+ className,
73
+ )}
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
80
+ return (
81
+ <CommandPrimitive.Separator
82
+ data-slot="command-separator"
83
+ className={cn('-mx-1.5 h-px bg-border', className)}
84
+ {...props}
85
+ />
86
+ );
87
+ }
88
+
89
+ function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
90
+ return (
91
+ <CommandPrimitive.Item
92
+ data-slot="command-item"
93
+ className={cn(
94
+ 'relative flex text-foreground cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
95
+ className,
96
+ )}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+
102
+ const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
103
+ return (
104
+ <span
105
+ data-slot="command-shortcut"
106
+ className={cn('ms-auto text-xs tracking-widest text-muted-foreground', className)}
107
+ {...props}
108
+ />
109
+ );
110
+ };
111
+
112
+ interface ButtonArrowProps extends React.SVGProps<SVGSVGElement> {
113
+ icon?: LucideIcon; // Allows passing any Lucide icon
114
+ }
115
+
116
+ function CommandCheck({ icon: Icon = Check, className, ...props }: ButtonArrowProps) {
117
+ return (
118
+ <Icon
119
+ data-slot="command-check"
120
+ data-check="true"
121
+ className={cn('size-4 ms-auto text-primary', className)}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ export {
128
+ Command,
129
+ CommandCheck,
130
+ CommandDialog,
131
+ CommandEmpty,
132
+ CommandGroup,
133
+ CommandInput,
134
+ CommandItem,
135
+ CommandList,
136
+ CommandSeparator,
137
+ CommandShortcut,
138
+ };
@@ -0,0 +1,220 @@
1
+ 'use client';
2
+ import { PlatformLink } from '../../platform/context';
3
+
4
+ /**
5
+ * CookieBanner — first-visit consent prompt.
6
+ *
7
+ * Shows once until a choice is recorded in localStorage under
8
+ * `cookie-consent`. The recorded value is a JSON object:
9
+ *
10
+ * { essential: true, analytics: bool, marketing: bool, decidedAt: ISO }
11
+ *
12
+ * Essential cookies (session, CSRF, theme prefs) are always on — we do
13
+ * not collect any analytics or marketing data today, but the choice is
14
+ * captured so a future opt-in is honoured.
15
+ *
16
+ * COMPLIANCE: Whenever this banner changes wording, the cookie categories,
17
+ * or the default values, ship a new PolicyVersionSeeder_{date}.php that
18
+ * bumps the cookie policy version. See PolicyVersion.php for the rule.
19
+ *
20
+ * NOTE: This component imports from @inertiajs/react which must be
21
+ * available in the consuming app.
22
+ */
23
+ import { Cookie } from 'lucide-react';
24
+ import * as React from 'react';
25
+
26
+ import { Button } from './button';
27
+ import {
28
+ Dialog,
29
+ DialogContent,
30
+ DialogDescription,
31
+ DialogFooter,
32
+ DialogHeader,
33
+ DialogTitle,
34
+ } from './dialog';
35
+ import { Toggle } from './toggle';
36
+
37
+ const STORAGE_KEY = 'cookie-consent';
38
+
39
+ type Consent = {
40
+ essential: true;
41
+ analytics: boolean;
42
+ marketing: boolean;
43
+ decidedAt: string;
44
+ };
45
+
46
+ function readStored(): Consent | null {
47
+ if (typeof window === 'undefined') return null;
48
+ try {
49
+ const raw = window.localStorage.getItem(STORAGE_KEY);
50
+ if (!raw) return null;
51
+ const parsed = JSON.parse(raw) as Consent;
52
+ return parsed.essential === true && parsed.decidedAt ? parsed : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function write(consent: Consent) {
59
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
60
+ window.dispatchEvent(
61
+ new CustomEvent('cookie-consent:changed', { detail: consent }),
62
+ );
63
+ }
64
+
65
+ export function CookieBanner() {
66
+ const [decided, setDecided] = React.useState(true);
67
+ const [managing, setManaging] = React.useState(false);
68
+ const [analytics, setAnalytics] = React.useState(false);
69
+ const [marketing, setMarketing] = React.useState(false);
70
+
71
+ React.useEffect(() => {
72
+ const stored = readStored();
73
+ if (!stored) {
74
+ setDecided(false);
75
+ }
76
+ }, []);
77
+
78
+ if (decided) return null;
79
+
80
+ const persist = (next: { analytics: boolean; marketing: boolean }) => {
81
+ write({
82
+ essential: true,
83
+ analytics: next.analytics,
84
+ marketing: next.marketing,
85
+ decidedAt: new Date().toISOString(),
86
+ });
87
+ setDecided(true);
88
+ };
89
+
90
+ const acceptAll = () => persist({ analytics: true, marketing: true });
91
+ const rejectAll = () => persist({ analytics: false, marketing: false });
92
+ const saveCustom = () => {
93
+ persist({ analytics, marketing });
94
+ setManaging(false);
95
+ };
96
+
97
+ return (
98
+ <>
99
+ <div
100
+ role="dialog"
101
+ aria-label="Cookie preferences"
102
+ className="fixed inset-x-4 bottom-4 z-50 mx-auto max-w-3xl rounded-xl border bg-surface-raised p-5 shadow-lg sm:p-6"
103
+ >
104
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-start">
105
+ <div className="bg-primary-subtle text-primary flex h-10 w-10 shrink-0 items-center justify-center rounded-lg">
106
+ <Cookie aria-hidden="true" className="size-5" />
107
+ </div>
108
+ <div className="flex-1">
109
+ <h2 className="font-display text-sm font-semibold text-foreground">
110
+ We use cookies to make this platform work
111
+ </h2>
112
+ <p className="mt-1 text-sm text-muted-foreground">
113
+ Essential cookies keep you logged in and protect
114
+ against CSRF. With your consent, we may add
115
+ analytics later. See our{' '}
116
+ <PlatformLink
117
+ href="/cookies"
118
+ target="_blank"
119
+ className="text-primary underline-offset-2 hover:underline"
120
+ >
121
+ Cookie Policy
122
+ </PlatformLink>{' '}
123
+ for details.
124
+ </p>
125
+ <div className="mt-4 flex flex-wrap items-center gap-2">
126
+ <Button size="sm" onClick={acceptAll}>
127
+ Accept all
128
+ </Button>
129
+ <Button
130
+ size="sm"
131
+ variant="outline"
132
+ onClick={rejectAll}
133
+ >
134
+ Reject non-essential
135
+ </Button>
136
+ <Button
137
+ size="sm"
138
+ variant="ghost"
139
+ onClick={() => setManaging(true)}
140
+ >
141
+ Manage preferences
142
+ </Button>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+
148
+ <Dialog open={managing} onOpenChange={setManaging}>
149
+ <DialogContent className="sm:max-w-md">
150
+ <DialogHeader>
151
+ <DialogTitle>Cookie preferences</DialogTitle>
152
+ <DialogDescription>
153
+ Choose which non-essential cookies to allow. You
154
+ can change these later at any time.
155
+ </DialogDescription>
156
+ </DialogHeader>
157
+ <ul className="space-y-3 py-2">
158
+ <li className="flex items-start justify-between gap-3 rounded-lg border p-3">
159
+ <div>
160
+ <p className="text-sm font-medium text-foreground">
161
+ Essential
162
+ </p>
163
+ <p className="text-xs text-muted-foreground">
164
+ Session, CSRF, theme preferences. Always
165
+ on.
166
+ </p>
167
+ </div>
168
+ <Toggle pressed disabled aria-label="Essential cookies are always on">
169
+ On
170
+ </Toggle>
171
+ </li>
172
+ <li className="flex items-start justify-between gap-3 rounded-lg border p-3">
173
+ <div>
174
+ <p className="text-sm font-medium text-foreground">
175
+ Analytics
176
+ </p>
177
+ <p className="text-xs text-muted-foreground">
178
+ Helps us understand usage patterns. No
179
+ personal data is shared with third
180
+ parties.
181
+ </p>
182
+ </div>
183
+ <Toggle
184
+ pressed={analytics}
185
+ onPressedChange={setAnalytics}
186
+ aria-label="Toggle analytics cookies"
187
+ >
188
+ {analytics ? 'On' : 'Off'}
189
+ </Toggle>
190
+ </li>
191
+ <li className="flex items-start justify-between gap-3 rounded-lg border p-3">
192
+ <div>
193
+ <p className="text-sm font-medium text-foreground">
194
+ Marketing
195
+ </p>
196
+ <p className="text-xs text-muted-foreground">
197
+ Currently unused. Reserved for future
198
+ feature announcements.
199
+ </p>
200
+ </div>
201
+ <Toggle
202
+ pressed={marketing}
203
+ onPressedChange={setMarketing}
204
+ aria-label="Toggle marketing cookies"
205
+ >
206
+ {marketing ? 'On' : 'Off'}
207
+ </Toggle>
208
+ </li>
209
+ </ul>
210
+ <DialogFooter>
211
+ <Button variant="ghost" onClick={() => setManaging(false)}>
212
+ Cancel
213
+ </Button>
214
+ <Button onClick={saveCustom}>Save preferences</Button>
215
+ </DialogFooter>
216
+ </DialogContent>
217
+ </Dialog>
218
+ </>
219
+ );
220
+ }
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * CopyButton — one-tap clipboard copy with visual ack.
5
+ *
6
+ * <CopyButton value='P901-00042' />
7
+ *
8
+ * Shows a check icon for 1.5s after copying. Falls back gracefully when
9
+ * navigator.clipboard isn't available (older browsers / insecure context).
10
+ */
11
+ import { Check, Copy } from 'lucide-react';
12
+ import * as React from 'react';
13
+
14
+ import { Button } from './button';
15
+ import { cn } from '../../lib/utils';
16
+
17
+ type Props = React.ComponentProps<typeof Button> & {
18
+ value: string;
19
+ label?: string;
20
+ };
21
+
22
+ export function CopyButton({
23
+ value,
24
+ label = 'Copy',
25
+ className,
26
+ variant = 'ghost',
27
+ size = 'icon',
28
+ ...rest
29
+ }: Props) {
30
+ const [copied, setCopied] = React.useState(false);
31
+
32
+ const copy = React.useCallback(async () => {
33
+ try {
34
+ await navigator.clipboard.writeText(value);
35
+ setCopied(true);
36
+ window.setTimeout(() => setCopied(false), 1500);
37
+ } catch {
38
+ // Insecure context / no permission — silently fail; consumer
39
+ // can offer a manual fallback if needed.
40
+ }
41
+ }, [value]);
42
+
43
+ return (
44
+ <Button
45
+ type="button"
46
+ variant={variant}
47
+ size={size}
48
+ aria-label={copied ? 'Copied' : label}
49
+ className={cn(className)}
50
+ onClick={copy}
51
+ {...rest}
52
+ >
53
+ {copied ? (
54
+ <Check aria-hidden="true" className="text-success" />
55
+ ) : (
56
+ <Copy aria-hidden="true" />
57
+ )}
58
+ </Button>
59
+ );
60
+ }
@@ -0,0 +1,124 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../lib/utils';
3
+ import { Badge } from './badge';
4
+ import { Button } from '../../controls/Button';
5
+ import {
6
+ Command,
7
+ CommandEmpty,
8
+ CommandGroup,
9
+ CommandInput,
10
+ CommandItem,
11
+ CommandList,
12
+ CommandSeparator,
13
+ } from './command';
14
+ import { Popover, PopoverContent, PopoverTrigger } from './popover';
15
+ import { Separator } from './separator';
16
+ import { Column } from '@tanstack/react-table';
17
+ import { Check, CirclePlus } from 'lucide-react';
18
+
19
+ interface DataGridColumnFilterProps<TData, TValue> {
20
+ column?: Column<TData, TValue>;
21
+ title?: string;
22
+ options: {
23
+ label: string;
24
+ value: string;
25
+ icon?: React.ComponentType<{ className?: string }>;
26
+ }[];
27
+ }
28
+
29
+ function DataGridColumnFilter<TData, TValue>({ column, title, options }: DataGridColumnFilterProps<TData, TValue>) {
30
+ const facets = column?.getFacetedUniqueValues();
31
+ const selectedValues = new Set(column?.getFilterValue() as string[]);
32
+
33
+ return (
34
+ <Popover>
35
+ <PopoverTrigger asChild>
36
+ <Button variant="outline" size="sm">
37
+ <CirclePlus className="size-4" />
38
+ {title}
39
+ {selectedValues?.size > 0 && (
40
+ <>
41
+ <Separator orientation="vertical" className="mx-2 h-4" />
42
+ <Badge variant="secondary" className="rounded-sm px-1 font-normal lg:hidden">
43
+ {selectedValues.size}
44
+ </Badge>
45
+ <div className="hidden space-x-1 lg:flex">
46
+ {selectedValues.size > 2 ? (
47
+ <Badge variant="secondary" className="rounded-sm px-1 font-normal">
48
+ {selectedValues.size} selected
49
+ </Badge>
50
+ ) : (
51
+ options
52
+ .filter((option) => selectedValues.has(option.value))
53
+ .map((option) => (
54
+ <Badge variant="secondary" key={option.value} className="rounded-sm px-1 font-normal">
55
+ {option.label}
56
+ </Badge>
57
+ ))
58
+ )}
59
+ </div>
60
+ </>
61
+ )}
62
+ </Button>
63
+ </PopoverTrigger>
64
+ <PopoverContent className="w-[200px] p-0" align="start">
65
+ <Command>
66
+ <CommandInput placeholder={title} />
67
+ <CommandList>
68
+ <CommandEmpty>No results found.</CommandEmpty>
69
+ <CommandGroup>
70
+ {options.map((option) => {
71
+ const isSelected = selectedValues.has(option.value);
72
+ return (
73
+ <CommandItem
74
+ key={option.value}
75
+ onSelect={() => {
76
+ if (isSelected) {
77
+ selectedValues.delete(option.value);
78
+ } else {
79
+ selectedValues.add(option.value);
80
+ }
81
+ const filterValues = Array.from(selectedValues);
82
+ column?.setFilterValue(filterValues.length ? filterValues : undefined);
83
+ }}
84
+ >
85
+ <div
86
+ className={cn(
87
+ 'me-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
88
+ isSelected ? 'bg-primary text-primary-foreground' : 'opacity-50 [&_svg]:invisible',
89
+ )}
90
+ >
91
+ <Check className={cn('h-4 w-4')} />
92
+ </div>
93
+ {option.icon && <option.icon className="mr-2 h-4 w-4 text-muted-foreground" />}
94
+ <span>{option.label}</span>
95
+ {facets?.get(option.value) && (
96
+ <span className="ms-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
97
+ {facets.get(option.value)}
98
+ </span>
99
+ )}
100
+ </CommandItem>
101
+ );
102
+ })}
103
+ </CommandGroup>
104
+ {selectedValues.size > 0 && (
105
+ <>
106
+ <CommandSeparator />
107
+ <CommandGroup>
108
+ <CommandItem
109
+ onSelect={() => column?.setFilterValue(undefined)}
110
+ className="justify-center text-center"
111
+ >
112
+ Clear filters
113
+ </CommandItem>
114
+ </CommandGroup>
115
+ </>
116
+ )}
117
+ </CommandList>
118
+ </Command>
119
+ </PopoverContent>
120
+ </Popover>
121
+ );
122
+ }
123
+
124
+ export { DataGridColumnFilter, type DataGridColumnFilterProps };