@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,351 @@
1
+ /**
2
+ * Custom Google Maps styles.
3
+ *
4
+ * Themes mirror Google's "style selector" reference set
5
+ * (https://developers.google.com/maps/documentation/javascript/examples/style-selector):
6
+ *
7
+ * - default → Google's stock styling (no overrides)
8
+ * - silver → desaturated greys, our standard light theme
9
+ * - night → dark navy with warm-brown accents, our standard dark theme
10
+ * - retro → cream / beige / orange roads with green parks
11
+ * - hiding → tiny overlay that hides business POIs + transit icons
12
+ *
13
+ * `hiding` is intended to be merged on top of one of the other themes
14
+ * rather than used by itself.
15
+ *
16
+ * We use inline style arrays so the theme can flip instantly at runtime
17
+ * (e.g. when the user toggles light/dark) without round-tripping the
18
+ * Google Cloud Console for a cloud-styled map ID.
19
+ */
20
+
21
+ type Style = google.maps.MapTypeStyle;
22
+
23
+ export type MapStyleName = 'default' | 'silver' | 'night' | 'retro' | 'hiding';
24
+
25
+ export const MAP_STYLES: Record<MapStyleName, Style[]> = {
26
+ default: [],
27
+
28
+ silver: [
29
+ { elementType: 'geometry', stylers: [{ color: '#f5f5f5' }] },
30
+ { elementType: 'labels.icon', stylers: [{ visibility: 'off' }] },
31
+ { elementType: 'labels.text.fill', stylers: [{ color: '#616161' }] },
32
+ { elementType: 'labels.text.stroke', stylers: [{ color: '#f5f5f5' }] },
33
+ {
34
+ featureType: 'administrative.land_parcel',
35
+ elementType: 'labels.text.fill',
36
+ stylers: [{ color: '#bdbdbd' }],
37
+ },
38
+ {
39
+ featureType: 'poi',
40
+ elementType: 'geometry',
41
+ stylers: [{ color: '#eeeeee' }],
42
+ },
43
+ {
44
+ featureType: 'poi',
45
+ elementType: 'labels.text.fill',
46
+ stylers: [{ color: '#757575' }],
47
+ },
48
+ {
49
+ featureType: 'poi.park',
50
+ elementType: 'geometry',
51
+ stylers: [{ color: '#e5e5e5' }],
52
+ },
53
+ {
54
+ featureType: 'poi.park',
55
+ elementType: 'labels.text.fill',
56
+ stylers: [{ color: '#9e9e9e' }],
57
+ },
58
+ {
59
+ featureType: 'road',
60
+ elementType: 'geometry',
61
+ stylers: [{ color: '#ffffff' }],
62
+ },
63
+ {
64
+ featureType: 'road.arterial',
65
+ elementType: 'labels.text.fill',
66
+ stylers: [{ color: '#757575' }],
67
+ },
68
+ {
69
+ featureType: 'road.highway',
70
+ elementType: 'geometry',
71
+ stylers: [{ color: '#dadada' }],
72
+ },
73
+ {
74
+ featureType: 'road.highway',
75
+ elementType: 'labels.text.fill',
76
+ stylers: [{ color: '#616161' }],
77
+ },
78
+ {
79
+ featureType: 'road.local',
80
+ elementType: 'labels.text.fill',
81
+ stylers: [{ color: '#9e9e9e' }],
82
+ },
83
+ {
84
+ featureType: 'transit.line',
85
+ elementType: 'geometry',
86
+ stylers: [{ color: '#e5e5e5' }],
87
+ },
88
+ {
89
+ featureType: 'transit.station',
90
+ elementType: 'geometry',
91
+ stylers: [{ color: '#eeeeee' }],
92
+ },
93
+ {
94
+ featureType: 'water',
95
+ elementType: 'geometry',
96
+ stylers: [{ color: '#c9c9c9' }],
97
+ },
98
+ {
99
+ featureType: 'water',
100
+ elementType: 'labels.text.fill',
101
+ stylers: [{ color: '#9e9e9e' }],
102
+ },
103
+ ],
104
+
105
+ night: [
106
+ { elementType: 'geometry', stylers: [{ color: '#242f3e' }] },
107
+ { elementType: 'labels.text.stroke', stylers: [{ color: '#242f3e' }] },
108
+ { elementType: 'labels.text.fill', stylers: [{ color: '#746855' }] },
109
+ {
110
+ featureType: 'administrative.locality',
111
+ elementType: 'labels.text.fill',
112
+ stylers: [{ color: '#d59563' }],
113
+ },
114
+ {
115
+ featureType: 'poi',
116
+ elementType: 'labels.text.fill',
117
+ stylers: [{ color: '#d59563' }],
118
+ },
119
+ {
120
+ featureType: 'poi.park',
121
+ elementType: 'geometry',
122
+ stylers: [{ color: '#263c3f' }],
123
+ },
124
+ {
125
+ featureType: 'poi.park',
126
+ elementType: 'labels.text.fill',
127
+ stylers: [{ color: '#6b9a76' }],
128
+ },
129
+ {
130
+ featureType: 'road',
131
+ elementType: 'geometry',
132
+ stylers: [{ color: '#38414e' }],
133
+ },
134
+ {
135
+ featureType: 'road',
136
+ elementType: 'geometry.stroke',
137
+ stylers: [{ color: '#212a37' }],
138
+ },
139
+ {
140
+ featureType: 'road',
141
+ elementType: 'labels.text.fill',
142
+ stylers: [{ color: '#9ca5b3' }],
143
+ },
144
+ {
145
+ featureType: 'road.highway',
146
+ elementType: 'geometry',
147
+ stylers: [{ color: '#746855' }],
148
+ },
149
+ {
150
+ featureType: 'road.highway',
151
+ elementType: 'geometry.stroke',
152
+ stylers: [{ color: '#1f2835' }],
153
+ },
154
+ {
155
+ featureType: 'road.highway',
156
+ elementType: 'labels.text.fill',
157
+ stylers: [{ color: '#f3d19c' }],
158
+ },
159
+ {
160
+ featureType: 'transit',
161
+ elementType: 'geometry',
162
+ stylers: [{ color: '#2f3948' }],
163
+ },
164
+ {
165
+ featureType: 'transit.station',
166
+ elementType: 'labels.text.fill',
167
+ stylers: [{ color: '#d59563' }],
168
+ },
169
+ {
170
+ featureType: 'water',
171
+ elementType: 'geometry',
172
+ stylers: [{ color: '#17263c' }],
173
+ },
174
+ {
175
+ featureType: 'water',
176
+ elementType: 'labels.text.fill',
177
+ stylers: [{ color: '#515c6d' }],
178
+ },
179
+ {
180
+ featureType: 'water',
181
+ elementType: 'labels.text.stroke',
182
+ stylers: [{ color: '#17263c' }],
183
+ },
184
+ ],
185
+
186
+ retro: [
187
+ { elementType: 'geometry', stylers: [{ color: '#ebe3cd' }] },
188
+ { elementType: 'labels.text.fill', stylers: [{ color: '#523735' }] },
189
+ { elementType: 'labels.text.stroke', stylers: [{ color: '#f5f1e6' }] },
190
+ {
191
+ featureType: 'administrative',
192
+ elementType: 'geometry.stroke',
193
+ stylers: [{ color: '#c9b2a6' }],
194
+ },
195
+ {
196
+ featureType: 'administrative.land_parcel',
197
+ elementType: 'geometry.stroke',
198
+ stylers: [{ color: '#dcd2be' }],
199
+ },
200
+ {
201
+ featureType: 'administrative.land_parcel',
202
+ elementType: 'labels.text.fill',
203
+ stylers: [{ color: '#ae9e90' }],
204
+ },
205
+ {
206
+ featureType: 'landscape.natural',
207
+ elementType: 'geometry',
208
+ stylers: [{ color: '#dfd2ae' }],
209
+ },
210
+ {
211
+ featureType: 'poi',
212
+ elementType: 'geometry',
213
+ stylers: [{ color: '#dfd2ae' }],
214
+ },
215
+ {
216
+ featureType: 'poi',
217
+ elementType: 'labels.text.fill',
218
+ stylers: [{ color: '#93817c' }],
219
+ },
220
+ {
221
+ featureType: 'poi.park',
222
+ elementType: 'geometry.fill',
223
+ stylers: [{ color: '#a5b076' }],
224
+ },
225
+ {
226
+ featureType: 'poi.park',
227
+ elementType: 'labels.text.fill',
228
+ stylers: [{ color: '#447530' }],
229
+ },
230
+ {
231
+ featureType: 'road',
232
+ elementType: 'geometry',
233
+ stylers: [{ color: '#f5f1e6' }],
234
+ },
235
+ {
236
+ featureType: 'road.arterial',
237
+ elementType: 'geometry',
238
+ stylers: [{ color: '#fdfcf8' }],
239
+ },
240
+ {
241
+ featureType: 'road.highway',
242
+ elementType: 'geometry',
243
+ stylers: [{ color: '#f8c967' }],
244
+ },
245
+ {
246
+ featureType: 'road.highway',
247
+ elementType: 'geometry.stroke',
248
+ stylers: [{ color: '#e9bc62' }],
249
+ },
250
+ {
251
+ featureType: 'road.highway.controlled_access',
252
+ elementType: 'geometry',
253
+ stylers: [{ color: '#e98d58' }],
254
+ },
255
+ {
256
+ featureType: 'road.highway.controlled_access',
257
+ elementType: 'geometry.stroke',
258
+ stylers: [{ color: '#db8555' }],
259
+ },
260
+ {
261
+ featureType: 'road.local',
262
+ elementType: 'labels.text.fill',
263
+ stylers: [{ color: '#806b63' }],
264
+ },
265
+ {
266
+ featureType: 'transit.line',
267
+ elementType: 'geometry',
268
+ stylers: [{ color: '#dfd2ae' }],
269
+ },
270
+ {
271
+ featureType: 'transit.line',
272
+ elementType: 'labels.text.fill',
273
+ stylers: [{ color: '#8f7d77' }],
274
+ },
275
+ {
276
+ featureType: 'transit.line',
277
+ elementType: 'labels.text.stroke',
278
+ stylers: [{ color: '#ebe3cd' }],
279
+ },
280
+ {
281
+ featureType: 'transit.station',
282
+ elementType: 'geometry',
283
+ stylers: [{ color: '#dfd2ae' }],
284
+ },
285
+ {
286
+ featureType: 'water',
287
+ elementType: 'geometry.fill',
288
+ stylers: [{ color: '#b9d3c2' }],
289
+ },
290
+ {
291
+ featureType: 'water',
292
+ elementType: 'labels.text.fill',
293
+ stylers: [{ color: '#92998d' }],
294
+ },
295
+ ],
296
+
297
+ hiding: [
298
+ {
299
+ featureType: 'poi.business',
300
+ stylers: [{ visibility: 'off' }],
301
+ },
302
+ {
303
+ featureType: 'transit',
304
+ elementType: 'labels.icon',
305
+ stylers: [{ visibility: 'off' }],
306
+ },
307
+ ],
308
+ };
309
+
310
+ /** Returns the style array for a named theme, or an empty array if unknown. */
311
+ export function mapStyleByName(name: MapStyleName): Style[] {
312
+ return MAP_STYLES[name] ?? [];
313
+ }
314
+
315
+ /**
316
+ * Picks the right style array for the current `<html class="dark">` state.
317
+ * Dark mode → night, light mode → retro.
318
+ */
319
+ export function mapStyleForAppearance(isDark: boolean): Style[] {
320
+ return isDark ? MAP_STYLES.night : MAP_STYLES.retro;
321
+ }
322
+
323
+ /**
324
+ * Subscribes to dark-mode changes by watching the `dark` class on the
325
+ * root `<html>` element. Returns an unsubscribe function. The callback
326
+ * receives the current `isDark` value.
327
+ */
328
+ export function watchDarkMode(callback: (isDark: boolean) => void): () => void {
329
+ if (typeof document === 'undefined') {
330
+ return () => {};
331
+ }
332
+
333
+ const observer = new MutationObserver(() => {
334
+ callback(document.documentElement.classList.contains('dark'));
335
+ });
336
+ observer.observe(document.documentElement, {
337
+ attributes: true,
338
+ attributeFilter: ['class'],
339
+ });
340
+
341
+ return () => observer.disconnect();
342
+ }
343
+
344
+ /** Reads the current `dark` class on the root `<html>` element. */
345
+ export function isDarkMode(): boolean {
346
+ if (typeof document === 'undefined') {
347
+ return false;
348
+ }
349
+
350
+ return document.documentElement.classList.contains('dark');
351
+ }
@@ -0,0 +1,11 @@
1
+ import type { PlatformLinkProps } from '../platform/types';
2
+ import { clsx, type ClassValue } from 'clsx';
3
+ import { twMerge } from 'tailwind-merge';
4
+
5
+ export function cn(...inputs: ClassValue[]) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+
9
+ export function toUrl(url: NonNullable<PlatformLinkProps['href']>): string {
10
+ return url;
11
+ }
@@ -0,0 +1,156 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useRef, useState } from 'react';
4
+ import type {
5
+ FormSubmitOptions,
6
+ NavigateOptions,
7
+ PlatformAdapter,
8
+ PlatformForm,
9
+ PlatformLinkProps,
10
+ } from '../types';
11
+
12
+ // ─── Link ────────────────────────────────────────────────────────────────────
13
+
14
+ function DefaultLink({ href, children, preserveScroll: _ps, replace: _r, ...props }: PlatformLinkProps) {
15
+ return <a href={href} {...props}>{children}</a>;
16
+ }
17
+
18
+ // ─── navigate ────────────────────────────────────────────────────────────────
19
+
20
+ function defaultNavigate(href: string, options?: NavigateOptions) {
21
+ if (typeof window === 'undefined') return;
22
+ if (options?.replace) {
23
+ window.location.replace(href);
24
+ } else {
25
+ window.location.href = href;
26
+ }
27
+ }
28
+
29
+ // ─── useCurrentUrl ───────────────────────────────────────────────────────────
30
+
31
+ function useDefaultCurrentUrl() {
32
+ if (typeof window === 'undefined') return '';
33
+ return window.location.pathname + window.location.search;
34
+ }
35
+
36
+ // ─── usePageProps ─────────────────────────────────────────────────────────────
37
+
38
+ function useDefaultPageProps<T extends Record<string, unknown>>(): T {
39
+ return {} as T;
40
+ }
41
+
42
+ // ─── useForm ─────────────────────────────────────────────────────────────────
43
+
44
+ function useFetchForm<T extends Record<string, unknown>>(initialData: T): PlatformForm<T> {
45
+ const [data, setDataState] = useState<T>(initialData);
46
+ const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
47
+ const [processing, setProcessing] = useState(false);
48
+ const [wasSuccessful, setWasSuccessful] = useState(false);
49
+ const [recentlySuccessful, setRecentlySuccessful] = useState(false);
50
+ const recentTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
51
+
52
+ const initialRef = useRef(initialData);
53
+
54
+ const setData: PlatformForm<T>['setData'] = useCallback((fieldOrValues: keyof T | Partial<T>, value?: unknown) => {
55
+ if (typeof fieldOrValues === 'string') {
56
+ setDataState((prev) => ({ ...prev, [fieldOrValues]: value }));
57
+ } else {
58
+ setDataState((prev) => ({ ...prev, ...(fieldOrValues as Partial<T>) }));
59
+ }
60
+ }, []);
61
+
62
+ const reset = useCallback((...fields: (keyof T)[]) => {
63
+ if (fields.length === 0) {
64
+ setDataState(initialRef.current);
65
+ } else {
66
+ setDataState((prev) => {
67
+ const next = { ...prev };
68
+ fields.forEach((f) => { next[f] = initialRef.current[f]; });
69
+ return next;
70
+ });
71
+ }
72
+ setErrors({});
73
+ }, []);
74
+
75
+ const clearErrors = useCallback((...fields: (keyof T)[]) => {
76
+ if (fields.length === 0) {
77
+ setErrors({});
78
+ } else {
79
+ setErrors((prev) => {
80
+ const next = { ...prev };
81
+ fields.forEach((f) => { delete next[f]; });
82
+ return next;
83
+ });
84
+ }
85
+ }, []);
86
+
87
+ const setError = useCallback((field: keyof T, message: string) => {
88
+ setErrors((prev) => ({ ...prev, [field]: message }));
89
+ }, []);
90
+
91
+ async function submit(method: string, url: string, options?: FormSubmitOptions<T>) {
92
+ setProcessing(true);
93
+ setWasSuccessful(false);
94
+ setErrors({});
95
+ try {
96
+ const res = await fetch(url, {
97
+ method: method.toUpperCase(),
98
+ headers: {
99
+ 'Content-Type': 'application/json',
100
+ Accept: 'application/json',
101
+ 'X-Requested-With': 'XMLHttpRequest',
102
+ },
103
+ body: JSON.stringify(data),
104
+ });
105
+
106
+ const json = await res.json().catch(() => ({}));
107
+
108
+ if (!res.ok) {
109
+ const serverErrors = (json?.errors ?? {}) as Partial<Record<keyof T, string>>;
110
+ setErrors(serverErrors);
111
+ options?.onError?.(serverErrors);
112
+ } else {
113
+ setWasSuccessful(true);
114
+ setRecentlySuccessful(true);
115
+ if (recentTimer.current) clearTimeout(recentTimer.current);
116
+ recentTimer.current = setTimeout(() => setRecentlySuccessful(false), 2000);
117
+ options?.onSuccess?.(json);
118
+ }
119
+ } catch {
120
+ const networkError = { _network: 'Network error. Please try again.' } as Partial<Record<keyof T, string>>;
121
+ setErrors(networkError);
122
+ options?.onError?.(networkError);
123
+ } finally {
124
+ setProcessing(false);
125
+ options?.onFinish?.();
126
+ }
127
+ }
128
+
129
+ return {
130
+ data,
131
+ setData,
132
+ errors,
133
+ processing,
134
+ wasSuccessful,
135
+ recentlySuccessful,
136
+ isDirty: JSON.stringify(data) !== JSON.stringify(initialRef.current),
137
+ post: (url, opts) => submit('POST', url, opts),
138
+ put: (url, opts) => submit('PUT', url, opts),
139
+ patch: (url, opts) => submit('PATCH', url, opts),
140
+ delete: (url, opts) => submit('DELETE', url, opts),
141
+ reset,
142
+ clearErrors,
143
+ setError,
144
+ };
145
+ }
146
+
147
+ // ─── Adapter ─────────────────────────────────────────────────────────────────
148
+
149
+ export const defaultAdapter: PlatformAdapter = {
150
+ Link: DefaultLink,
151
+ navigate: defaultNavigate,
152
+ useCurrentUrl: useDefaultCurrentUrl,
153
+ usePageProps: useDefaultPageProps,
154
+ useForm: useFetchForm,
155
+ Head: null,
156
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Inertia.js Platform Adapter Factory
3
+ *
4
+ * Call this ONCE in the consuming app's entry point (app.tsx / bootstrap.tsx)
5
+ * and pass the result to <PlatformProvider adapter={...}>.
6
+ *
7
+ * The factory accepts Inertia's primitives so packages/node never imports
8
+ * @inertiajs/react directly — zero coupling.
9
+ *
10
+ * Usage (core/ or login/ resources/js/app.tsx):
11
+ *
12
+ * import { Link, usePage, useForm, Head, router } from '@inertiajs/react';
13
+ * import { createInertiaAdapter, PlatformProvider } from '@trackany-device/components';
14
+ *
15
+ * const adapter = createInertiaAdapter({ Link, usePage, useForm, Head, router });
16
+ *
17
+ * createInertiaApp({
18
+ * resolve: ...,
19
+ * setup({ el, App, props }) {
20
+ * createRoot(el).render(
21
+ * <PlatformProvider adapter={adapter}><App {...props} /></PlatformProvider>
22
+ * );
23
+ * },
24
+ * });
25
+ */
26
+
27
+ import type { ComponentType } from 'react';
28
+ import type { PlatformAdapter, PlatformLinkProps, PlatformForm, FormSubmitOptions, NavigateOptions } from '../types';
29
+
30
+ type InertiaLink = ComponentType<PlatformLinkProps>;
31
+
32
+ interface InertiaRouter {
33
+ visit: (url: string, options?: { replace?: boolean; preserveScroll?: boolean; preserveState?: boolean }) => void;
34
+ }
35
+
36
+ interface InertiaPageObject {
37
+ props: Record<string, unknown>;
38
+ url: string;
39
+ }
40
+
41
+ interface InertiaDeps {
42
+ /** Inertia's <Link> component */
43
+ Link: InertiaLink;
44
+ /** Inertia's usePage hook */
45
+ usePage: () => InertiaPageObject;
46
+ /** Inertia's useForm hook */
47
+ useForm: <T extends Record<string, unknown>>(initialData: T) => PlatformForm<T>;
48
+ /** Inertia's <Head> component (optional — pass null if not needed) */
49
+ Head: ComponentType<{ title?: string }> | null;
50
+ /** Inertia's router instance */
51
+ router: InertiaRouter;
52
+ }
53
+
54
+ export function createInertiaAdapter({
55
+ Link,
56
+ usePage,
57
+ useForm,
58
+ Head,
59
+ router,
60
+ }: InertiaDeps): PlatformAdapter {
61
+ return {
62
+ Link,
63
+
64
+ navigate(href: string, options?: NavigateOptions) {
65
+ router.visit(href, {
66
+ replace: options?.replace,
67
+ preserveScroll: options?.preserveScroll,
68
+ preserveState: options?.preserveState,
69
+ });
70
+ },
71
+
72
+ useCurrentUrl() {
73
+ return usePage().url;
74
+ },
75
+
76
+ usePageProps<T extends Record<string, unknown>>(): T {
77
+ return usePage().props as T;
78
+ },
79
+
80
+ useForm<T extends Record<string, unknown>>(initialData: T): PlatformForm<T> {
81
+ return useForm<T>(initialData);
82
+ },
83
+
84
+ Head: Head
85
+ ? (({ title, description: _d }) => <Head title={title} />) as ComponentType<{ title?: string; description?: string }>
86
+ : null,
87
+ };
88
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Next.js Platform Adapter Factory
3
+ *
4
+ * Call this ONCE in the consuming app's root layout and pass the result
5
+ * to <PlatformProvider adapter={...}>.
6
+ *
7
+ * The factory accepts Next.js primitives so packages/node never imports
8
+ * next/link or next/navigation directly — zero coupling.
9
+ *
10
+ * Usage (web/ app/layout.tsx):
11
+ *
12
+ * 'use client';
13
+ * import Link from 'next/link';
14
+ * import { useRouter, usePathname } from 'next/navigation';
15
+ * import { createNextjsAdapter, PlatformProvider } from '@trackany-device/components';
16
+ *
17
+ * export default function ClientProviders({ children, pageProps }) {
18
+ * const adapter = createNextjsAdapter({ Link, useRouter, usePathname, pageProps });
19
+ * return <PlatformProvider adapter={adapter}>{children}</PlatformProvider>;
20
+ * }
21
+ *
22
+ * Note: usePageProps() in the Next.js adapter reads from `pageProps` passed
23
+ * to the factory, NOT from a server context. For shared data (auth, nav_links
24
+ * etc.) fetch them server-side and pass them in as pageProps.
25
+ */
26
+
27
+ import { useRef } from 'react';
28
+ import type { ComponentType } from 'react';
29
+ import type { PlatformAdapter, PlatformLinkProps, PlatformForm, NavigateOptions } from '../types';
30
+ import { defaultAdapter } from './default';
31
+
32
+ interface NextjsRouter {
33
+ push: (href: string) => void;
34
+ replace: (href: string) => void;
35
+ }
36
+
37
+ interface NextjsDeps {
38
+ /** next/link's <Link> component */
39
+ Link: ComponentType<PlatformLinkProps>;
40
+ /** next/navigation's useRouter hook */
41
+ useRouter: () => NextjsRouter;
42
+ /** next/navigation's usePathname hook */
43
+ usePathname: () => string;
44
+ /**
45
+ * Shared page props (auth, nav_links, etc.) — equivalent to Inertia's
46
+ * shared props. Pass them from the server component down via props.
47
+ */
48
+ pageProps?: Record<string, unknown>;
49
+ }
50
+
51
+ export function createNextjsAdapter({
52
+ Link,
53
+ useRouter,
54
+ usePathname,
55
+ pageProps = {},
56
+ }: NextjsDeps): PlatformAdapter {
57
+ const frozenProps = pageProps;
58
+
59
+ return {
60
+ Link,
61
+
62
+ navigate(href: string, options?: NavigateOptions) {
63
+ const router = useRouter();
64
+ if (options?.replace) {
65
+ router.replace(href);
66
+ } else {
67
+ router.push(href);
68
+ }
69
+ },
70
+
71
+ useCurrentUrl() {
72
+ return usePathname();
73
+ },
74
+
75
+ usePageProps<T extends Record<string, unknown>>(): T {
76
+ return frozenProps as T;
77
+ },
78
+
79
+ // useForm uses the default fetch-based implementation — same interface as Inertia.
80
+ useForm: defaultAdapter.useForm,
81
+
82
+ // Next.js manages document head via the `metadata` export on server components,
83
+ // not via a client component. PlatformHead renders nothing here.
84
+ Head: null,
85
+ };
86
+ }