@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,224 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import { Check, ChevronRight, Circle } from 'lucide-react';
|
|
6
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
7
|
+
|
|
8
|
+
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
9
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
13
|
+
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
17
|
+
return <DropdownMenuPrimitive.Trigger className="select-none" data-slot="dropdown-menu-trigger" {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function DropdownMenuSubTrigger({
|
|
21
|
+
className,
|
|
22
|
+
inset,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
26
|
+
inset?: boolean;
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
30
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
31
|
+
className={cn(
|
|
32
|
+
'flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-sm outline-hidden',
|
|
33
|
+
'focus:bg-accent focus:text-foreground',
|
|
34
|
+
'data-[state=open]:bg-accent data-[state=open]:text-foreground',
|
|
35
|
+
'data-[here=true]:bg-accent data-[here=true]:text-foreground',
|
|
36
|
+
'[&>svg]:pointer-events-none [&_svg:not([role=img]):not([class*=text-])]:opacity-60 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
37
|
+
inset && 'ps-8',
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
<ChevronRight data-slot="dropdown-menu-sub-trigger-indicator" className="ms-auto size-3.5! rtl:rotate-180" />
|
|
44
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function DropdownMenuSubContent({
|
|
49
|
+
className,
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
52
|
+
return (
|
|
53
|
+
<DropdownMenuPrimitive.SubContent
|
|
54
|
+
data-slot="dropdown-menu-sub-content"
|
|
55
|
+
className={cn(
|
|
56
|
+
'space-y-0.5 z-50 min-w-[8rem] overflow-hidden shadow-md shadow-black/5 rounded-md border border-border bg-popover text-popover-foreground p-2 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
57
|
+
className,
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function DropdownMenuContent({
|
|
65
|
+
className,
|
|
66
|
+
sideOffset = 4,
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
69
|
+
return (
|
|
70
|
+
<DropdownMenuPrimitive.Portal>
|
|
71
|
+
<DropdownMenuPrimitive.Content
|
|
72
|
+
data-slot="dropdown-menu-content"
|
|
73
|
+
sideOffset={sideOffset}
|
|
74
|
+
className={cn(
|
|
75
|
+
'space-y-0.5 z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-md shadow-black/5 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
</DropdownMenuPrimitive.Portal>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
85
|
+
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function DropdownMenuItem({
|
|
89
|
+
className,
|
|
90
|
+
inset,
|
|
91
|
+
variant,
|
|
92
|
+
...props
|
|
93
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
94
|
+
inset?: boolean;
|
|
95
|
+
variant?: 'destructive';
|
|
96
|
+
}) {
|
|
97
|
+
return (
|
|
98
|
+
<DropdownMenuPrimitive.Item
|
|
99
|
+
data-slot="dropdown-menu-item"
|
|
100
|
+
className={cn(
|
|
101
|
+
'text-foreground relative flex cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-hidden transition-colors data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([role=img]):not([class*=text-])]:opacity-60 [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0',
|
|
102
|
+
'focus:bg-accent focus:text-foreground',
|
|
103
|
+
'data-[active=true]:bg-accent data-[active=true]:text-accent-foreground',
|
|
104
|
+
inset && 'ps-8',
|
|
105
|
+
variant === 'destructive' &&
|
|
106
|
+
'text-destructive hover:text-destructive focus:text-destructive hover:bg-destructive/5 focus:bg-destructive/5 data-[active=true]:bg-destructive/5',
|
|
107
|
+
className,
|
|
108
|
+
)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function DropdownMenuCheckboxItem({
|
|
115
|
+
className,
|
|
116
|
+
children,
|
|
117
|
+
checked,
|
|
118
|
+
...props
|
|
119
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
120
|
+
return (
|
|
121
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
122
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
123
|
+
className={cn(
|
|
124
|
+
'relative flex cursor-default select-none items-center rounded-md py-1.5 ps-8 pe-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
127
|
+
checked={checked}
|
|
128
|
+
{...props}
|
|
129
|
+
>
|
|
130
|
+
<span className="absolute start-2 flex h-3.5 w-3.5 items-center text-muted-foreground justify-center">
|
|
131
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
132
|
+
<Check className="h-4 w-4 text-primary" />
|
|
133
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
134
|
+
</span>
|
|
135
|
+
{children}
|
|
136
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function DropdownMenuRadioItem({
|
|
141
|
+
className,
|
|
142
|
+
children,
|
|
143
|
+
...props
|
|
144
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
145
|
+
return (
|
|
146
|
+
<DropdownMenuPrimitive.RadioItem
|
|
147
|
+
data-slot="dropdown-menu-radio-item"
|
|
148
|
+
className={cn(
|
|
149
|
+
'relative flex cursor-default select-none items-center rounded-md py-1.5 ps-6 pe-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
150
|
+
className,
|
|
151
|
+
)}
|
|
152
|
+
{...props}
|
|
153
|
+
>
|
|
154
|
+
<span className="absolute start-1.5 flex h-3.5 w-3.5 items-center justify-center">
|
|
155
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
156
|
+
<Circle className="h-1.5 w-1.5 fill-primary stroke-primary" />
|
|
157
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
158
|
+
</span>
|
|
159
|
+
{children}
|
|
160
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function DropdownMenuLabel({
|
|
165
|
+
className,
|
|
166
|
+
inset,
|
|
167
|
+
...props
|
|
168
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
169
|
+
inset?: boolean;
|
|
170
|
+
}) {
|
|
171
|
+
return (
|
|
172
|
+
<DropdownMenuPrimitive.Label
|
|
173
|
+
data-slot="dropdown-menu-label"
|
|
174
|
+
className={cn('px-2 py-1.5 text-xs text-muted-foreground font-medium', inset && 'ps-8', className)}
|
|
175
|
+
{...props}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
181
|
+
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
185
|
+
return (
|
|
186
|
+
<DropdownMenuPrimitive.Separator
|
|
187
|
+
data-slot="dropdown-menu-separator"
|
|
188
|
+
className={cn('-mx-2 my-1.5 h-px bg-muted', className)}
|
|
189
|
+
{...props}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function DropdownMenuShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
|
|
195
|
+
return (
|
|
196
|
+
<span
|
|
197
|
+
data-slot="dropdown-menu-shortcut"
|
|
198
|
+
className={cn('ms-auto text-xs tracking-widest opacity-60', className)}
|
|
199
|
+
{...props}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
205
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export {
|
|
209
|
+
DropdownMenu,
|
|
210
|
+
DropdownMenuCheckboxItem,
|
|
211
|
+
DropdownMenuContent,
|
|
212
|
+
DropdownMenuGroup,
|
|
213
|
+
DropdownMenuItem,
|
|
214
|
+
DropdownMenuLabel,
|
|
215
|
+
DropdownMenuPortal,
|
|
216
|
+
DropdownMenuRadioGroup,
|
|
217
|
+
DropdownMenuRadioItem,
|
|
218
|
+
DropdownMenuSeparator,
|
|
219
|
+
DropdownMenuShortcut,
|
|
220
|
+
DropdownMenuSub,
|
|
221
|
+
DropdownMenuSubContent,
|
|
222
|
+
DropdownMenuSubTrigger,
|
|
223
|
+
DropdownMenuTrigger,
|
|
224
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
import { Inbox } from 'lucide-react';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
import { Button } from './button';
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
icon?: LucideIcon;
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
actionLabel?: string;
|
|
13
|
+
actionHref?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function EmptyState({
|
|
19
|
+
icon: Icon = Inbox,
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
actionLabel,
|
|
23
|
+
actionHref,
|
|
24
|
+
className,
|
|
25
|
+
children,
|
|
26
|
+
}: Props) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
role="status"
|
|
30
|
+
className={cn(
|
|
31
|
+
'flex flex-col items-center justify-center rounded-lg border border-dashed bg-surface px-6 py-12 text-center',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<div className="bg-primary-subtle text-primary mb-4 flex h-12 w-12 items-center justify-center rounded-full">
|
|
36
|
+
<Icon aria-hidden="true" className="size-6" />
|
|
37
|
+
</div>
|
|
38
|
+
<h3 className="font-display text-lg font-semibold text-foreground">
|
|
39
|
+
{title}
|
|
40
|
+
</h3>
|
|
41
|
+
{description && (
|
|
42
|
+
<p className="mt-1 max-w-sm text-sm text-muted-foreground">
|
|
43
|
+
{description}
|
|
44
|
+
</p>
|
|
45
|
+
)}
|
|
46
|
+
{children && <div className="mt-6">{children}</div>}
|
|
47
|
+
{!children && actionLabel && actionHref && (
|
|
48
|
+
<Button asChild className="mt-6">
|
|
49
|
+
<a href={actionHref}>{actionLabel}</a>
|
|
50
|
+
</Button>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FileUpload — drag-and-drop multi-file picker.
|
|
5
|
+
*
|
|
6
|
+
* Headless: it manages drag-state + selection internally and reports back
|
|
7
|
+
* via onChange(File[]). It does NOT upload — caller is responsible for
|
|
8
|
+
* passing the file list to an Inertia form / fetch / FormData payload.
|
|
9
|
+
*
|
|
10
|
+
* <FileUpload accept='image/*' maxSize={2 * 1024 * 1024}
|
|
11
|
+
* onChange={(files) => form.setData('logo', files[0])} />
|
|
12
|
+
*/
|
|
13
|
+
import { Upload, X } from 'lucide-react';
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
|
|
16
|
+
import { Button } from './button';
|
|
17
|
+
import { cn } from '../../lib/utils';
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
accept?: string;
|
|
21
|
+
multiple?: boolean;
|
|
22
|
+
/** Bytes. Files larger than this are rejected silently with an error message. */
|
|
23
|
+
maxSize?: number;
|
|
24
|
+
hint?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
onChange?: (files: File[]) => void;
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function FileUpload({
|
|
32
|
+
accept,
|
|
33
|
+
multiple = false,
|
|
34
|
+
maxSize,
|
|
35
|
+
hint,
|
|
36
|
+
label = 'Drop files here, or click to browse',
|
|
37
|
+
name,
|
|
38
|
+
onChange,
|
|
39
|
+
className,
|
|
40
|
+
}: Props) {
|
|
41
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
42
|
+
const [files, setFiles] = React.useState<File[]>([]);
|
|
43
|
+
const [dragging, setDragging] = React.useState(false);
|
|
44
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
45
|
+
|
|
46
|
+
const handleFiles = React.useCallback(
|
|
47
|
+
(incoming: FileList | null) => {
|
|
48
|
+
if (!incoming) return;
|
|
49
|
+
const list = Array.from(incoming);
|
|
50
|
+
|
|
51
|
+
if (maxSize) {
|
|
52
|
+
const tooBig = list.find((f) => f.size > maxSize);
|
|
53
|
+
if (tooBig) {
|
|
54
|
+
setError(
|
|
55
|
+
`${tooBig.name} exceeds the ${(maxSize / (1024 * 1024)).toFixed(1)} MB limit.`,
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setError(null);
|
|
62
|
+
const next = multiple ? [...files, ...list] : list.slice(0, 1);
|
|
63
|
+
setFiles(next);
|
|
64
|
+
onChange?.(next);
|
|
65
|
+
},
|
|
66
|
+
[files, maxSize, multiple, onChange],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const remove = (idx: number) => {
|
|
70
|
+
const next = files.filter((_, i) => i !== idx);
|
|
71
|
+
setFiles(next);
|
|
72
|
+
onChange?.(next);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className={cn('space-y-2', className)}>
|
|
77
|
+
<label
|
|
78
|
+
onDragOver={(e) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
setDragging(true);
|
|
81
|
+
}}
|
|
82
|
+
onDragLeave={() => setDragging(false)}
|
|
83
|
+
onDrop={(e) => {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
setDragging(false);
|
|
86
|
+
handleFiles(e.dataTransfer.files);
|
|
87
|
+
}}
|
|
88
|
+
className={cn(
|
|
89
|
+
'flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed bg-surface px-6 py-8 text-center transition-colors',
|
|
90
|
+
dragging
|
|
91
|
+
? 'border-primary bg-primary-subtle/40'
|
|
92
|
+
: 'border-border hover:border-primary/60',
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
<Upload
|
|
96
|
+
aria-hidden="true"
|
|
97
|
+
className="mb-2 size-6 text-muted-foreground"
|
|
98
|
+
/>
|
|
99
|
+
<span className="text-sm font-medium text-foreground">
|
|
100
|
+
{label}
|
|
101
|
+
</span>
|
|
102
|
+
{hint && (
|
|
103
|
+
<span className="mt-1 text-xs text-muted-foreground">
|
|
104
|
+
{hint}
|
|
105
|
+
</span>
|
|
106
|
+
)}
|
|
107
|
+
<input
|
|
108
|
+
ref={inputRef}
|
|
109
|
+
type="file"
|
|
110
|
+
name={name}
|
|
111
|
+
accept={accept}
|
|
112
|
+
multiple={multiple}
|
|
113
|
+
className="sr-only"
|
|
114
|
+
onChange={(e) => handleFiles(e.target.files)}
|
|
115
|
+
/>
|
|
116
|
+
</label>
|
|
117
|
+
|
|
118
|
+
{error && (
|
|
119
|
+
<p role="alert" className="text-xs text-destructive">
|
|
120
|
+
{error}
|
|
121
|
+
</p>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
{files.length > 0 && (
|
|
125
|
+
<ul className="space-y-1">
|
|
126
|
+
{files.map((file, idx) => (
|
|
127
|
+
<li
|
|
128
|
+
key={`${file.name}-${idx}`}
|
|
129
|
+
className="flex items-center justify-between rounded-md border bg-card px-3 py-2 text-sm"
|
|
130
|
+
>
|
|
131
|
+
<span className="truncate font-mono text-xs text-foreground">
|
|
132
|
+
{file.name}
|
|
133
|
+
<span className="ml-2 text-muted-foreground">
|
|
134
|
+
{(file.size / 1024).toFixed(1)} KB
|
|
135
|
+
</span>
|
|
136
|
+
</span>
|
|
137
|
+
<Button
|
|
138
|
+
type="button"
|
|
139
|
+
size="icon"
|
|
140
|
+
variant="ghost"
|
|
141
|
+
aria-label={`Remove ${file.name}`}
|
|
142
|
+
onClick={() => remove(idx)}
|
|
143
|
+
>
|
|
144
|
+
<X aria-hidden="true" className="size-4" />
|
|
145
|
+
</Button>
|
|
146
|
+
</li>
|
|
147
|
+
))}
|
|
148
|
+
</ul>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
5
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
6
|
+
import { Controller, type ControllerProps, type FieldPath, type FieldValues, FormProvider, useFormContext } from 'react-hook-form';
|
|
7
|
+
import { cn } from '../../lib/utils';
|
|
8
|
+
import { Label } from '../../controls/Label';
|
|
9
|
+
|
|
10
|
+
const Form = FormProvider;
|
|
11
|
+
|
|
12
|
+
type FormFieldContextValue<
|
|
13
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
14
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
15
|
+
> = { name: TName };
|
|
16
|
+
|
|
17
|
+
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
|
18
|
+
|
|
19
|
+
const FormField = <
|
|
20
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
21
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
22
|
+
>({ ...props }: ControllerProps<TFieldValues, TName>) => (
|
|
23
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
24
|
+
<Controller {...props} />
|
|
25
|
+
</FormFieldContext.Provider>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
type FormItemContextValue = { id: string };
|
|
29
|
+
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
|
30
|
+
|
|
31
|
+
const useFormField = () => {
|
|
32
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
33
|
+
const itemContext = React.useContext(FormItemContext);
|
|
34
|
+
const { getFieldState, formState } = useFormContext();
|
|
35
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
36
|
+
const { id } = itemContext;
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
name: fieldContext.name,
|
|
40
|
+
formItemId: `${id}-form-item`,
|
|
41
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
42
|
+
formMessageId: `${id}-form-item-message`,
|
|
43
|
+
...fieldState,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function FormItem({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
48
|
+
const id = React.useId();
|
|
49
|
+
const { error } = useFormField();
|
|
50
|
+
return (
|
|
51
|
+
<FormItemContext.Provider value={{ id }}>
|
|
52
|
+
<div data-slot="form-item" className={cn('flex flex-col gap-2.5', className)} data-invalid={!!error} {...props} />
|
|
53
|
+
</FormItemContext.Provider>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
58
|
+
const { formItemId } = useFormField();
|
|
59
|
+
return <Label data-slot="form-label" className={cn('font-medium text-foreground', className)} htmlFor={formItemId} {...props} />;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
|
63
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
64
|
+
return (
|
|
65
|
+
<Slot
|
|
66
|
+
data-slot="form-control"
|
|
67
|
+
id={formItemId}
|
|
68
|
+
aria-describedby={!error ? formDescriptionId : `${formDescriptionId} ${formMessageId}`}
|
|
69
|
+
aria-invalid={!!error}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function FormDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
76
|
+
const { formDescriptionId, error } = useFormField();
|
|
77
|
+
if (error) return null;
|
|
78
|
+
return <div data-slot="form-description" id={formDescriptionId} className={cn('text-xs text-muted-foreground -mt-0.5', className)} {...props} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function FormMessage({ className, children, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
82
|
+
const { error, formMessageId } = useFormField();
|
|
83
|
+
const body = error ? String(error?.message) : children;
|
|
84
|
+
if (!body) return null;
|
|
85
|
+
return <div data-slot="form-message" id={formMessageId} className={cn('-mt-0.5 text-xs font-normal text-destructive', className)} {...props}>{body}</div>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
interface IconProps {
|
|
4
|
+
iconNode?: LucideIcon | null;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Icon({ iconNode: IconComponent, className }: IconProps) {
|
|
9
|
+
if (!IconComponent) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return <IconComponent className={className} />;
|
|
14
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { OTPInput, OTPInputContext } from "input-otp"
|
|
4
|
+
import { Minus } from "lucide-react"
|
|
5
|
+
import * as React from "react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
const InputOTP = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof OTPInput>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof OTPInput>
|
|
12
|
+
>(({ className, containerClassName, ...props }, ref) => (
|
|
13
|
+
<OTPInput
|
|
14
|
+
ref={ref}
|
|
15
|
+
containerClassName={cn(
|
|
16
|
+
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
|
17
|
+
containerClassName
|
|
18
|
+
)}
|
|
19
|
+
className={cn("disabled:cursor-not-allowed", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
))
|
|
23
|
+
InputOTP.displayName = "InputOTP"
|
|
24
|
+
|
|
25
|
+
const InputOTPGroup = React.forwardRef<
|
|
26
|
+
React.ElementRef<"div">,
|
|
27
|
+
React.ComponentPropsWithoutRef<"div">
|
|
28
|
+
>(({ className, ...props }, ref) => (
|
|
29
|
+
<div ref={ref} className={cn("flex items-center", className)} {...props} />
|
|
30
|
+
))
|
|
31
|
+
InputOTPGroup.displayName = "InputOTPGroup"
|
|
32
|
+
|
|
33
|
+
const InputOTPSlot = React.forwardRef<
|
|
34
|
+
React.ElementRef<"div">,
|
|
35
|
+
React.ComponentPropsWithoutRef<"div"> & { index: number }
|
|
36
|
+
>(({ index, className, ...props }, ref) => {
|
|
37
|
+
const inputOTPContext = React.useContext(OTPInputContext)
|
|
38
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn(
|
|
44
|
+
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
|
|
45
|
+
isActive && "z-10 ring-1 ring-ring",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{char}
|
|
51
|
+
{hasFakeCaret && (
|
|
52
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
53
|
+
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
InputOTPSlot.displayName = "InputOTPSlot"
|
|
60
|
+
|
|
61
|
+
const InputOTPSeparator = React.forwardRef<
|
|
62
|
+
React.ElementRef<"div">,
|
|
63
|
+
React.ComponentPropsWithoutRef<"div">
|
|
64
|
+
>(({ ...props }, ref) => (
|
|
65
|
+
<div ref={ref} role="separator" {...props}>
|
|
66
|
+
<Minus />
|
|
67
|
+
</div>
|
|
68
|
+
))
|
|
69
|
+
InputOTPSeparator.displayName = "InputOTPSeparator"
|
|
70
|
+
|
|
71
|
+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|