@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,280 @@
1
+ import { PlatformLink } from '../../platform/context';
2
+ /**
3
+ * DocsShell — reusable docs layout used by every doc series.
4
+ *
5
+ * Matches the TAD101 docs visual language (sidebar with section list,
6
+ * prose article in the main column, prev/next pager at the foot). Each
7
+ * doc series passes its own sections + active id; the shell takes care
8
+ * of the chrome.
9
+ *
10
+ * Existing TAD101 pages keep their dedicated DocsLayout (which is a
11
+ * specialised wrapper around this shell) so changes here can't break
12
+ * those pages until they're migrated.
13
+ */
14
+ import {
15
+ AlertTriangle,
16
+ ArrowLeft,
17
+ ArrowRight,
18
+ CheckCircle2,
19
+ Info,
20
+ } from 'lucide-react';
21
+ import type { LucideIcon } from 'lucide-react';
22
+
23
+ import { Button } from '../ui/button';
24
+ import { Card } from '../ui/card';
25
+ import { CopyButton } from '../ui/copy-button';
26
+ import { cn } from '../../lib/utils';
27
+
28
+ export type DocsSection<TId extends string = string> = {
29
+ id: TId;
30
+ title: string;
31
+ href: string;
32
+ icon: LucideIcon;
33
+ };
34
+
35
+ type Props<TId extends string> = {
36
+ /** Short brand label rendered in the sidebar header (e.g. "TAD101", "Tenant Manual"). */
37
+ seriesLabel: string;
38
+ /** Optional version pill shown next to the series label. */
39
+ version?: string;
40
+ /** Optional last-updated date string shown under the sidebar nav. */
41
+ lastUpdated?: string;
42
+ /** All sections in this doc series. */
43
+ sections: DocsSection<TId>[];
44
+ /** Which section the current page represents. */
45
+ active: TId;
46
+ /** Page title rendered as the h1 + browser title. */
47
+ pageTitle: string;
48
+ /** Optional eyebrow above the h1 (defaults to seriesLabel). */
49
+ eyebrow?: string;
50
+ children: React.ReactNode;
51
+ };
52
+
53
+ export default function DocsShell<TId extends string>({
54
+ seriesLabel,
55
+ version,
56
+ lastUpdated,
57
+ sections,
58
+ active,
59
+ pageTitle,
60
+ eyebrow,
61
+ children,
62
+ }: Props<TId>) {
63
+ return (
64
+ <>
65
+
66
+ <div className="mx-auto flex w-full max-w-7xl gap-6 px-4 py-10 lg:gap-10 lg:px-8">
67
+ <aside className="hidden w-64 shrink-0 lg:block">
68
+ <Card className="sticky top-24 border-border bg-card p-4">
69
+ <div className="mb-3 flex items-center justify-between">
70
+ <PlatformLink
71
+ href="/docs"
72
+ className="text-xs font-semibold tracking-widest text-muted-foreground uppercase hover:text-foreground"
73
+ >
74
+ {seriesLabel}
75
+ </PlatformLink>
76
+ {version && (
77
+ <span className="rounded-full bg-primary/10 px-2 py-0.5 font-mono text-[10px] font-medium text-primary">
78
+ v{version}
79
+ </span>
80
+ )}
81
+ </div>
82
+ <nav className="space-y-0.5">
83
+ {sections.map((s) => {
84
+ const Icon = s.icon;
85
+ const isActive = s.id === active;
86
+
87
+ return (
88
+ <PlatformLink
89
+ key={s.id}
90
+ href={s.href}
91
+ className={cn(
92
+ 'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
93
+ isActive
94
+ ? 'bg-primary/10 font-semibold text-primary'
95
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
96
+ )}
97
+ >
98
+ <Icon
99
+ className={cn(
100
+ 'size-4 shrink-0',
101
+ isActive
102
+ ? 'text-primary'
103
+ : 'text-muted-foreground/70',
104
+ )}
105
+ />
106
+ <span>{s.title}</span>
107
+ </PlatformLink>
108
+ );
109
+ })}
110
+ </nav>
111
+ {lastUpdated && (
112
+ <div className="mt-4 border-t border-border pt-3 text-xs text-muted-foreground">
113
+ Last updated:{' '}
114
+ <span className="text-foreground">
115
+ {lastUpdated}
116
+ </span>
117
+ </div>
118
+ )}
119
+ <div className="mt-3 border-t border-border pt-3">
120
+ <PlatformLink
121
+ href="/docs"
122
+ className="text-xs text-muted-foreground hover:text-foreground"
123
+ >
124
+ ← All documentation
125
+ </PlatformLink>
126
+ </div>
127
+ </Card>
128
+ </aside>
129
+
130
+ <main className="min-w-0 flex-1">
131
+ <header className="mb-8">
132
+ <p className="text-xs font-semibold tracking-widest text-primary uppercase">
133
+ {eyebrow ?? seriesLabel}
134
+ </p>
135
+ <h1 className="mt-2 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
136
+ {pageTitle}
137
+ </h1>
138
+ </header>
139
+
140
+ <article
141
+ className={cn(
142
+ 'prose max-w-none prose-neutral dark:prose-invert',
143
+ 'prose-headings:font-semibold prose-headings:tracking-tight',
144
+ 'prose-a:text-primary prose-a:no-underline hover:prose-a:underline',
145
+ 'prose-strong:text-foreground',
146
+ 'prose-code:rounded prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:text-[0.875em] prose-code:font-medium prose-code:text-foreground prose-code:before:hidden prose-code:after:hidden',
147
+ 'prose-table:my-4 prose-table:text-sm',
148
+ 'prose-th:border-b prose-th:border-border prose-th:bg-muted/40 prose-th:px-3 prose-th:py-2 prose-th:text-left',
149
+ 'prose-td:border-b prose-td:border-border prose-td:px-3 prose-td:py-2',
150
+ )}
151
+ >
152
+ {children}
153
+ </article>
154
+
155
+ <DocsFooter active={active} sections={sections} />
156
+ </main>
157
+ </div>
158
+ </>
159
+ );
160
+ }
161
+
162
+ function DocsFooter<TId extends string>({
163
+ active,
164
+ sections,
165
+ }: {
166
+ active: TId;
167
+ sections: DocsSection<TId>[];
168
+ }) {
169
+ const idx = sections.findIndex((s) => s.id === active);
170
+ const prev = idx > 0 ? sections[idx - 1] : null;
171
+ const next =
172
+ idx >= 0 && idx < sections.length - 1 ? sections[idx + 1] : null;
173
+
174
+ if (!prev && !next) {
175
+ return null;
176
+ }
177
+
178
+ return (
179
+ <nav className="mt-12 flex items-center justify-between gap-4 border-t border-border pt-6">
180
+ <div>
181
+ {prev && (
182
+ <Button asChild variant="ghost">
183
+ <PlatformLink href={prev.href}>
184
+ <ArrowLeft className="size-4" />
185
+ <span className="flex flex-col items-start">
186
+ <span className="text-[10px] tracking-widest text-muted-foreground uppercase">
187
+ Previous
188
+ </span>
189
+ <span>{prev.title}</span>
190
+ </span>
191
+ </PlatformLink>
192
+ </Button>
193
+ )}
194
+ </div>
195
+ <div>
196
+ {next && (
197
+ <Button asChild variant="ghost">
198
+ <PlatformLink href={next.href}>
199
+ <span className="flex flex-col items-end">
200
+ <span className="text-[10px] tracking-widest text-muted-foreground uppercase">
201
+ Next
202
+ </span>
203
+ <span>{next.title}</span>
204
+ </span>
205
+ <ArrowRight className="size-4" />
206
+ </PlatformLink>
207
+ </Button>
208
+ )}
209
+ </div>
210
+ </nav>
211
+ );
212
+ }
213
+
214
+ export function CodeBlock({
215
+ language,
216
+ children,
217
+ }: {
218
+ language?: string;
219
+ children: string;
220
+ }) {
221
+ return (
222
+ <div className="not-prose my-4 overflow-hidden rounded-lg border border-border bg-muted/40">
223
+ {language && (
224
+ <div className="flex items-center justify-between border-b border-border bg-muted/40 px-3 py-1.5">
225
+ <span className="font-mono text-xs text-muted-foreground">
226
+ {language}
227
+ </span>
228
+ <CopyButton value={children} />
229
+ </div>
230
+ )}
231
+ <pre className="overflow-x-auto p-4 text-sm leading-relaxed text-foreground">
232
+ <code>{children}</code>
233
+ </pre>
234
+ </div>
235
+ );
236
+ }
237
+
238
+ export function Callout({
239
+ tone = 'info',
240
+ children,
241
+ }: {
242
+ tone?: 'info' | 'warning' | 'success';
243
+ children: React.ReactNode;
244
+ }) {
245
+ const config = {
246
+ info: {
247
+ border: 'border-info-subtle',
248
+ bg: 'bg-info-subtle/40',
249
+ text: 'text-info-fg',
250
+ icon: Info,
251
+ },
252
+ warning: {
253
+ border: 'border-warning-subtle',
254
+ bg: 'bg-warning-subtle/40',
255
+ text: 'text-warning-fg',
256
+ icon: AlertTriangle,
257
+ },
258
+ success: {
259
+ border: 'border-primary/30',
260
+ bg: 'bg-primary/5',
261
+ text: 'text-primary',
262
+ icon: CheckCircle2,
263
+ },
264
+ }[tone];
265
+
266
+ const Icon = config.icon;
267
+
268
+ return (
269
+ <div
270
+ className={cn(
271
+ 'not-prose my-4 flex gap-3 rounded-lg border px-4 py-3 text-sm',
272
+ config.border,
273
+ config.bg,
274
+ )}
275
+ >
276
+ <Icon className={cn('mt-0.5 size-4 shrink-0', config.text)} />
277
+ <div className="text-foreground">{children}</div>
278
+ </div>
279
+ );
280
+ }
@@ -0,0 +1,383 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
4
+ import CutoutImage from './cutout-image';
5
+
6
+ type LayerStyle = React.CSSProperties & {
7
+ '--mx'?: string;
8
+ '--my'?: string;
9
+ '--delay'?: string;
10
+ '--float'?: string;
11
+ };
12
+
13
+ type AssetKey =
14
+ | 'background'
15
+ | 'monitor'
16
+ | 'phone'
17
+ | 'truck'
18
+ | 'pickup'
19
+ | 'van'
20
+ | 'satellite'
21
+ | 'cloud'
22
+ | 'pin';
23
+
24
+ type FleetHeroAnimatedProps = {
25
+ className?: string;
26
+ assetBasePath?: string;
27
+ };
28
+
29
+ const defaultAssets: Record<AssetKey, string> = {
30
+ background: 'bg.png',
31
+ monitor: 'pc.png',
32
+ phone: 'mobile.png',
33
+ truck: 'truck.png',
34
+ pickup: 'hilux.png',
35
+ van: 'van.png',
36
+ satellite: 'satellite.png',
37
+ cloud: 'cloud.png',
38
+ pin: 'pin.png',
39
+ };
40
+
41
+ function joinPath(base: string, file: string) {
42
+ return `${base.replace(/\/$/, '')}/${file}`;
43
+ }
44
+
45
+ export default function FleetHeroAnimated({
46
+ className = '',
47
+ assetBasePath = '/elements-slider',
48
+ }: FleetHeroAnimatedProps) {
49
+ const wrapRef = useRef<HTMLDivElement | null>(null);
50
+ const [pointer, setPointer] = useState({ x: 0, y: 0 });
51
+ const [loaded, setLoaded] = useState(false);
52
+
53
+ const assets = useMemo(() => {
54
+ return Object.fromEntries(
55
+ Object.entries(defaultAssets).map(([key, value]) => [
56
+ key,
57
+ joinPath(assetBasePath, value),
58
+ ]),
59
+ ) as Record<AssetKey, string>;
60
+ }, [assetBasePath]);
61
+
62
+ useEffect(() => {
63
+ const timers = window.setTimeout(() => setLoaded(true), 120);
64
+
65
+ return () => window.clearTimeout(timers);
66
+ }, []);
67
+
68
+ const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
69
+ const rect = event.currentTarget.getBoundingClientRect();
70
+ const x = ((event.clientX - rect.left) / rect.width - 0.5) * 2;
71
+ const y = ((event.clientY - rect.top) / rect.height - 0.5) * 2;
72
+ setPointer({ x, y });
73
+ };
74
+
75
+ const resetPointer = () => setPointer({ x: 0, y: 0 });
76
+
77
+ const parallax = (depth: number): LayerStyle => ({
78
+ '--mx': `${pointer.x * depth}px`,
79
+ '--my': `${pointer.y * depth}px`,
80
+ });
81
+
82
+ return (
83
+ <section
84
+ ref={wrapRef}
85
+ onPointerMove={handlePointerMove}
86
+ onPointerLeave={resetPointer}
87
+ className={`relative isolate mx-auto flex w-full justify-center overflow-hidden bg-white ${className}`}
88
+ aria-label="Fleet, Employee and Assets Tracking and Attendance System"
89
+ >
90
+ <style>{styles}</style>
91
+
92
+ <div
93
+ className={`fleet-hero relative aspect-[561/701] w-full max-w-[1122px] overflow-hidden bg-white transition-opacity duration-700 ${
94
+ loaded ? 'opacity-100' : 'opacity-0'
95
+ }`}
96
+ >
97
+ <img
98
+ src={assets.background}
99
+ alt=""
100
+ className="absolute inset-0 h-full w-full object-cover"
101
+ draggable={false}
102
+ />
103
+
104
+ <div className="pointer-events-none absolute inset-x-0 top-0 z-20 h-[24%] bg-gradient-to-b from-white via-white/90 to-transparent" />
105
+ <div className="pointer-events-none absolute inset-y-0 left-0 z-20 w-[10%] bg-gradient-to-r from-white to-transparent" />
106
+ <div className="pointer-events-none absolute inset-y-0 right-0 z-20 w-[10%] bg-gradient-to-l from-white to-transparent" />
107
+
108
+ <div className="absolute inset-x-[3%] top-[2.8%] z-30 text-center">
109
+ <div className="mx-auto mb-[1.6%] inline-flex items-center rounded-full border border-emerald-100 bg-white/80 px-4 py-1.5 text-[clamp(10px,1.35vw,15px)] font-semibold text-emerald-800 shadow-sm backdrop-blur">
110
+ Smart Operations Platform
111
+ </div>
112
+
113
+ <h1 className="mx-auto max-w-[920px] text-[clamp(18px,3.1vw,64px)] leading-[0.96] font-black tracking-[-0.045em] text-balance text-neutral-950">
114
+ <span className="block">
115
+ Fleet, Employee &amp; Assets
116
+ </span>
117
+ <span className="block pt-[1.4%]">
118
+ <span className="text-emerald-700">Tracking</span>
119
+ <span className="font-extrabold text-neutral-950">
120
+ {' '}
121
+ and{' '}
122
+ </span>
123
+ <span className="text-emerald-700">
124
+ Attendance System
125
+ </span>
126
+ </span>
127
+ </h1>
128
+
129
+ <p className="mx-auto mt-[2%] max-w-[780px] text-[clamp(12px,1.55vw,18px)] leading-relaxed font-medium text-balance text-neutral-500">
130
+ Real-time monitoring. Workforce visibility. Asset
131
+ oversight. Attendance insights.
132
+ </p>
133
+
134
+ <div className="mx-auto mt-[2.5%] h-[3px] w-[15%] rounded-full bg-gradient-to-r from-emerald-700 via-emerald-500 to-red-500" />
135
+
136
+ <div className="mx-auto mt-[3.2%] grid max-w-[720px] grid-cols-4 gap-[clamp(8px,2vw,28px)] px-[3%]">
137
+ <FeatureIcon icon="⌖" label="Live Tracking" />
138
+ <FeatureIcon icon="●✓" label="Attendance" />
139
+ <FeatureIcon icon="▣" label="Asset Monitoring" />
140
+ <FeatureIcon icon="◉" label="Incident Alerts" />
141
+ </div>
142
+ </div>
143
+
144
+ <div className="absolute top-[54%] left-[1.4%] z-30 w-[47%]">
145
+ <CutoutImage
146
+ src={assets.monitor}
147
+ alt="Fleet dashboard overview"
148
+ className="fleet-layer fleet-layer-monitor block w-full object-contain drop-shadow-2xl select-none"
149
+ style={parallax(-7)}
150
+ draggable={false}
151
+ />
152
+ </div>
153
+
154
+ <div className="absolute top-[64.5%] left-[35.5%] z-50 w-[16.5%]">
155
+ <CutoutImage
156
+ src={assets.phone}
157
+ alt="Live monitoring mobile app"
158
+ className="fleet-layer fleet-layer-phone block w-full object-contain drop-shadow-2xl select-none"
159
+ style={parallax(11)}
160
+ draggable={false}
161
+ />
162
+ </div>
163
+
164
+ <CutoutImage
165
+ src={assets.van}
166
+ alt="Service van"
167
+ className="fleet-layer fleet-layer-van absolute top-[60%] left-[50.5%] z-20 w-[20%] object-contain opacity-95 drop-shadow-xl select-none"
168
+ style={parallax(-3)}
169
+ draggable={false}
170
+ />
171
+
172
+ <CutoutImage
173
+ src={assets.truck}
174
+ alt="Sanitation fleet truck"
175
+ className="fleet-layer fleet-layer-truck absolute top-[62%] right-[3.5%] z-40 w-[20%] object-contain drop-shadow-2xl select-none"
176
+ style={parallax(5)}
177
+ draggable={false}
178
+ />
179
+
180
+ <CutoutImage
181
+ src={assets.pickup}
182
+ alt="Utility pickup vehicle"
183
+ className="fleet-layer fleet-layer-pickup absolute top-[70.2%] right-[25.5%] z-60 w-[24%] object-contain drop-shadow-2xl select-none"
184
+ style={parallax(13)}
185
+ draggable={false}
186
+ />
187
+
188
+ <CutoutImage
189
+ src={assets.satellite}
190
+ alt=""
191
+ className="fleet-layer fleet-layer-satellite absolute top-[40%] right-[11.5%] z-30 w-[10%] object-contain drop-shadow-lg select-none"
192
+ style={parallax(18)}
193
+ draggable={false}
194
+ />
195
+
196
+ <CutoutImage
197
+ src={assets.cloud}
198
+ alt=""
199
+ className="fleet-layer fleet-layer-cloud absolute top-[44%] right-[24%] z-30 w-[8%] object-contain drop-shadow-lg select-none"
200
+ style={parallax(10)}
201
+ draggable={false}
202
+ />
203
+
204
+ <CutoutImage
205
+ src={assets.pin}
206
+ alt=""
207
+ className="fleet-layer fleet-layer-pin-left absolute top-[79.6%] left-[7%] z-40 w-[7%] object-contain drop-shadow-xl select-none"
208
+ style={parallax(16)}
209
+ draggable={false}
210
+ />
211
+
212
+ <CutoutImage
213
+ src={assets.pin}
214
+ alt=""
215
+ className="fleet-layer fleet-layer-pin-right absolute top-[78.5%] right-[4.5%] z-40 w-[7%] object-contain drop-shadow-xl select-none"
216
+ style={parallax(15)}
217
+ draggable={false}
218
+ />
219
+
220
+ <div className="absolute inset-x-[6.5%] bottom-[3.8%] z-70 grid grid-cols-4 overflow-hidden rounded-[2rem] bg-gradient-to-r from-emerald-900 via-emerald-800 to-emerald-700 px-[2%] py-[1.8%] text-white shadow-2xl shadow-emerald-900/20 max-sm:rounded-2xl">
221
+ <Benefit icon="🛡" title="Secure & Reliable" />
222
+ <Benefit icon="◷" title="Real-time Monitoring" />
223
+ <Benefit icon="⚙" title="Optimized Operations" />
224
+ <Benefit icon="👥" title="Better Public Services" />
225
+ </div>
226
+
227
+ <div className="pointer-events-none absolute inset-x-0 bottom-0 z-80 h-[5%] bg-gradient-to-t from-white via-white/70 to-transparent" />
228
+ </div>
229
+ </section>
230
+ );
231
+ }
232
+
233
+ function FeatureIcon({ icon, label }: { icon: string; label: string }) {
234
+ return (
235
+ <div className="group flex flex-col items-center gap-2 text-center">
236
+ <div className="grid aspect-square w-[clamp(42px,5.2vw,62px)] place-items-center rounded-full border border-emerald-200 bg-white/80 text-[clamp(17px,2.2vw,28px)] font-black text-emerald-700 shadow-sm backdrop-blur transition-transform duration-300 group-hover:-translate-y-1 group-hover:shadow-md">
237
+ {icon}
238
+ </div>
239
+ <span className="text-[clamp(9px,1.1vw,13px)] leading-tight font-bold text-neutral-800">
240
+ {label}
241
+ </span>
242
+ </div>
243
+ );
244
+ }
245
+
246
+ function Benefit({ icon, title }: { icon: string; title: string }) {
247
+ return (
248
+ <div className="flex min-w-0 items-center justify-center gap-[8%] border-r border-white/25 px-[3%] last:border-r-0">
249
+ <span className="text-[clamp(18px,3vw,34px)] leading-none opacity-95">
250
+ {icon}
251
+ </span>
252
+ <span className="max-w-[110px] text-[clamp(9px,1.45vw,17px)] leading-tight font-bold">
253
+ {title}
254
+ </span>
255
+ </div>
256
+ );
257
+ }
258
+
259
+ const styles = `
260
+ .fleet-layer {
261
+ --mx: 0px;
262
+ --my: 0px;
263
+ --delay: 0ms;
264
+ --float: 8px;
265
+ transform: translate3d(var(--mx), var(--my), 0);
266
+ transition: transform 180ms ease-out;
267
+ will-change: transform;
268
+ }
269
+
270
+ .fleet-hero {
271
+ transform: translateZ(0);
272
+ }
273
+
274
+ .fleet-hero::before {
275
+ content: "";
276
+ position: absolute;
277
+ inset: 40% 5% 8% 5%;
278
+ background:
279
+ radial-gradient(circle at 24% 82%, rgba(16, 185, 129, .16), transparent 18%),
280
+ radial-gradient(circle at 74% 65%, rgba(16, 185, 129, .13), transparent 20%);
281
+ pointer-events: none;
282
+ z-index: 10;
283
+ }
284
+
285
+ .fleet-hero::after {
286
+ content: "";
287
+ position: absolute;
288
+ left: 8%;
289
+ right: 8%;
290
+ bottom: 15.5%;
291
+ height: 22%;
292
+ border-radius: 999px;
293
+ background: linear-gradient(90deg, transparent, rgba(34, 197, 94, .32), transparent);
294
+ filter: blur(22px);
295
+ opacity: .65;
296
+ z-index: 15;
297
+ pointer-events: none;
298
+ }
299
+
300
+ .fleet-layer-monitor { animation: fleetRise 850ms cubic-bezier(.2,.8,.2,1) both, fleetFloat 7s ease-in-out 900ms infinite; }
301
+ .fleet-layer-phone { animation: fleetRise 950ms cubic-bezier(.2,.8,.2,1) 90ms both, fleetFloatPhone 5.5s ease-in-out 1050ms infinite; }
302
+ .fleet-layer-van { animation: fleetDriveIn 900ms cubic-bezier(.2,.8,.2,1) 170ms both, fleetFloat 8s ease-in-out 1200ms infinite; }
303
+ .fleet-layer-truck { animation: fleetDriveIn 950ms cubic-bezier(.2,.8,.2,1) 240ms both, fleetFloatTruck 6.5s ease-in-out 1300ms infinite; }
304
+ .fleet-layer-pickup { animation: fleetDriveIn 900ms cubic-bezier(.2,.8,.2,1) 320ms both, fleetFloatPickup 5.8s ease-in-out 1400ms infinite; }
305
+ .fleet-layer-satellite { animation: fleetSatelliteIn 1000ms cubic-bezier(.2,.8,.2,1) 420ms both, fleetOrbit 8s ease-in-out 1500ms infinite; transform-origin: 55% 45%; }
306
+ .fleet-layer-cloud { animation: fleetPop 700ms cubic-bezier(.2,.9,.2,1) 520ms both, fleetFloat 6s ease-in-out 1250ms infinite; }
307
+ .fleet-layer-pin-left, .fleet-layer-pin-right { animation: fleetPinDrop 800ms cubic-bezier(.2,.8,.2,1) 550ms both, fleetPulse 2.5s ease-in-out 1450ms infinite; }
308
+
309
+ @keyframes fleetRise {
310
+ from { opacity: 0; transform: translate3d(calc(var(--mx) - 12px), calc(var(--my) + 42px), 0) scale(.96); filter: blur(8px); }
311
+ to { opacity: 1; transform: translate3d(var(--mx), var(--my), 0) scale(1); filter: blur(0); }
312
+ }
313
+
314
+ @keyframes fleetDriveIn {
315
+ from { opacity: 0; transform: translate3d(calc(var(--mx) + 64px), calc(var(--my) + 28px), 0) scale(.94); filter: blur(7px); }
316
+ to { opacity: 1; transform: translate3d(var(--mx), var(--my), 0) scale(1); filter: blur(0); }
317
+ }
318
+
319
+ @keyframes fleetSatelliteIn {
320
+ from { opacity: 0; transform: translate3d(calc(var(--mx) + 38px), calc(var(--my) - 28px), 0) rotate(-14deg) scale(.82); filter: blur(7px); }
321
+ to { opacity: 1; transform: translate3d(var(--mx), var(--my), 0) rotate(0deg) scale(1); filter: blur(0); }
322
+ }
323
+
324
+ @keyframes fleetPop {
325
+ from { opacity: 0; transform: translate3d(var(--mx), var(--my), 0) scale(.65); }
326
+ to { opacity: 1; transform: translate3d(var(--mx), var(--my), 0) scale(1); }
327
+ }
328
+
329
+ @keyframes fleetPinDrop {
330
+ 0% { opacity: 0; transform: translate3d(var(--mx), calc(var(--my) - 40px), 0) scale(.85); }
331
+ 70% { opacity: 1; transform: translate3d(var(--mx), calc(var(--my) + 5px), 0) scale(1.04); }
332
+ 100% { opacity: 1; transform: translate3d(var(--mx), var(--my), 0) scale(1); }
333
+ }
334
+
335
+ @keyframes fleetFloat {
336
+ 0%,100% { translate: 0 0; }
337
+ 50% { translate: 0 -7px; }
338
+ }
339
+ @keyframes fleetFloatPhone {
340
+ 0%,100% { translate: 0 0; rotate: 0deg; }
341
+ 50% { translate: 0 -10px; rotate: .5deg; }
342
+ }
343
+ @keyframes fleetFloatTruck {
344
+ 0%,100% { translate: 0 0; }
345
+ 50% { translate: -4px -6px; }
346
+ }
347
+ @keyframes fleetFloatPickup {
348
+ 0%,100% { translate: 0 0; }
349
+ 50% { translate: 5px -8px; }
350
+ }
351
+ @keyframes fleetOrbit {
352
+ 0%,100% { translate: 0 0; rotate: 0deg; }
353
+ 50% { translate: 9px -9px; rotate: 2.5deg; }
354
+ }
355
+ @keyframes fleetPulse {
356
+ 0%,100% { scale: 1; filter: drop-shadow(0 10px 14px rgba(220,38,38,.22)); }
357
+ 50% { scale: 1.06; filter: drop-shadow(0 16px 22px rgba(220,38,38,.34)); }
358
+ }
359
+
360
+ @media (max-width: 640px) {
361
+ .fleet-layer-monitor { left: -3% !important; top: 49% !important; width: 53% !important; }
362
+ .fleet-layer-phone { left: 39% !important; top: 58% !important; width: 18.5% !important; }
363
+ .fleet-layer-truck { right: -1% !important; top: 61% !important; width: 45% !important; }
364
+ .fleet-layer-pickup { right: 22% !important; top: 72% !important; width: 37% !important; }
365
+ .fleet-layer-van { left: 50% !important; top: 64% !important; width: 26% !important; }
366
+ }
367
+
368
+ @media (prefers-reduced-motion: reduce) {
369
+ .fleet-layer,
370
+ .fleet-layer-monitor,
371
+ .fleet-layer-phone,
372
+ .fleet-layer-van,
373
+ .fleet-layer-truck,
374
+ .fleet-layer-pickup,
375
+ .fleet-layer-satellite,
376
+ .fleet-layer-cloud,
377
+ .fleet-layer-pin-left,
378
+ .fleet-layer-pin-right {
379
+ animation: none !important;
380
+ transition: none !important;
381
+ }
382
+ }
383
+ `;
@@ -0,0 +1,17 @@
1
+ import type { HTMLAttributes } from 'react';
2
+ import { cn } from '../lib/utils';
3
+
4
+ export default function InputError({
5
+ message,
6
+ className = '',
7
+ ...props
8
+ }: HTMLAttributes<HTMLParagraphElement> & { message?: string }) {
9
+ return message ? (
10
+ <p
11
+ {...props}
12
+ className={cn('text-sm text-red-600 dark:text-red-400', className)}
13
+ >
14
+ {message}
15
+ </p>
16
+ ) : null;
17
+ }
@@ -0,0 +1,7 @@
1
+ Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures.
2
+
3
+ To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/docs/#local-fonts
4
+
5
+ You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects.
6
+
7
+ You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection.