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