@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,99 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import { cn } from '../../lib/utils';
5
+ import type { BaseAppLayoutProps } from './layout-types';
6
+ import type { NavItem } from '../../types/navigation';
7
+ import { Toolbar } from './partials/Toolbar';
8
+ import { Footer } from './partials/Footer';
9
+ import { HeaderTopbar } from './partials/HeaderTopbar';
10
+ import { AccordionMenu, AccordionMenuGroup, AccordionMenuItem, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '../../components/ui/accordion-menu';
11
+ import { ScrollArea } from '../../components/ui/scroll-area';
12
+
13
+ /**
14
+ * SplitSidebarLayout (demo4)
15
+ * No horizontal navbar. Left sidebar split into:
16
+ * - primary panel: icon/section switcher
17
+ * - secondary panel: full nav items for the active section
18
+ */
19
+ interface SplitSidebarLayoutProps extends BaseAppLayoutProps {
20
+ primaryItems?: NavItem[];
21
+ secondaryItems?: NavItem[];
22
+ activePrimary?: string;
23
+ onPrimaryChange?: (value: string) => void;
24
+ showToolbar?: boolean;
25
+ }
26
+
27
+ export function SplitSidebarLayout({
28
+ children, navItems = [], primaryItems = [], secondaryItems = [],
29
+ currentUrl = '', logo, logoHref = '/', appName, user,
30
+ title, breadcrumbs = [], toolbarActions, activePrimary,
31
+ onLogout, settingsUrl, logoutUrl, unreadCount = 0,
32
+ footerLinks = [], copyright, showToolbar = true,
33
+ }: SplitSidebarLayoutProps) {
34
+ const sideItems = secondaryItems.length > 0 ? secondaryItems : navItems;
35
+
36
+ return (
37
+ <div className="flex min-h-screen">
38
+ {/* Primary sidebar (icon strip) */}
39
+ {primaryItems.length > 0 && (
40
+ <aside className="w-16 shrink-0 border-e border-sidebar-border bg-sidebar flex flex-col items-center py-3 gap-1">
41
+ {logo && <a href={logoHref} className="mb-3">{logo}</a>}
42
+ {primaryItems.map((item, i) => (
43
+ <button
44
+ key={i}
45
+ onClick={() => {}}
46
+ className={cn('w-10 h-10 flex items-center justify-center rounded-lg text-sidebar-foreground hover:bg-sidebar-accent transition-colors text-xs', activePrimary === item.title && 'bg-sidebar-accent')}
47
+ title={item.title}
48
+ >
49
+ {item.title.slice(0, 2)}
50
+ </button>
51
+ ))}
52
+ </aside>
53
+ )}
54
+
55
+ {/* Secondary sidebar (full nav) */}
56
+ <aside className="w-56 shrink-0 border-e border-sidebar-border bg-sidebar flex flex-col">
57
+ {!primaryItems.length && logo && (
58
+ <div className="flex items-center gap-2 px-4 h-[70px] border-b border-sidebar-border shrink-0">
59
+ <a href={logoHref}>{logo}</a>
60
+ {appName && <span className="text-sm font-semibold">{appName}</span>}
61
+ </div>
62
+ )}
63
+ <ScrollArea className="flex-1 py-3 px-2">
64
+ <AccordionMenu type="single" collapsible matchPath={(href) => !!href && currentUrl.startsWith(href)} selectedValue={currentUrl}>
65
+ <AccordionMenuGroup>
66
+ {sideItems.map((item, i) => (
67
+ item.items ? (
68
+ <AccordionMenuSub key={i} value={item.title}>
69
+ <AccordionMenuSubTrigger>{item.title}</AccordionMenuSubTrigger>
70
+ <AccordionMenuSubContent type="single" collapsible parentValue={item.title}>
71
+ {item.items.map((c, ci) => <AccordionMenuItem key={ci} value={c.href ?? c.title} asChild><a href={c.href}>{c.title}</a></AccordionMenuItem>)}
72
+ </AccordionMenuSubContent>
73
+ </AccordionMenuSub>
74
+ ) : (
75
+ <AccordionMenuItem key={i} value={item.href ?? item.title} asChild><a href={item.href}>{item.title}</a></AccordionMenuItem>
76
+ )
77
+ ))}
78
+ </AccordionMenuGroup>
79
+ </AccordionMenu>
80
+ </ScrollArea>
81
+ </aside>
82
+
83
+ {/* Main area */}
84
+ <div className="flex flex-col flex-1 min-w-0">
85
+ <header className="flex items-center h-[70px] border-b border-border bg-background px-4 shrink-0">
86
+ <div className="flex-1" />
87
+ <HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
88
+ </header>
89
+ <main className="flex-1" role="content">
90
+ {showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
91
+ <Toolbar title={title} breadcrumbs={breadcrumbs} actions={toolbarActions} currentUrl={currentUrl} />
92
+ )}
93
+ {children}
94
+ </main>
95
+ <Footer links={footerLinks} copyright={copyright} />
96
+ </div>
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,105 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState, type ReactNode } from 'react';
4
+ import { cn } from '../../lib/utils';
5
+ import type { BaseAppLayoutProps } from './layout-types';
6
+ import { Navbar } from './partials/Navbar';
7
+ import { Toolbar } from './partials/Toolbar';
8
+ import { Footer } from './partials/Footer';
9
+ import { HeaderTopbar } from './partials/HeaderTopbar';
10
+
11
+ interface TopNavLayoutProps extends BaseAppLayoutProps {
12
+ headerRightSlot?: ReactNode;
13
+ navRightSlot?: ReactNode;
14
+ showToolbar?: boolean;
15
+ }
16
+
17
+ export function TopNavLayout({
18
+ children,
19
+ navItems = [],
20
+ currentUrl = '',
21
+ logo,
22
+ logoHref = '/',
23
+ appName,
24
+ user,
25
+ title,
26
+ breadcrumbs = [],
27
+ toolbarActions,
28
+ stickyHeader = true,
29
+ stickyOffset = 100,
30
+ onLogout,
31
+ settingsUrl,
32
+ logoutUrl,
33
+ unreadCount = 0,
34
+ footerLinks = [],
35
+ copyright,
36
+ headerRightSlot,
37
+ navRightSlot,
38
+ showToolbar = true,
39
+ }: TopNavLayoutProps) {
40
+ const [isSticky, setIsSticky] = useState(false);
41
+
42
+ useEffect(() => {
43
+ if (!stickyHeader) return;
44
+ const handleScroll = () => setIsSticky(window.scrollY > stickyOffset);
45
+ window.addEventListener('scroll', handleScroll, { passive: true });
46
+ return () => window.removeEventListener('scroll', handleScroll);
47
+ }, [stickyHeader, stickyOffset]);
48
+
49
+ const headerHeight = isSticky ? '60px' : '100px';
50
+
51
+ return (
52
+ <div
53
+ className="flex grow flex-col min-h-screen"
54
+ style={{ '--header-height': headerHeight } as React.CSSProperties}
55
+ >
56
+ {/* Header */}
57
+ <header
58
+ className={cn(
59
+ 'flex items-center shrink-0 transition-[height] border-b border-border',
60
+ 'h-(--header-height)',
61
+ stickyHeader && isSticky && 'fixed z-10 top-0 inset-x-0 shadow-xs backdrop-blur-md bg-background/80',
62
+ )}
63
+ style={{ height: headerHeight }}
64
+ >
65
+ <div className="container mx-auto px-4 flex justify-between items-center gap-4">
66
+ {/* Logo */}
67
+ <div className="flex items-center gap-3 shrink-0">
68
+ {logo && <a href={logoHref} className="shrink-0">{logo}</a>}
69
+ {appName && <span className="text-sm font-medium text-foreground hidden md:inline">{appName}</span>}
70
+ </div>
71
+ {/* Right topbar */}
72
+ <HeaderTopbar
73
+ user={user}
74
+ unreadCount={unreadCount}
75
+ settingsUrl={settingsUrl}
76
+ logoutUrl={logoutUrl}
77
+ onLogout={onLogout}
78
+ extraSlot={headerRightSlot}
79
+ />
80
+ </div>
81
+ </header>
82
+
83
+ {/* Spacer when header is sticky */}
84
+ {stickyHeader && isSticky && <div style={{ height: '100px' }} aria-hidden="true" />}
85
+
86
+ {/* Horizontal navbar */}
87
+ <Navbar navItems={navItems} currentUrl={currentUrl} rightSlot={navRightSlot} />
88
+
89
+ {/* Content */}
90
+ <main className="grow" role="content">
91
+ {showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
92
+ <Toolbar
93
+ title={title}
94
+ breadcrumbs={breadcrumbs}
95
+ actions={toolbarActions}
96
+ currentUrl={currentUrl}
97
+ />
98
+ )}
99
+ {children}
100
+ </main>
101
+
102
+ <Footer links={footerLinks} copyright={copyright} />
103
+ </div>
104
+ );
105
+ }
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ // layout-2: top nav with external links in navbar (same structure as TopNavLayout)
3
+ export { TopNavLayout as TopNavLinksLayout } from './TopNavLayout';
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ // Workspace variant — shares same structure as WorkspaceSidebarLayout
3
+ export { WorkspaceSidebarLayout as WorkspaceBreadcrumbLayout } from './WorkspaceSidebarLayout';
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ // Workspace variant — shares same structure as WorkspaceSidebarLayout
3
+ export { WorkspaceSidebarLayout as WorkspaceCommunitiesLayout } from './WorkspaceSidebarLayout';
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ // Navbar + sidebar variant
3
+ export { NavbarSidebarLayout as WorkspaceNavbarLayout } from './NavbarSidebarLayout';
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+ // layout-14/20: workspace sidebar — primary nav + workspace switcher + secondary panel
3
+ // Used for multi-workspace apps (similar to Notion, Linear, Slack)
4
+ import { type ReactNode } from 'react';
5
+ import { cn } from '../../lib/utils';
6
+ import type { BaseAppLayoutProps } from './layout-types';
7
+ import { Toolbar } from './partials/Toolbar';
8
+ import { Footer } from './partials/Footer';
9
+ import { HeaderTopbar } from './partials/HeaderTopbar';
10
+ import { AccordionMenu, AccordionMenuGroup, AccordionMenuItem, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '../../components/ui/accordion-menu';
11
+ import { ScrollArea } from '../../components/ui/scroll-area';
12
+
13
+ interface WorkspaceSidebarLayoutProps extends BaseAppLayoutProps {
14
+ workspaces?: Array<{ id: string; name: string; icon?: string; href: string }>;
15
+ activeWorkspace?: string;
16
+ communities?: Array<{ name: string; href: string }>;
17
+ secondaryItems?: import('../../types/navigation').NavItem[];
18
+ sidebarFooter?: ReactNode;
19
+ showToolbar?: boolean;
20
+ }
21
+
22
+ export function WorkspaceSidebarLayout({
23
+ children, navItems = [], currentUrl = '',
24
+ logo, logoHref = '/', appName, user,
25
+ title, breadcrumbs = [], toolbarActions,
26
+ workspaces = [], activeWorkspace, communities = [],
27
+ secondaryItems = [], sidebarFooter,
28
+ onLogout, settingsUrl, logoutUrl, unreadCount = 0,
29
+ footerLinks = [], copyright, showToolbar = true,
30
+ }: WorkspaceSidebarLayoutProps) {
31
+ const allNavItems = [...navItems, ...secondaryItems];
32
+ return (
33
+ <div className="flex min-h-screen">
34
+ {/* Workspace strip */}
35
+ {workspaces.length > 0 && (
36
+ <div className="w-[52px] flex flex-col items-center py-3 gap-1.5 border-e border-sidebar-border bg-sidebar shrink-0">
37
+ {logo && <a href={logoHref} className="mb-2">{logo}</a>}
38
+ {workspaces.map((ws, i) => (
39
+ <a key={i} href={ws.href} className={cn('w-8 h-8 rounded-lg flex items-center justify-center text-xs font-bold transition-colors', activeWorkspace === ws.id ? 'bg-sidebar-primary text-sidebar-primary-foreground' : 'bg-sidebar-accent text-sidebar-foreground hover:bg-sidebar-accent/80')}>
40
+ {ws.icon ?? ws.name.slice(0, 2)}
41
+ </a>
42
+ ))}
43
+ </div>
44
+ )}
45
+
46
+ {/* Main sidebar */}
47
+ <aside className="w-56 shrink-0 flex flex-col border-e border-sidebar-border bg-sidebar">
48
+ {!workspaces.length && logo && (
49
+ <div className="flex items-center gap-2 px-4 h-[70px] border-b border-sidebar-border shrink-0">
50
+ <a href={logoHref}>{logo}</a>
51
+ {appName && <span className="text-sm font-semibold">{appName}</span>}
52
+ </div>
53
+ )}
54
+ <ScrollArea className="flex-1 py-2 px-2">
55
+ <AccordionMenu type="single" collapsible matchPath={(href) => !!href && currentUrl.startsWith(href)} selectedValue={currentUrl}>
56
+ <AccordionMenuGroup>
57
+ {allNavItems.map((item, i) => (
58
+ item.items ? (
59
+ <AccordionMenuSub key={i} value={item.title}>
60
+ <AccordionMenuSubTrigger>{item.title}</AccordionMenuSubTrigger>
61
+ <AccordionMenuSubContent type="single" collapsible parentValue={item.title}>
62
+ {item.items.map((c, ci) => <AccordionMenuItem key={ci} value={c.href ?? c.title} asChild><a href={c.href}>{c.title}</a></AccordionMenuItem>)}
63
+ </AccordionMenuSubContent>
64
+ </AccordionMenuSub>
65
+ ) : (
66
+ <AccordionMenuItem key={i} value={item.href ?? item.title} asChild><a href={item.href}>{item.title}</a></AccordionMenuItem>
67
+ )
68
+ ))}
69
+ </AccordionMenuGroup>
70
+ {communities.length > 0 && (
71
+ <AccordionMenuGroup>
72
+ <div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">Communities</div>
73
+ {communities.map((c, i) => (
74
+ <AccordionMenuItem key={i} value={c.href} asChild><a href={c.href}>{c.name}</a></AccordionMenuItem>
75
+ ))}
76
+ </AccordionMenuGroup>
77
+ )}
78
+ </AccordionMenu>
79
+ </ScrollArea>
80
+ {sidebarFooter && <div className="p-3 border-t border-sidebar-border">{sidebarFooter}</div>}
81
+ </aside>
82
+
83
+ <div className="flex flex-col flex-1 min-w-0">
84
+ <header className="flex items-center h-[70px] border-b border-border bg-background px-4 shrink-0">
85
+ <div className="flex-1" />
86
+ <HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
87
+ </header>
88
+ <main className="flex-1" role="content">
89
+ {showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
90
+ <Toolbar title={title} breadcrumbs={breadcrumbs} actions={toolbarActions} currentUrl={currentUrl} />
91
+ )}
92
+ {children}
93
+ </main>
94
+ <Footer links={footerLinks} copyright={copyright} />
95
+ </div>
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ // Workspace variant — shares same structure as WorkspaceSidebarLayout
3
+ export { WorkspaceSidebarLayout as WorkspaceSidebarTitleLayout } from './WorkspaceSidebarLayout';
@@ -0,0 +1,45 @@
1
+ import type { ReactNode } from 'react';
2
+ import { AppShell } from '../../components/app-shell';
3
+ import { AppContent } from '../../components/app-content';
4
+ import { AppHeader } from '../../components/app-header';
5
+ import type { NavItem, BreadcrumbItem } from '../../types/navigation';
6
+ import type { User } from '../../types/auth';
7
+
8
+ export type AppHeaderLayoutProps = {
9
+ breadcrumbs?: BreadcrumbItem[];
10
+ children: ReactNode;
11
+ user?: User | null;
12
+ navItems?: NavItem[];
13
+ unreadCount?: number;
14
+ dashboardHref?: string;
15
+ settingsUrl?: string;
16
+ logoutUrl?: string;
17
+ };
18
+
19
+ export default function AppHeaderLayout({
20
+ breadcrumbs = [],
21
+ children,
22
+ user,
23
+ navItems = [],
24
+ unreadCount = 0,
25
+ dashboardHref = '/dashboard',
26
+ settingsUrl = '/settings/profile',
27
+ logoutUrl = '/logout',
28
+ }: AppHeaderLayoutProps) {
29
+ return (
30
+ <AppShell variant="header">
31
+ <AppHeader
32
+ breadcrumbs={breadcrumbs}
33
+ user={user}
34
+ navItems={navItems}
35
+ unreadCount={unreadCount}
36
+ dashboardHref={dashboardHref}
37
+ settingsUrl={settingsUrl}
38
+ logoutUrl={logoutUrl}
39
+ />
40
+ <AppContent variant="header">
41
+ {children}
42
+ </AppContent>
43
+ </AppShell>
44
+ );
45
+ }
@@ -0,0 +1,56 @@
1
+ import type { ReactNode } from 'react';
2
+ import { AppShell } from '../../components/app-shell';
3
+ import { AppContent } from '../../components/app-content';
4
+ import { AppSidebar } from '../../components/app-sidebar';
5
+ import { AppSidebarHeader } from '../../components/app-sidebar-header';
6
+ import type { NavItem, BreadcrumbItem } from '../../types/navigation';
7
+ import type { User } from '../../types/auth';
8
+
9
+ export type AppSidebarLayoutProps = {
10
+ breadcrumbs?: BreadcrumbItem[];
11
+ children: ReactNode;
12
+ user?: User | null;
13
+ navItems?: NavItem[];
14
+ tenant?: { display_name?: string | null; sub_brand?: string | null } | null;
15
+ unreadCount?: number;
16
+ dashboardHref?: string;
17
+ settingsUrl?: string;
18
+ logoutUrl?: string;
19
+ defaultOpen?: boolean;
20
+ };
21
+
22
+ export default function AppSidebarLayout({
23
+ breadcrumbs = [],
24
+ children,
25
+ user,
26
+ navItems,
27
+ tenant,
28
+ unreadCount = 0,
29
+ dashboardHref = '/dashboard',
30
+ settingsUrl = '/settings/profile',
31
+ logoutUrl = '/logout',
32
+ defaultOpen = true,
33
+ }: AppSidebarLayoutProps) {
34
+ return (
35
+ <AppShell variant="sidebar" defaultOpen={defaultOpen}>
36
+ <AppSidebar
37
+ navItems={navItems}
38
+ user={user}
39
+ tenant={tenant}
40
+ dashboardHref={dashboardHref}
41
+ settingsUrl={settingsUrl}
42
+ logoutUrl={logoutUrl}
43
+ />
44
+ <AppContent variant="sidebar">
45
+ <AppSidebarHeader
46
+ breadcrumbs={breadcrumbs}
47
+ user={user}
48
+ unreadCount={unreadCount}
49
+ settingsUrl={settingsUrl}
50
+ logoutUrl={logoutUrl}
51
+ />
52
+ {children}
53
+ </AppContent>
54
+ </AppShell>
55
+ );
56
+ }
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, useState, type ReactNode } from 'react';
4
+
5
+ interface LayoutContextValue {
6
+ sidebarCollapsed: boolean;
7
+ setSidebarCollapsed: (collapsed: boolean) => void;
8
+ toggleSidebar: () => void;
9
+ stickyHeader: boolean;
10
+ setStickyHeader: (sticky: boolean) => void;
11
+ }
12
+
13
+ const LayoutContext = createContext<LayoutContextValue | null>(null);
14
+
15
+ export function LayoutProvider({
16
+ children,
17
+ defaultSidebarCollapsed = false,
18
+ defaultStickyHeader = true,
19
+ }: {
20
+ children: ReactNode;
21
+ defaultSidebarCollapsed?: boolean;
22
+ defaultStickyHeader?: boolean;
23
+ }) {
24
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(defaultSidebarCollapsed);
25
+ const [stickyHeader, setStickyHeader] = useState(defaultStickyHeader);
26
+
27
+ return (
28
+ <LayoutContext.Provider value={{
29
+ sidebarCollapsed,
30
+ setSidebarCollapsed,
31
+ toggleSidebar: () => setSidebarCollapsed((c) => !c),
32
+ stickyHeader,
33
+ setStickyHeader,
34
+ }}>
35
+ {children}
36
+ </LayoutContext.Provider>
37
+ );
38
+ }
39
+
40
+ export function useLayout() {
41
+ const ctx = useContext(LayoutContext);
42
+ if (!ctx) throw new Error('useLayout must be used within a LayoutProvider');
43
+ return ctx;
44
+ }
@@ -0,0 +1,47 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { NavItem, BreadcrumbItem } from '../../types/navigation';
3
+
4
+ export interface AppLayoutUser {
5
+ name: string;
6
+ email?: string;
7
+ avatar?: string;
8
+ }
9
+
10
+ export interface AppLayoutFooterLink {
11
+ label: string;
12
+ href: string;
13
+ }
14
+
15
+ export interface BaseAppLayoutProps {
16
+ children: ReactNode;
17
+
18
+ // Navigation
19
+ navItems?: NavItem[];
20
+ currentUrl?: string;
21
+
22
+ // Identity
23
+ logo?: ReactNode;
24
+ logoHref?: string;
25
+ appName?: string;
26
+ user?: AppLayoutUser | null;
27
+
28
+ // Page context
29
+ title?: string;
30
+ breadcrumbs?: BreadcrumbItem[];
31
+ toolbarActions?: ReactNode;
32
+
33
+ // Behaviour
34
+ stickyHeader?: boolean;
35
+ stickyOffset?: number;
36
+ defaultSidebarCollapsed?: boolean;
37
+
38
+ // Actions
39
+ onLogout?: () => void;
40
+ settingsUrl?: string;
41
+ logoutUrl?: string;
42
+ unreadCount?: number;
43
+
44
+ // Footer
45
+ footerLinks?: AppLayoutFooterLink[];
46
+ copyright?: string;
47
+ }
@@ -0,0 +1,35 @@
1
+ import type { AppLayoutFooterLink } from '../layout-types';
2
+ import { cn } from '../../../lib/utils';
3
+
4
+ interface FooterProps {
5
+ links?: AppLayoutFooterLink[];
6
+ copyright?: string;
7
+ className?: string;
8
+ }
9
+
10
+ export function Footer({ links = [], copyright, className }: FooterProps) {
11
+ const year = new Date().getFullYear();
12
+ return (
13
+ <footer className={cn('footer border-t border-border', className)}>
14
+ <div className="container mx-auto px-4">
15
+ <div className="flex flex-col md:flex-row justify-center md:justify-between items-center gap-3 py-5">
16
+ <div className="flex order-2 md:order-1 gap-2 font-normal text-sm">
17
+ <span className="text-muted-foreground">{year} &copy;</span>
18
+ {copyright && (
19
+ <span className="text-secondary-foreground">{copyright}</span>
20
+ )}
21
+ </div>
22
+ {links.length > 0 && (
23
+ <nav className="flex order-1 md:order-2 gap-4 font-normal text-sm text-muted-foreground">
24
+ {links.map((link) => (
25
+ <a key={link.href} href={link.href} className="hover:text-primary transition-colors">
26
+ {link.label}
27
+ </a>
28
+ ))}
29
+ </nav>
30
+ )}
31
+ </div>
32
+ </div>
33
+ </footer>
34
+ );
35
+ }
@@ -0,0 +1,96 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import type { AppLayoutUser } from '../layout-types';
5
+ import { Bell, Search } from 'lucide-react';
6
+ import { cn } from '../../../lib/utils';
7
+ import { Button } from '../../../controls/Button';
8
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../../components/ui/dropdown-menu';
9
+
10
+ interface HeaderTopbarProps {
11
+ user?: AppLayoutUser | null;
12
+ unreadCount?: number;
13
+ onSearchOpen?: () => void;
14
+ settingsUrl?: string;
15
+ logoutUrl?: string;
16
+ onLogout?: () => void;
17
+ className?: string;
18
+ extraSlot?: ReactNode;
19
+ }
20
+
21
+ function getInitials(name: string): string {
22
+ return name
23
+ .split(' ')
24
+ .map((n) => n[0])
25
+ .slice(0, 2)
26
+ .join('')
27
+ .toUpperCase();
28
+ }
29
+
30
+ export function HeaderTopbar({
31
+ user,
32
+ unreadCount = 0,
33
+ onSearchOpen,
34
+ settingsUrl,
35
+ logoutUrl,
36
+ onLogout,
37
+ className,
38
+ extraSlot,
39
+ }: HeaderTopbarProps) {
40
+ return (
41
+ <div className={cn('flex items-center gap-3', className)}>
42
+ {extraSlot}
43
+
44
+ {onSearchOpen && (
45
+ <Button variant="ghost" size="sm" className="size-9 p-0 rounded-full" onClick={onSearchOpen} aria-label="Search">
46
+ <Search className="size-4" />
47
+ </Button>
48
+ )}
49
+
50
+ <Button variant="ghost" size="sm" className="size-9 p-0 rounded-full relative" aria-label="Notifications">
51
+ <Bell className="size-4" />
52
+ {unreadCount > 0 && (
53
+ <span className="absolute top-0.5 right-0.5 size-2 rounded-full bg-primary" />
54
+ )}
55
+ </Button>
56
+
57
+ {user && (
58
+ <DropdownMenu>
59
+ <DropdownMenuTrigger asChild>
60
+ <button className="size-9 rounded-full border border-border overflow-hidden cursor-pointer shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
61
+ {user.avatar ? (
62
+ <img src={user.avatar} alt={user.name} className="size-full object-cover" />
63
+ ) : (
64
+ <span className="size-full flex items-center justify-center bg-muted text-xs font-medium text-muted-foreground">
65
+ {getInitials(user.name)}
66
+ </span>
67
+ )}
68
+ </button>
69
+ </DropdownMenuTrigger>
70
+ <DropdownMenuContent align="end" className="w-52">
71
+ <div className="px-2 py-1.5">
72
+ <p className="text-sm font-medium text-foreground truncate">{user.name}</p>
73
+ {user.email && <p className="text-xs text-muted-foreground truncate">{user.email}</p>}
74
+ </div>
75
+ <DropdownMenuSeparator />
76
+ {settingsUrl && (
77
+ <DropdownMenuItem asChild>
78
+ <a href={settingsUrl}>Settings</a>
79
+ </DropdownMenuItem>
80
+ )}
81
+ <DropdownMenuSeparator />
82
+ {logoutUrl ? (
83
+ <DropdownMenuItem asChild>
84
+ <a href={logoutUrl} className="text-destructive focus:text-destructive">Sign out</a>
85
+ </DropdownMenuItem>
86
+ ) : onLogout ? (
87
+ <DropdownMenuItem onClick={onLogout} className="text-destructive focus:text-destructive">
88
+ Sign out
89
+ </DropdownMenuItem>
90
+ ) : null}
91
+ </DropdownMenuContent>
92
+ </DropdownMenu>
93
+ )}
94
+ </div>
95
+ );
96
+ }