@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.
- package/package.json +185 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/map/arrows/map-arrow-blue.png +0 -0
- package/src/assets/map/arrows/map-arrow-green.png +0 -0
- package/src/assets/map/arrows/map-arrow-purple.png +0 -0
- package/src/assets/map/arrows/map-arrow-red.png +0 -0
- package/src/assets/map/flags/flag-blue.png +0 -0
- package/src/assets/map/flags/flag-green.png +0 -0
- package/src/assets/map/flags/flag-red.png +0 -0
- package/src/assets/map/flags/flag-yellow.png +0 -0
- package/src/assets/map/pins/map-pin-blue.png +0 -0
- package/src/assets/map/pins/map-pin-green.png +0 -0
- package/src/assets/map/pins/map-pin-purple.png +0 -0
- package/src/assets/map/pins/map-pin-red.png +0 -0
- package/src/components/Card.tsx +9 -0
- package/src/components/alert-error.tsx +24 -0
- package/src/components/app-content.tsx +22 -0
- package/src/components/app-header.tsx +153 -0
- package/src/components/app-logo-icon.tsx +13 -0
- package/src/components/app-logo.tsx +21 -0
- package/src/components/app-shell.tsx +19 -0
- package/src/components/app-sidebar-header.tsx +68 -0
- package/src/components/app-sidebar.tsx +106 -0
- package/src/components/appearance-tabs.tsx +46 -0
- package/src/components/breadcrumbs.tsx +50 -0
- package/src/components/cms/blurred-image.tsx +111 -0
- package/src/components/cms/section-bg.tsx +473 -0
- package/src/components/cms/section-button.tsx +127 -0
- package/src/components/cms/sections/banner-5050-section.tsx +135 -0
- package/src/components/cms/sections/blogs-listing-section.tsx +270 -0
- package/src/components/cms/sections/cards-grid-section.tsx +185 -0
- package/src/components/cms/sections/contact-form-section.tsx +157 -0
- package/src/components/cms/sections/cta-section.tsx +101 -0
- package/src/components/cms/sections/featured-blog-slider-section.tsx +256 -0
- package/src/components/cms/sections/featured-products-grid-section.tsx +173 -0
- package/src/components/cms/sections/featured-solutions-grid-section.tsx +183 -0
- package/src/components/cms/sections/hero-section.tsx +180 -0
- package/src/components/cms/sections/solutions-with-filter-section.tsx +234 -0
- package/src/components/cms/sections/text-section.tsx +77 -0
- package/src/components/cutout-image.tsx +228 -0
- package/src/components/devices/devices-mini-map.tsx +275 -0
- package/src/components/docs/docs-shell.tsx +280 -0
- package/src/components/fleet-hero-animated.tsx +383 -0
- package/src/components/input-error.tsx +17 -0
- package/src/components/keenicons/assets/duotone/Read Me.txt +7 -0
- package/src/components/keenicons/assets/duotone/demo-files/demo.css +160 -0
- package/src/components/keenicons/assets/duotone/demo-files/demo.js +32 -0
- package/src/components/keenicons/assets/duotone/demo.html +12424 -0
- package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.svg +1109 -0
- package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.ttf +0 -0
- package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.woff +0 -0
- package/src/components/keenicons/assets/duotone/selection.json +17313 -0
- package/src/components/keenicons/assets/duotone/style.css +4931 -0
- package/src/components/keenicons/assets/filled/Read Me.txt +7 -0
- package/src/components/keenicons/assets/filled/demo-files/demo.css +160 -0
- package/src/components/keenicons/assets/filled/demo-files/demo.js +32 -0
- package/src/components/keenicons/assets/filled/demo.html +12370 -0
- package/src/components/keenicons/assets/filled/fonts/keenicons-filled.svg +1082 -0
- package/src/components/keenicons/assets/filled/fonts/keenicons-filled.ttf +0 -0
- package/src/components/keenicons/assets/filled/fonts/keenicons-filled.woff +0 -0
- package/src/components/keenicons/assets/filled/selection.json +17096 -0
- package/src/components/keenicons/assets/filled/style.css +4769 -0
- package/src/components/keenicons/assets/outline/Read Me.txt +7 -0
- package/src/components/keenicons/assets/outline/demo-files/demo.css +160 -0
- package/src/components/keenicons/assets/outline/demo-files/demo.js +32 -0
- package/src/components/keenicons/assets/outline/demo.html +11356 -0
- package/src/components/keenicons/assets/outline/fonts/keenicons-outline.svg +575 -0
- package/src/components/keenicons/assets/outline/fonts/keenicons-outline.ttf +0 -0
- package/src/components/keenicons/assets/outline/fonts/keenicons-outline.woff +0 -0
- package/src/components/keenicons/assets/outline/selection.json +13054 -0
- package/src/components/keenicons/assets/outline/style.css +1721 -0
- package/src/components/keenicons/assets/solid/Read Me.txt +7 -0
- package/src/components/keenicons/assets/solid/demo-files/demo.css +160 -0
- package/src/components/keenicons/assets/solid/demo-files/demo.js +32 -0
- package/src/components/keenicons/assets/solid/demo.html +11356 -0
- package/src/components/keenicons/assets/solid/fonts/keenicons-solid.svg +575 -0
- package/src/components/keenicons/assets/solid/fonts/keenicons-solid.ttf +0 -0
- package/src/components/keenicons/assets/solid/fonts/keenicons-solid.woff +0 -0
- package/src/components/keenicons/assets/solid/selection.json +13048 -0
- package/src/components/keenicons/assets/solid/style.css +1721 -0
- package/src/components/keenicons/assets/styles.css +4 -0
- package/src/components/keenicons/index.ts +2 -0
- package/src/components/keenicons/keenicons.tsx +16 -0
- package/src/components/keenicons/types.ts +7 -0
- package/src/components/nav-footer.tsx +49 -0
- package/src/components/nav-main.tsx +53 -0
- package/src/components/nav-user.tsx +59 -0
- package/src/components/notification-bell.tsx +190 -0
- package/src/components/products/product-card.tsx +159 -0
- package/src/components/text-link.tsx +23 -0
- package/src/components/ui/accordion-menu.tsx +322 -0
- package/src/components/ui/accordion.tsx +133 -0
- package/src/components/ui/alert-dialog.tsx +82 -0
- package/src/components/ui/alert.tsx +63 -0
- package/src/components/ui/avatar-group.tsx +129 -0
- package/src/components/ui/avatar.tsx +67 -0
- package/src/components/ui/badge.tsx +230 -0
- package/src/components/ui/breadcrumb.tsx +88 -0
- package/src/components/ui/button.tsx +412 -0
- package/src/components/ui/calendar.tsx +56 -0
- package/src/components/ui/card.tsx +147 -0
- package/src/components/ui/chart.tsx +290 -0
- package/src/components/ui/checkbox.tsx +47 -0
- package/src/components/ui/code.tsx +45 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/command-palette.tsx +189 -0
- package/src/components/ui/command.tsx +138 -0
- package/src/components/ui/cookie-banner.tsx +220 -0
- package/src/components/ui/copy-button.tsx +60 -0
- package/src/components/ui/data-grid-column-filter.tsx +124 -0
- package/src/components/ui/data-grid-column-header.tsx +284 -0
- package/src/components/ui/data-grid-column-visibility.tsx +38 -0
- package/src/components/ui/data-grid-pagination.tsx +206 -0
- package/src/components/ui/data-grid-table-dnd-rows.tsx +147 -0
- package/src/components/ui/data-grid-table-dnd.tsx +175 -0
- package/src/components/ui/data-grid-table.tsx +500 -0
- package/src/components/ui/data-grid.tsx +193 -0
- package/src/components/ui/data-list.tsx +76 -0
- package/src/components/ui/datefield.tsx +91 -0
- package/src/components/ui/dialog.tsx +139 -0
- package/src/components/ui/divider.tsx +41 -0
- package/src/components/ui/drawer.tsx +59 -0
- package/src/components/ui/dropdown-menu.tsx +224 -0
- package/src/components/ui/empty-state.tsx +54 -0
- package/src/components/ui/file-upload.tsx +152 -0
- package/src/components/ui/form.tsx +88 -0
- package/src/components/ui/icon.tsx +14 -0
- package/src/components/ui/input-otp.tsx +71 -0
- package/src/components/ui/input.tsx +155 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +31 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/pagination.tsx +37 -0
- package/src/components/ui/placeholder-pattern.tsx +21 -0
- package/src/components/ui/popover.tsx +50 -0
- package/src/components/ui/progress.tsx +65 -0
- package/src/components/ui/radio-group.tsx +73 -0
- package/src/components/ui/resizable.tsx +39 -0
- package/src/components/ui/scroll-area.tsx +50 -0
- package/src/components/ui/select.tsx +234 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/sheet.tsx +147 -0
- package/src/components/ui/sidebar.tsx +721 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/slider.tsx +35 -0
- package/src/components/ui/sonner.tsx +28 -0
- package/src/components/ui/sortable.tsx +724 -0
- package/src/components/ui/spinner.tsx +17 -0
- package/src/components/ui/stat-card.tsx +82 -0
- package/src/components/ui/stepper.tsx +410 -0
- package/src/components/ui/switch.tsx +68 -0
- package/src/components/ui/table.tsx +42 -0
- package/src/components/ui/tabs.tsx +196 -0
- package/src/components/ui/timeline.tsx +90 -0
- package/src/components/ui/toggle-group.tsx +73 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +55 -0
- package/src/components/user-info.tsx +33 -0
- package/src/components/user-menu-content.tsx +53 -0
- package/src/components/web/SiteFooter.tsx +154 -0
- package/src/components/web/SiteHeader.tsx +159 -0
- package/src/components/workflows/workflow-canvas.tsx +321 -0
- package/src/controls/Blockquote.tsx +25 -0
- package/src/controls/Button.tsx +101 -0
- package/src/controls/Checkbox.tsx +29 -0
- package/src/controls/DateField.tsx +37 -0
- package/src/controls/FormField.tsx +20 -0
- package/src/controls/Heading.tsx +28 -0
- package/src/controls/Input.tsx +21 -0
- package/src/controls/Label.tsx +18 -0
- package/src/controls/Paragraph.tsx +39 -0
- package/src/controls/PasswordInput.tsx +40 -0
- package/src/controls/RadioGroup.tsx +70 -0
- package/src/controls/Select.tsx +24 -0
- package/src/controls/Slider.tsx +33 -0
- package/src/controls/Switch.tsx +31 -0
- package/src/controls/Textarea.tsx +22 -0
- package/src/elements/ConfirmPasswordForm.tsx +43 -0
- package/src/elements/DeviceStatusBadge.tsx +38 -0
- package/src/elements/DriverCard.tsx +67 -0
- package/src/elements/ForgotPasswordForm.tsx +64 -0
- package/src/elements/IncidentCard.tsx +67 -0
- package/src/elements/LoginForm.tsx +100 -0
- package/src/elements/OtpForm.tsx +71 -0
- package/src/elements/RegisterForm.tsx +150 -0
- package/src/elements/ResetPasswordForm.tsx +72 -0
- package/src/elements/SmsChallengeForm.tsx +104 -0
- package/src/elements/VehicleCard.tsx +73 -0
- package/src/elements/VerifyEmailForm.tsx +39 -0
- package/src/hooks/use-appearance.tsx +117 -0
- package/src/hooks/use-applied-theme.ts +98 -0
- package/src/hooks/use-clipboard.ts +34 -0
- package/src/hooks/use-current-url.ts +83 -0
- package/src/hooks/use-dark-mode.ts +48 -0
- package/src/hooks/use-flash-toast.ts +29 -0
- package/src/hooks/use-initials.tsx +24 -0
- package/src/hooks/use-mobile-navigation.ts +12 -0
- package/src/hooks/use-mobile.tsx +38 -0
- package/src/index.ts +408 -0
- package/src/layouts/AppLayout.tsx +60 -0
- package/src/layouts/AuthLayout.tsx +32 -0
- package/src/layouts/SettingsLayout.tsx +21 -0
- package/src/layouts/app/AIChatLayout.tsx +73 -0
- package/src/layouts/app/AsideSidebarLayout.tsx +3 -0
- package/src/layouts/app/CalendarSidebarLayout.tsx +69 -0
- package/src/layouts/app/CommunitiesNavbarLayout.tsx +3 -0
- package/src/layouts/app/DualNavbarSidebarLayout.tsx +3 -0
- package/src/layouts/app/FocusSidebarLayout.tsx +75 -0
- package/src/layouts/app/MailLayout.tsx +69 -0
- package/src/layouts/app/MegaMenuHeaderLayout.tsx +3 -0
- package/src/layouts/app/MegaMenuLayout.tsx +81 -0
- package/src/layouts/app/MegaMenuNavbarLayout.tsx +88 -0
- package/src/layouts/app/MegaMenuSearchNavbarLayout.tsx +3 -0
- package/src/layouts/app/NavbarCollapsibleLayout.tsx +88 -0
- package/src/layouts/app/NavbarCollapsibleLinksLayout.tsx +3 -0
- package/src/layouts/app/NavbarMinimalLayout.tsx +3 -0
- package/src/layouts/app/NavbarMinimalSidebarLayout.tsx +3 -0
- package/src/layouts/app/NavbarSidebarDashboardLayout.tsx +3 -0
- package/src/layouts/app/NavbarSidebarLayout.tsx +92 -0
- package/src/layouts/app/NavbarSimpleSidebarLayout.tsx +3 -0
- package/src/layouts/app/NavbarTitledSidebarLayout.tsx +3 -0
- package/src/layouts/app/PanelSidebarLayout.tsx +3 -0
- package/src/layouts/app/SearchNavbarSidebarLayout.tsx +3 -0
- package/src/layouts/app/SidebarBreadcrumbLayout.tsx +3 -0
- package/src/layouts/app/SidebarCleanLayout.tsx +3 -0
- package/src/layouts/app/SidebarCommunitiesLayout.tsx +3 -0
- package/src/layouts/app/SidebarContentLayout.tsx +3 -0
- package/src/layouts/app/SidebarDualMenuLayout.tsx +104 -0
- package/src/layouts/app/SidebarFixedLayout.tsx +166 -0
- package/src/layouts/app/SidebarFooterNavbarLayout.tsx +3 -0
- package/src/layouts/app/SidebarHeaderMenuLayout.tsx +3 -0
- package/src/layouts/app/SidebarMegaMenuLayout.tsx +4 -0
- package/src/layouts/app/SidebarMinimalLayout.tsx +70 -0
- package/src/layouts/app/SidebarMobileSearchLayout.tsx +3 -0
- package/src/layouts/app/SidebarMultiPanelLayout.tsx +3 -0
- package/src/layouts/app/SidebarPrimarySecondaryLayout.tsx +3 -0
- package/src/layouts/app/SidebarSearchHeaderLayout.tsx +103 -0
- package/src/layouts/app/SidebarSearchToolbarLayout.tsx +3 -0
- package/src/layouts/app/SidebarTabsDualLayout.tsx +3 -0
- package/src/layouts/app/SidebarTabsLayout.tsx +98 -0
- package/src/layouts/app/SidebarTreeLayout.tsx +3 -0
- package/src/layouts/app/SplitNavbarLayout.tsx +3 -0
- package/src/layouts/app/SplitSidebarDashboardLayout.tsx +3 -0
- package/src/layouts/app/SplitSidebarLayout.tsx +99 -0
- package/src/layouts/app/TopNavLayout.tsx +105 -0
- package/src/layouts/app/TopNavLinksLayout.tsx +3 -0
- package/src/layouts/app/WorkspaceBreadcrumbLayout.tsx +3 -0
- package/src/layouts/app/WorkspaceCommunitiesLayout.tsx +3 -0
- package/src/layouts/app/WorkspaceNavbarLayout.tsx +3 -0
- package/src/layouts/app/WorkspaceSidebarLayout.tsx +98 -0
- package/src/layouts/app/WorkspaceSidebarTitleLayout.tsx +3 -0
- package/src/layouts/app/app-header-layout.tsx +45 -0
- package/src/layouts/app/app-sidebar-layout.tsx +56 -0
- package/src/layouts/app/layout-context.tsx +44 -0
- package/src/layouts/app/layout-types.ts +47 -0
- package/src/layouts/app/partials/Footer.tsx +35 -0
- package/src/layouts/app/partials/HeaderTopbar.tsx +96 -0
- package/src/layouts/app/partials/Navbar.tsx +85 -0
- package/src/layouts/app/partials/Toolbar.tsx +47 -0
- package/src/layouts/app-layout.tsx +29 -0
- package/src/layouts/auth/AuthBrandedLayout.tsx +58 -0
- package/src/layouts/auth/AuthCardLayout.tsx +31 -0
- package/src/layouts/auth/AuthCenteredLayout.tsx +41 -0
- package/src/layouts/auth/AuthClassicLayout.tsx +41 -0
- package/src/layouts/auth/AuthSimpleLayout.tsx +33 -0
- package/src/layouts/auth/AuthSplitLayout.tsx +89 -0
- package/src/layouts/web-app-layout.tsx +162 -0
- package/src/layouts/web-layout.tsx +23 -0
- package/src/lib/datetime.ts +188 -0
- package/src/lib/google-maps-loader.ts +99 -0
- package/src/lib/location.ts +127 -0
- package/src/lib/lucide-icon-map.ts +132 -0
- package/src/lib/map-markers.ts +124 -0
- package/src/lib/map-styles.ts +351 -0
- package/src/lib/utils.ts +11 -0
- package/src/platform/adapters/default.tsx +156 -0
- package/src/platform/adapters/inertia.tsx +88 -0
- package/src/platform/adapters/nextjs.ts +86 -0
- package/src/platform/context.tsx +106 -0
- package/src/platform/index.ts +27 -0
- package/src/platform/types.ts +105 -0
- package/src/styles/layouts/sidebar-fixed.css +161 -0
- package/src/styles/themes.css +583 -0
- package/src/types/assets.d.ts +5 -0
- package/src/types/auth.ts +25 -0
- package/src/types/global.d.ts +13 -0
- package/src/types/index.ts +9 -0
- package/src/types/navigation.ts +15 -0
- package/src/types/ui.ts +32 -0
|
@@ -0,0 +1,69 @@
|
|
|
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 { HeaderTopbar } from './partials/HeaderTopbar';
|
|
7
|
+
import { ScrollArea } from '../../components/ui/scroll-area';
|
|
8
|
+
import { Toolbar } from './partials/Toolbar';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CalendarSidebarLayout (layout-36)
|
|
12
|
+
* Sidebar with mini calendar + category list + main calendar area.
|
|
13
|
+
*/
|
|
14
|
+
interface CalendarSidebarLayoutProps extends BaseAppLayoutProps {
|
|
15
|
+
miniCalendar?: ReactNode;
|
|
16
|
+
categories?: Array<{ name: string; color: string; visible?: boolean }>;
|
|
17
|
+
showToolbar?: boolean;
|
|
18
|
+
sidebarFooter?: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function CalendarSidebarLayout({
|
|
22
|
+
children, currentUrl = '',
|
|
23
|
+
logo, logoHref = '/', appName, user,
|
|
24
|
+
title, breadcrumbs = [], toolbarActions,
|
|
25
|
+
settingsUrl, logoutUrl, onLogout, unreadCount = 0,
|
|
26
|
+
miniCalendar, categories = [], sidebarFooter,
|
|
27
|
+
showToolbar = true,
|
|
28
|
+
}: CalendarSidebarLayoutProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex min-h-screen">
|
|
31
|
+
<aside className="w-64 shrink-0 flex flex-col border-e border-sidebar-border bg-sidebar">
|
|
32
|
+
<div className="flex items-center gap-2 px-4 h-[70px] border-b border-sidebar-border shrink-0">
|
|
33
|
+
{logo && <a href={logoHref}>{logo}</a>}
|
|
34
|
+
{appName && <span className="text-sm font-semibold text-sidebar-foreground">{appName}</span>}
|
|
35
|
+
</div>
|
|
36
|
+
<ScrollArea className="flex-1">
|
|
37
|
+
{miniCalendar && <div className="p-3 border-b border-sidebar-border">{miniCalendar}</div>}
|
|
38
|
+
{categories.length > 0 && (
|
|
39
|
+
<div className="p-3">
|
|
40
|
+
<p className="text-xs font-medium text-muted-foreground mb-2 px-1">Calendars</p>
|
|
41
|
+
<div className="space-y-1">
|
|
42
|
+
{categories.map((cat, i) => (
|
|
43
|
+
<label key={i} className="flex items-center gap-2 px-1 py-1 rounded text-sm cursor-pointer hover:bg-sidebar-accent">
|
|
44
|
+
<span className="size-3 rounded-full shrink-0" style={{ backgroundColor: cat.color }} />
|
|
45
|
+
<span className="text-sidebar-foreground">{cat.name}</span>
|
|
46
|
+
</label>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</ScrollArea>
|
|
52
|
+
{sidebarFooter && <div className="p-3 border-t border-sidebar-border">{sidebarFooter}</div>}
|
|
53
|
+
</aside>
|
|
54
|
+
|
|
55
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
56
|
+
<header className="flex items-center h-[70px] border-b border-border bg-background px-4 shrink-0">
|
|
57
|
+
<div className="flex-1" />
|
|
58
|
+
<HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
|
|
59
|
+
</header>
|
|
60
|
+
<main className="flex-1 overflow-hidden flex flex-col" role="content">
|
|
61
|
+
{showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
|
|
62
|
+
<Toolbar title={title} breadcrumbs={breadcrumbs} actions={toolbarActions} currentUrl={currentUrl} />
|
|
63
|
+
)}
|
|
64
|
+
{children}
|
|
65
|
+
</main>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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 { HeaderTopbar } from './partials/HeaderTopbar';
|
|
7
|
+
import { ScrollArea } from '../../components/ui/scroll-area';
|
|
8
|
+
import { AccordionMenu, AccordionMenuGroup, AccordionMenuItem } from '../../components/ui/accordion-menu';
|
|
9
|
+
import { Button } from '../../controls/Button';
|
|
10
|
+
import { Search } from 'lucide-react';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* FocusSidebarLayout (layout-39)
|
|
14
|
+
* Todo/task-focused sidebar: focus card + tag list + task groups + toolbar search.
|
|
15
|
+
*/
|
|
16
|
+
interface FocusSidebarLayoutProps extends BaseAppLayoutProps {
|
|
17
|
+
focusCard?: ReactNode;
|
|
18
|
+
tags?: Array<{ name: string; count?: number; href: string }>;
|
|
19
|
+
headerSearchPlaceholder?: string;
|
|
20
|
+
sidebarFooter?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function FocusSidebarLayout({
|
|
24
|
+
children, navItems = [], currentUrl = '',
|
|
25
|
+
logo, logoHref = '/', user,
|
|
26
|
+
settingsUrl, logoutUrl, onLogout, unreadCount = 0,
|
|
27
|
+
focusCard, tags = [], sidebarFooter,
|
|
28
|
+
}: FocusSidebarLayoutProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex min-h-screen">
|
|
31
|
+
<aside className="w-64 shrink-0 flex flex-col border-e border-sidebar-border bg-sidebar">
|
|
32
|
+
<div className="flex items-center justify-between px-4 h-[70px] border-b border-sidebar-border shrink-0">
|
|
33
|
+
{logo && <a href={logoHref}>{logo}</a>}
|
|
34
|
+
<Button variant="ghost" size="sm" className="size-8 p-0 ms-auto">
|
|
35
|
+
<Search className="size-4" />
|
|
36
|
+
</Button>
|
|
37
|
+
</div>
|
|
38
|
+
<ScrollArea className="flex-1 py-2 px-2">
|
|
39
|
+
{focusCard && <div className="mb-3 p-2 rounded-lg bg-primary/10 border border-primary/20">{focusCard}</div>}
|
|
40
|
+
<AccordionMenu type="single" collapsible matchPath={(href) => !!href && currentUrl.startsWith(href)} selectedValue={currentUrl}>
|
|
41
|
+
<AccordionMenuGroup>
|
|
42
|
+
{navItems.map((item, i) => (
|
|
43
|
+
<AccordionMenuItem key={i} value={item.href ?? item.title} asChild>
|
|
44
|
+
<a href={item.href}>{item.title}</a>
|
|
45
|
+
</AccordionMenuItem>
|
|
46
|
+
))}
|
|
47
|
+
</AccordionMenuGroup>
|
|
48
|
+
{tags.length > 0 && (
|
|
49
|
+
<AccordionMenuGroup>
|
|
50
|
+
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">Tags</div>
|
|
51
|
+
{tags.map((tag, i) => (
|
|
52
|
+
<AccordionMenuItem key={i} value={tag.href} asChild>
|
|
53
|
+
<a href={tag.href} className="flex justify-between">
|
|
54
|
+
<span>{tag.name}</span>
|
|
55
|
+
{tag.count !== undefined && <span className="text-muted-foreground text-xs">{tag.count}</span>}
|
|
56
|
+
</a>
|
|
57
|
+
</AccordionMenuItem>
|
|
58
|
+
))}
|
|
59
|
+
</AccordionMenuGroup>
|
|
60
|
+
)}
|
|
61
|
+
</AccordionMenu>
|
|
62
|
+
</ScrollArea>
|
|
63
|
+
{sidebarFooter && <div className="p-3 border-t border-sidebar-border">{sidebarFooter}</div>}
|
|
64
|
+
</aside>
|
|
65
|
+
|
|
66
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
67
|
+
<header className="flex items-center h-[70px] border-b border-border bg-background px-4 shrink-0">
|
|
68
|
+
<div className="flex-1" />
|
|
69
|
+
<HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
|
|
70
|
+
</header>
|
|
71
|
+
<main className="flex-1" role="content">{children}</main>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { HeaderTopbar } from './partials/HeaderTopbar';
|
|
7
|
+
import { ScrollArea } from '../../components/ui/scroll-area';
|
|
8
|
+
import { AccordionMenu, AccordionMenuGroup, AccordionMenuItem } from '../../components/ui/accordion-menu';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MailLayout (layout-37)
|
|
12
|
+
* Three-column layout: sidebar nav + mail list panel + mail view panel.
|
|
13
|
+
* Used for email clients, CRM inbox, notification centre.
|
|
14
|
+
*/
|
|
15
|
+
interface MailLayoutProps extends BaseAppLayoutProps {
|
|
16
|
+
listPanel?: ReactNode;
|
|
17
|
+
viewPanel?: ReactNode;
|
|
18
|
+
sidebarFooter?: ReactNode;
|
|
19
|
+
listPanelWidth?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function MailLayout({
|
|
23
|
+
children, navItems = [], currentUrl = '',
|
|
24
|
+
logo, logoHref = '/', user,
|
|
25
|
+
settingsUrl, logoutUrl, onLogout, unreadCount = 0,
|
|
26
|
+
listPanel, viewPanel, sidebarFooter,
|
|
27
|
+
listPanelWidth = 'w-80',
|
|
28
|
+
}: MailLayoutProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex min-h-screen">
|
|
31
|
+
{/* Sidebar */}
|
|
32
|
+
<aside className="w-60 shrink-0 flex flex-col border-e border-sidebar-border bg-sidebar">
|
|
33
|
+
<div className="flex items-center gap-2 px-4 h-[70px] border-b border-sidebar-border shrink-0">
|
|
34
|
+
{logo && <a href={logoHref}>{logo}</a>}
|
|
35
|
+
</div>
|
|
36
|
+
<ScrollArea className="flex-1 py-2 px-2">
|
|
37
|
+
<AccordionMenu type="single" collapsible matchPath={(href) => !!href && currentUrl.startsWith(href)} selectedValue={currentUrl}>
|
|
38
|
+
<AccordionMenuGroup>
|
|
39
|
+
{navItems.map((item, i) => (
|
|
40
|
+
<AccordionMenuItem key={i} value={item.href ?? item.title} asChild>
|
|
41
|
+
<a href={item.href}>{item.title}</a>
|
|
42
|
+
</AccordionMenuItem>
|
|
43
|
+
))}
|
|
44
|
+
</AccordionMenuGroup>
|
|
45
|
+
</AccordionMenu>
|
|
46
|
+
</ScrollArea>
|
|
47
|
+
{sidebarFooter && <div className="p-3 border-t border-sidebar-border">{sidebarFooter}</div>}
|
|
48
|
+
</aside>
|
|
49
|
+
|
|
50
|
+
{/* List panel */}
|
|
51
|
+
{listPanel && (
|
|
52
|
+
<aside className={cn('shrink-0 border-e border-border flex flex-col', listPanelWidth)}>
|
|
53
|
+
{listPanel}
|
|
54
|
+
</aside>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{/* View panel / main */}
|
|
58
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
59
|
+
<header className="flex items-center h-[70px] border-b border-border bg-background px-4 shrink-0">
|
|
60
|
+
<div className="flex-1" />
|
|
61
|
+
<HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
|
|
62
|
+
</header>
|
|
63
|
+
<main className="flex-1 overflow-auto" role="content">
|
|
64
|
+
{viewPanel ?? children}
|
|
65
|
+
</main>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../components/ui/dropdown-menu';
|
|
11
|
+
import { ChevronDown } from 'lucide-react';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MegaMenuLayout (demo7)
|
|
15
|
+
* Fixed header with integrated horizontal mega menu. No sidebar.
|
|
16
|
+
*/
|
|
17
|
+
interface MegaMenuLayoutProps extends BaseAppLayoutProps {
|
|
18
|
+
showToolbar?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function MegaMenuLayout({
|
|
22
|
+
children, navItems = [], currentUrl = '',
|
|
23
|
+
logo, logoHref = '/', appName, user,
|
|
24
|
+
title, breadcrumbs = [], toolbarActions,
|
|
25
|
+
onLogout, settingsUrl, logoutUrl, unreadCount = 0,
|
|
26
|
+
footerLinks = [], copyright, showToolbar = true,
|
|
27
|
+
}: MegaMenuLayoutProps) {
|
|
28
|
+
function isActive(url: string) { return !!url && url !== '#' && currentUrl.startsWith(url); }
|
|
29
|
+
function hasActiveChild(items: NavItem[]): boolean { return items.some((i) => isActive(i.href ?? '') || (i.items ? hasActiveChild(i.items) : false)); }
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex flex-col min-h-screen">
|
|
33
|
+
<header className="sticky top-0 z-20 flex items-center h-[70px] border-b border-border bg-background/95 backdrop-blur-sm px-4 shrink-0">
|
|
34
|
+
<div className="container mx-auto flex items-center gap-6">
|
|
35
|
+
{logo && <a href={logoHref} className="shrink-0">{logo}</a>}
|
|
36
|
+
{appName && <span className="text-sm font-medium hidden md:inline">{appName}</span>}
|
|
37
|
+
|
|
38
|
+
{/* Mega menu inline */}
|
|
39
|
+
<nav className="flex items-stretch flex-1 overflow-x-auto">
|
|
40
|
+
{navItems.map((item, i) => {
|
|
41
|
+
const active = isActive(item.href ?? '') || (item.items ? hasActiveChild(item.items) : false);
|
|
42
|
+
if (item.items && item.items.length > 0) {
|
|
43
|
+
return (
|
|
44
|
+
<DropdownMenu key={i}>
|
|
45
|
+
<DropdownMenuTrigger asChild>
|
|
46
|
+
<button className={cn('flex items-center gap-1.5 px-3 py-3.5 text-sm text-nowrap border-b-2 transition-colors hover:text-mono bg-transparent', active ? 'text-mono border-mono' : 'text-secondary-foreground border-transparent')}>
|
|
47
|
+
{item.title} <ChevronDown className="size-3.5" />
|
|
48
|
+
</button>
|
|
49
|
+
</DropdownMenuTrigger>
|
|
50
|
+
<DropdownMenuContent className="min-w-[200px]">
|
|
51
|
+
{item.items.map((child, ci) => (
|
|
52
|
+
<DropdownMenuItem key={ci} asChild>
|
|
53
|
+
<a href={child.href} className={cn(isActive(child.href ?? '') && 'bg-accent')}>{child.title}</a>
|
|
54
|
+
</DropdownMenuItem>
|
|
55
|
+
))}
|
|
56
|
+
</DropdownMenuContent>
|
|
57
|
+
</DropdownMenu>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return (
|
|
61
|
+
<a key={i} href={item.href} className={cn('flex items-center px-3 py-3.5 text-sm text-nowrap border-b-2 transition-colors hover:text-mono', active ? 'text-mono border-mono' : 'text-secondary-foreground border-transparent')}>
|
|
62
|
+
{item.title}
|
|
63
|
+
</a>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</nav>
|
|
67
|
+
|
|
68
|
+
<HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
|
|
69
|
+
</div>
|
|
70
|
+
</header>
|
|
71
|
+
|
|
72
|
+
<main className="flex-1" role="content">
|
|
73
|
+
{showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
|
|
74
|
+
<Toolbar title={title} breadcrumbs={breadcrumbs} actions={toolbarActions} currentUrl={currentUrl} />
|
|
75
|
+
)}
|
|
76
|
+
{children}
|
|
77
|
+
</main>
|
|
78
|
+
<Footer links={footerLinks} copyright={copyright} />
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { BaseAppLayoutProps } from './layout-types';
|
|
4
|
+
import type { NavItem } from '../../types/navigation';
|
|
5
|
+
import { Navbar } from './partials/Navbar';
|
|
6
|
+
import { Toolbar } from './partials/Toolbar';
|
|
7
|
+
import { Footer } from './partials/Footer';
|
|
8
|
+
import { HeaderTopbar } from './partials/HeaderTopbar';
|
|
9
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../../components/ui/dropdown-menu';
|
|
10
|
+
import { ChevronDown, Search } from 'lucide-react';
|
|
11
|
+
import { cn } from '../../lib/utils';
|
|
12
|
+
import { Button } from '../../controls/Button';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* MegaMenuNavbarLayout (demo9)
|
|
16
|
+
* Header with logo, search, and mega-menu dropdown nav + horizontal navbar below.
|
|
17
|
+
*/
|
|
18
|
+
interface MegaMenuNavbarLayoutProps extends BaseAppLayoutProps {
|
|
19
|
+
showToolbar?: boolean;
|
|
20
|
+
onSearchOpen?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function MegaMenuNavbarLayout({
|
|
24
|
+
children, navItems = [], currentUrl = '',
|
|
25
|
+
logo, logoHref = '/', appName, user,
|
|
26
|
+
title, breadcrumbs = [], toolbarActions,
|
|
27
|
+
onLogout, settingsUrl, logoutUrl, unreadCount = 0,
|
|
28
|
+
footerLinks = [], copyright, showToolbar = true, onSearchOpen,
|
|
29
|
+
}: MegaMenuNavbarLayoutProps) {
|
|
30
|
+
function isActive(url: string) { return !!url && url !== '#' && currentUrl.startsWith(url); }
|
|
31
|
+
function hasActiveChild(items: NavItem[]): boolean { return items.some((i) => isActive(i.href ?? '') || (i.items ? hasActiveChild(i.items) : false)); }
|
|
32
|
+
|
|
33
|
+
// Split navItems: top-level as mega menu triggers, sub-items go in dropdown
|
|
34
|
+
const megaItems = navItems;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex flex-col min-h-screen">
|
|
38
|
+
{/* Header with mega menu */}
|
|
39
|
+
<header className="sticky top-0 z-20 flex items-center h-[70px] border-b border-border bg-background/95 backdrop-blur-sm px-4 shrink-0">
|
|
40
|
+
<div className="container mx-auto flex items-center gap-4">
|
|
41
|
+
{logo && <a href={logoHref} className="shrink-0">{logo}</a>}
|
|
42
|
+
{appName && <span className="text-sm font-medium hidden md:inline">{appName}</span>}
|
|
43
|
+
|
|
44
|
+
{/* Mega menu */}
|
|
45
|
+
<nav className="flex items-stretch flex-1 overflow-x-auto">
|
|
46
|
+
{megaItems.map((item, i) => {
|
|
47
|
+
const active = isActive(item.href ?? '') || (item.items ? hasActiveChild(item.items) : false);
|
|
48
|
+
if (item.items && item.items.length > 0) {
|
|
49
|
+
return (
|
|
50
|
+
<DropdownMenu key={i}>
|
|
51
|
+
<DropdownMenuTrigger asChild>
|
|
52
|
+
<button className={cn('flex items-center gap-1 px-3 py-3.5 text-sm border-b-2 transition-colors hover:text-mono bg-transparent text-nowrap', active ? 'text-mono border-mono' : 'text-secondary-foreground border-transparent')}>
|
|
53
|
+
{item.title} <ChevronDown className="size-3" />
|
|
54
|
+
</button>
|
|
55
|
+
</DropdownMenuTrigger>
|
|
56
|
+
<DropdownMenuContent className="min-w-[180px]">
|
|
57
|
+
{item.items.map((c, ci) => <DropdownMenuItem key={ci} asChild><a href={c.href}>{c.title}</a></DropdownMenuItem>)}
|
|
58
|
+
</DropdownMenuContent>
|
|
59
|
+
</DropdownMenu>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
<a key={i} href={item.href} className={cn('flex items-center px-3 py-3.5 text-sm border-b-2 transition-colors hover:text-mono text-nowrap', active ? 'text-mono border-mono' : 'text-secondary-foreground border-transparent')}>
|
|
64
|
+
{item.title}
|
|
65
|
+
</a>
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</nav>
|
|
69
|
+
|
|
70
|
+
{onSearchOpen && (
|
|
71
|
+
<Button variant="ghost" size="sm" className="size-9 p-0 rounded-full" onClick={onSearchOpen}>
|
|
72
|
+
<Search className="size-4" />
|
|
73
|
+
</Button>
|
|
74
|
+
)}
|
|
75
|
+
<HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
|
|
76
|
+
</div>
|
|
77
|
+
</header>
|
|
78
|
+
|
|
79
|
+
<main className="flex-1" role="content">
|
|
80
|
+
{showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
|
|
81
|
+
<Toolbar title={title} breadcrumbs={breadcrumbs} actions={toolbarActions} currentUrl={currentUrl} />
|
|
82
|
+
)}
|
|
83
|
+
{children}
|
|
84
|
+
</main>
|
|
85
|
+
<Footer links={footerLinks} copyright={copyright} />
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } 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
|
+
import { AccordionMenu, AccordionMenuGroup, AccordionMenuItem, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '../../components/ui/accordion-menu';
|
|
11
|
+
import { ScrollArea } from '../../components/ui/scroll-area';
|
|
12
|
+
import { Button } from '../../controls/Button';
|
|
13
|
+
import { Menu, X } from 'lucide-react';
|
|
14
|
+
import type { NavItem } from '../../types/navigation';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* NavbarCollapsibleLayout (demo3)
|
|
18
|
+
* Header (logo + topbar) + horizontal navbar + optional collapsible sidebar.
|
|
19
|
+
* Sidebar contains section-specific sub-navigation driven by the active navbar item.
|
|
20
|
+
*/
|
|
21
|
+
interface NavbarCollapsibleLayoutProps extends BaseAppLayoutProps {
|
|
22
|
+
sidebarItems?: NavItem[];
|
|
23
|
+
showToolbar?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function NavbarCollapsibleLayout({
|
|
27
|
+
children, navItems = [], sidebarItems = [], currentUrl = '',
|
|
28
|
+
logo, logoHref = '/', appName, user,
|
|
29
|
+
title, breadcrumbs = [], toolbarActions,
|
|
30
|
+
onLogout, settingsUrl, logoutUrl, unreadCount = 0,
|
|
31
|
+
footerLinks = [], copyright, showToolbar = true,
|
|
32
|
+
defaultSidebarCollapsed = false,
|
|
33
|
+
}: NavbarCollapsibleLayoutProps) {
|
|
34
|
+
const [sidebarOpen, setSidebarOpen] = useState(!defaultSidebarCollapsed);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex flex-col min-h-screen">
|
|
38
|
+
{/* Header */}
|
|
39
|
+
<header className="flex items-center h-[70px] border-b border-border bg-background px-4 shrink-0">
|
|
40
|
+
<div className="container mx-auto flex justify-between items-center gap-4">
|
|
41
|
+
<div className="flex items-center gap-3">
|
|
42
|
+
{logo && <a href={logoHref}>{logo}</a>}
|
|
43
|
+
{appName && <span className="text-sm font-medium hidden md:inline">{appName}</span>}
|
|
44
|
+
</div>
|
|
45
|
+
<HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
|
|
46
|
+
</div>
|
|
47
|
+
</header>
|
|
48
|
+
|
|
49
|
+
{/* Navbar */}
|
|
50
|
+
<Navbar navItems={navItems} currentUrl={currentUrl} />
|
|
51
|
+
|
|
52
|
+
{/* Body: optional sidebar + content */}
|
|
53
|
+
<div className="flex flex-1">
|
|
54
|
+
{sidebarItems.length > 0 && (
|
|
55
|
+
<>
|
|
56
|
+
<aside className={cn('w-64 shrink-0 border-e border-border bg-sidebar hidden lg:block', !sidebarOpen && 'hidden')}>
|
|
57
|
+
<ScrollArea className="h-full py-3 px-2">
|
|
58
|
+
<AccordionMenu type="single" collapsible matchPath={(href) => !!href && currentUrl.startsWith(href)} selectedValue={currentUrl}>
|
|
59
|
+
<AccordionMenuGroup>
|
|
60
|
+
{sidebarItems.map((item, i) => (
|
|
61
|
+
item.items ? (
|
|
62
|
+
<AccordionMenuSub key={i} value={item.title}>
|
|
63
|
+
<AccordionMenuSubTrigger>{item.title}</AccordionMenuSubTrigger>
|
|
64
|
+
<AccordionMenuSubContent type="single" collapsible parentValue={item.title}>
|
|
65
|
+
{item.items.map((c, ci) => <AccordionMenuItem key={ci} value={c.href ?? c.title} asChild><a href={c.href}>{c.title}</a></AccordionMenuItem>)}
|
|
66
|
+
</AccordionMenuSubContent>
|
|
67
|
+
</AccordionMenuSub>
|
|
68
|
+
) : (
|
|
69
|
+
<AccordionMenuItem key={i} value={item.href ?? item.title} asChild><a href={item.href}>{item.title}</a></AccordionMenuItem>
|
|
70
|
+
)
|
|
71
|
+
))}
|
|
72
|
+
</AccordionMenuGroup>
|
|
73
|
+
</AccordionMenu>
|
|
74
|
+
</ScrollArea>
|
|
75
|
+
</aside>
|
|
76
|
+
</>
|
|
77
|
+
)}
|
|
78
|
+
<main className="flex-1 min-w-0" role="content">
|
|
79
|
+
{showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
|
|
80
|
+
<Toolbar title={title} breadcrumbs={breadcrumbs} actions={toolbarActions} currentUrl={currentUrl} />
|
|
81
|
+
)}
|
|
82
|
+
{children}
|
|
83
|
+
</main>
|
|
84
|
+
</div>
|
|
85
|
+
<Footer links={footerLinks} copyright={copyright} />
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import type { BaseAppLayoutProps } from './layout-types';
|
|
6
|
+
import type { NavItem } from '../../types/navigation';
|
|
7
|
+
import { Navbar } from './partials/Navbar';
|
|
8
|
+
import { Toolbar } from './partials/Toolbar';
|
|
9
|
+
import { Footer } from './partials/Footer';
|
|
10
|
+
import { HeaderTopbar } from './partials/HeaderTopbar';
|
|
11
|
+
import { AccordionMenu, AccordionMenuGroup, AccordionMenuItem, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '../../components/ui/accordion-menu';
|
|
12
|
+
import { ScrollArea } from '../../components/ui/scroll-area';
|
|
13
|
+
import { Button } from '../../controls/Button';
|
|
14
|
+
import { Menu } from 'lucide-react';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* NavbarSidebarLayout (demo5)
|
|
18
|
+
* Header (logo + topbar) + horizontal navbar + collapsible sidebar.
|
|
19
|
+
* Sidebar nav changes based on the active navbar section.
|
|
20
|
+
*/
|
|
21
|
+
interface NavbarSidebarLayoutProps extends BaseAppLayoutProps {
|
|
22
|
+
sidebarItems?: NavItem[];
|
|
23
|
+
showToolbar?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function NavbarSidebarLayout({
|
|
27
|
+
children, navItems = [], sidebarItems = [], currentUrl = '',
|
|
28
|
+
logo, logoHref = '/', appName, user,
|
|
29
|
+
title, breadcrumbs = [], toolbarActions,
|
|
30
|
+
onLogout, settingsUrl, logoutUrl, unreadCount = 0,
|
|
31
|
+
footerLinks = [], copyright, showToolbar = true,
|
|
32
|
+
defaultSidebarCollapsed = false,
|
|
33
|
+
}: NavbarSidebarLayoutProps) {
|
|
34
|
+
const [collapsed, setCollapsed] = useState(defaultSidebarCollapsed);
|
|
35
|
+
const effectiveSidebarItems = sidebarItems.length > 0 ? sidebarItems : [];
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex flex-col min-h-screen">
|
|
39
|
+
{/* Header */}
|
|
40
|
+
<header className="flex items-center h-[70px] border-b border-border bg-background px-4 shrink-0">
|
|
41
|
+
<div className="container mx-auto flex justify-between items-center gap-4">
|
|
42
|
+
<div className="flex items-center gap-3">
|
|
43
|
+
{logo && <a href={logoHref}>{logo}</a>}
|
|
44
|
+
{appName && <span className="text-sm font-medium hidden md:inline">{appName}</span>}
|
|
45
|
+
</div>
|
|
46
|
+
<HeaderTopbar user={user} unreadCount={unreadCount} settingsUrl={settingsUrl} logoutUrl={logoutUrl} onLogout={onLogout} />
|
|
47
|
+
</div>
|
|
48
|
+
</header>
|
|
49
|
+
|
|
50
|
+
{/* Navbar */}
|
|
51
|
+
<Navbar navItems={navItems} currentUrl={currentUrl} />
|
|
52
|
+
|
|
53
|
+
{/* Body */}
|
|
54
|
+
<div className="flex flex-1">
|
|
55
|
+
{effectiveSidebarItems.length > 0 && (
|
|
56
|
+
<aside className={cn('shrink-0 border-e border-sidebar-border bg-sidebar transition-all', collapsed ? 'w-0 overflow-hidden' : 'w-60')}>
|
|
57
|
+
<div className="flex items-center justify-between px-3 py-2 border-b border-sidebar-border">
|
|
58
|
+
<Button variant="ghost" size="sm" className="size-8 p-0" onClick={() => setCollapsed((c) => !c)}>
|
|
59
|
+
<Menu className="size-4" />
|
|
60
|
+
</Button>
|
|
61
|
+
</div>
|
|
62
|
+
<ScrollArea className="py-2 px-2">
|
|
63
|
+
<AccordionMenu type="single" collapsible matchPath={(href) => !!href && currentUrl.startsWith(href)} selectedValue={currentUrl}>
|
|
64
|
+
<AccordionMenuGroup>
|
|
65
|
+
{effectiveSidebarItems.map((item, i) => (
|
|
66
|
+
item.items ? (
|
|
67
|
+
<AccordionMenuSub key={i} value={item.title}>
|
|
68
|
+
<AccordionMenuSubTrigger>{item.title}</AccordionMenuSubTrigger>
|
|
69
|
+
<AccordionMenuSubContent type="single" collapsible parentValue={item.title}>
|
|
70
|
+
{item.items.map((c, ci) => <AccordionMenuItem key={ci} value={c.href ?? c.title} asChild><a href={c.href}>{c.title}</a></AccordionMenuItem>)}
|
|
71
|
+
</AccordionMenuSubContent>
|
|
72
|
+
</AccordionMenuSub>
|
|
73
|
+
) : (
|
|
74
|
+
<AccordionMenuItem key={i} value={item.href ?? item.title} asChild><a href={item.href}>{item.title}</a></AccordionMenuItem>
|
|
75
|
+
)
|
|
76
|
+
))}
|
|
77
|
+
</AccordionMenuGroup>
|
|
78
|
+
</AccordionMenu>
|
|
79
|
+
</ScrollArea>
|
|
80
|
+
</aside>
|
|
81
|
+
)}
|
|
82
|
+
<main className="flex-1 min-w-0" role="content">
|
|
83
|
+
{showToolbar && (title || breadcrumbs.length > 0 || toolbarActions) && (
|
|
84
|
+
<Toolbar title={title} breadcrumbs={breadcrumbs} actions={toolbarActions} currentUrl={currentUrl} />
|
|
85
|
+
)}
|
|
86
|
+
{children}
|
|
87
|
+
</main>
|
|
88
|
+
</div>
|
|
89
|
+
<Footer links={footerLinks} copyright={copyright} />
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|