@trackany-device/components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/package.json +185 -0
  2. package/src/assets/logo.png +0 -0
  3. package/src/assets/map/arrows/map-arrow-blue.png +0 -0
  4. package/src/assets/map/arrows/map-arrow-green.png +0 -0
  5. package/src/assets/map/arrows/map-arrow-purple.png +0 -0
  6. package/src/assets/map/arrows/map-arrow-red.png +0 -0
  7. package/src/assets/map/flags/flag-blue.png +0 -0
  8. package/src/assets/map/flags/flag-green.png +0 -0
  9. package/src/assets/map/flags/flag-red.png +0 -0
  10. package/src/assets/map/flags/flag-yellow.png +0 -0
  11. package/src/assets/map/pins/map-pin-blue.png +0 -0
  12. package/src/assets/map/pins/map-pin-green.png +0 -0
  13. package/src/assets/map/pins/map-pin-purple.png +0 -0
  14. package/src/assets/map/pins/map-pin-red.png +0 -0
  15. package/src/components/Card.tsx +9 -0
  16. package/src/components/alert-error.tsx +24 -0
  17. package/src/components/app-content.tsx +22 -0
  18. package/src/components/app-header.tsx +153 -0
  19. package/src/components/app-logo-icon.tsx +13 -0
  20. package/src/components/app-logo.tsx +21 -0
  21. package/src/components/app-shell.tsx +19 -0
  22. package/src/components/app-sidebar-header.tsx +68 -0
  23. package/src/components/app-sidebar.tsx +106 -0
  24. package/src/components/appearance-tabs.tsx +46 -0
  25. package/src/components/breadcrumbs.tsx +50 -0
  26. package/src/components/cms/blurred-image.tsx +111 -0
  27. package/src/components/cms/section-bg.tsx +473 -0
  28. package/src/components/cms/section-button.tsx +127 -0
  29. package/src/components/cms/sections/banner-5050-section.tsx +135 -0
  30. package/src/components/cms/sections/blogs-listing-section.tsx +270 -0
  31. package/src/components/cms/sections/cards-grid-section.tsx +185 -0
  32. package/src/components/cms/sections/contact-form-section.tsx +157 -0
  33. package/src/components/cms/sections/cta-section.tsx +101 -0
  34. package/src/components/cms/sections/featured-blog-slider-section.tsx +256 -0
  35. package/src/components/cms/sections/featured-products-grid-section.tsx +173 -0
  36. package/src/components/cms/sections/featured-solutions-grid-section.tsx +183 -0
  37. package/src/components/cms/sections/hero-section.tsx +180 -0
  38. package/src/components/cms/sections/solutions-with-filter-section.tsx +234 -0
  39. package/src/components/cms/sections/text-section.tsx +77 -0
  40. package/src/components/cutout-image.tsx +228 -0
  41. package/src/components/devices/devices-mini-map.tsx +275 -0
  42. package/src/components/docs/docs-shell.tsx +280 -0
  43. package/src/components/fleet-hero-animated.tsx +383 -0
  44. package/src/components/input-error.tsx +17 -0
  45. package/src/components/keenicons/assets/duotone/Read Me.txt +7 -0
  46. package/src/components/keenicons/assets/duotone/demo-files/demo.css +160 -0
  47. package/src/components/keenicons/assets/duotone/demo-files/demo.js +32 -0
  48. package/src/components/keenicons/assets/duotone/demo.html +12424 -0
  49. package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.svg +1109 -0
  50. package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.ttf +0 -0
  51. package/src/components/keenicons/assets/duotone/fonts/keenicons-duotone.woff +0 -0
  52. package/src/components/keenicons/assets/duotone/selection.json +17313 -0
  53. package/src/components/keenicons/assets/duotone/style.css +4931 -0
  54. package/src/components/keenicons/assets/filled/Read Me.txt +7 -0
  55. package/src/components/keenicons/assets/filled/demo-files/demo.css +160 -0
  56. package/src/components/keenicons/assets/filled/demo-files/demo.js +32 -0
  57. package/src/components/keenicons/assets/filled/demo.html +12370 -0
  58. package/src/components/keenicons/assets/filled/fonts/keenicons-filled.svg +1082 -0
  59. package/src/components/keenicons/assets/filled/fonts/keenicons-filled.ttf +0 -0
  60. package/src/components/keenicons/assets/filled/fonts/keenicons-filled.woff +0 -0
  61. package/src/components/keenicons/assets/filled/selection.json +17096 -0
  62. package/src/components/keenicons/assets/filled/style.css +4769 -0
  63. package/src/components/keenicons/assets/outline/Read Me.txt +7 -0
  64. package/src/components/keenicons/assets/outline/demo-files/demo.css +160 -0
  65. package/src/components/keenicons/assets/outline/demo-files/demo.js +32 -0
  66. package/src/components/keenicons/assets/outline/demo.html +11356 -0
  67. package/src/components/keenicons/assets/outline/fonts/keenicons-outline.svg +575 -0
  68. package/src/components/keenicons/assets/outline/fonts/keenicons-outline.ttf +0 -0
  69. package/src/components/keenicons/assets/outline/fonts/keenicons-outline.woff +0 -0
  70. package/src/components/keenicons/assets/outline/selection.json +13054 -0
  71. package/src/components/keenicons/assets/outline/style.css +1721 -0
  72. package/src/components/keenicons/assets/solid/Read Me.txt +7 -0
  73. package/src/components/keenicons/assets/solid/demo-files/demo.css +160 -0
  74. package/src/components/keenicons/assets/solid/demo-files/demo.js +32 -0
  75. package/src/components/keenicons/assets/solid/demo.html +11356 -0
  76. package/src/components/keenicons/assets/solid/fonts/keenicons-solid.svg +575 -0
  77. package/src/components/keenicons/assets/solid/fonts/keenicons-solid.ttf +0 -0
  78. package/src/components/keenicons/assets/solid/fonts/keenicons-solid.woff +0 -0
  79. package/src/components/keenicons/assets/solid/selection.json +13048 -0
  80. package/src/components/keenicons/assets/solid/style.css +1721 -0
  81. package/src/components/keenicons/assets/styles.css +4 -0
  82. package/src/components/keenicons/index.ts +2 -0
  83. package/src/components/keenicons/keenicons.tsx +16 -0
  84. package/src/components/keenicons/types.ts +7 -0
  85. package/src/components/nav-footer.tsx +49 -0
  86. package/src/components/nav-main.tsx +53 -0
  87. package/src/components/nav-user.tsx +59 -0
  88. package/src/components/notification-bell.tsx +190 -0
  89. package/src/components/products/product-card.tsx +159 -0
  90. package/src/components/text-link.tsx +23 -0
  91. package/src/components/ui/accordion-menu.tsx +322 -0
  92. package/src/components/ui/accordion.tsx +133 -0
  93. package/src/components/ui/alert-dialog.tsx +82 -0
  94. package/src/components/ui/alert.tsx +63 -0
  95. package/src/components/ui/avatar-group.tsx +129 -0
  96. package/src/components/ui/avatar.tsx +67 -0
  97. package/src/components/ui/badge.tsx +230 -0
  98. package/src/components/ui/breadcrumb.tsx +88 -0
  99. package/src/components/ui/button.tsx +412 -0
  100. package/src/components/ui/calendar.tsx +56 -0
  101. package/src/components/ui/card.tsx +147 -0
  102. package/src/components/ui/chart.tsx +290 -0
  103. package/src/components/ui/checkbox.tsx +47 -0
  104. package/src/components/ui/code.tsx +45 -0
  105. package/src/components/ui/collapsible.tsx +31 -0
  106. package/src/components/ui/command-palette.tsx +189 -0
  107. package/src/components/ui/command.tsx +138 -0
  108. package/src/components/ui/cookie-banner.tsx +220 -0
  109. package/src/components/ui/copy-button.tsx +60 -0
  110. package/src/components/ui/data-grid-column-filter.tsx +124 -0
  111. package/src/components/ui/data-grid-column-header.tsx +284 -0
  112. package/src/components/ui/data-grid-column-visibility.tsx +38 -0
  113. package/src/components/ui/data-grid-pagination.tsx +206 -0
  114. package/src/components/ui/data-grid-table-dnd-rows.tsx +147 -0
  115. package/src/components/ui/data-grid-table-dnd.tsx +175 -0
  116. package/src/components/ui/data-grid-table.tsx +500 -0
  117. package/src/components/ui/data-grid.tsx +193 -0
  118. package/src/components/ui/data-list.tsx +76 -0
  119. package/src/components/ui/datefield.tsx +91 -0
  120. package/src/components/ui/dialog.tsx +139 -0
  121. package/src/components/ui/divider.tsx +41 -0
  122. package/src/components/ui/drawer.tsx +59 -0
  123. package/src/components/ui/dropdown-menu.tsx +224 -0
  124. package/src/components/ui/empty-state.tsx +54 -0
  125. package/src/components/ui/file-upload.tsx +152 -0
  126. package/src/components/ui/form.tsx +88 -0
  127. package/src/components/ui/icon.tsx +14 -0
  128. package/src/components/ui/input-otp.tsx +71 -0
  129. package/src/components/ui/input.tsx +155 -0
  130. package/src/components/ui/kbd.tsx +26 -0
  131. package/src/components/ui/label.tsx +31 -0
  132. package/src/components/ui/navigation-menu.tsx +168 -0
  133. package/src/components/ui/pagination.tsx +37 -0
  134. package/src/components/ui/placeholder-pattern.tsx +21 -0
  135. package/src/components/ui/popover.tsx +50 -0
  136. package/src/components/ui/progress.tsx +65 -0
  137. package/src/components/ui/radio-group.tsx +73 -0
  138. package/src/components/ui/resizable.tsx +39 -0
  139. package/src/components/ui/scroll-area.tsx +50 -0
  140. package/src/components/ui/select.tsx +234 -0
  141. package/src/components/ui/separator.tsx +24 -0
  142. package/src/components/ui/sheet.tsx +147 -0
  143. package/src/components/ui/sidebar.tsx +721 -0
  144. package/src/components/ui/skeleton.tsx +15 -0
  145. package/src/components/ui/slider.tsx +35 -0
  146. package/src/components/ui/sonner.tsx +28 -0
  147. package/src/components/ui/sortable.tsx +724 -0
  148. package/src/components/ui/spinner.tsx +17 -0
  149. package/src/components/ui/stat-card.tsx +82 -0
  150. package/src/components/ui/stepper.tsx +410 -0
  151. package/src/components/ui/switch.tsx +68 -0
  152. package/src/components/ui/table.tsx +42 -0
  153. package/src/components/ui/tabs.tsx +196 -0
  154. package/src/components/ui/timeline.tsx +90 -0
  155. package/src/components/ui/toggle-group.tsx +73 -0
  156. package/src/components/ui/toggle.tsx +45 -0
  157. package/src/components/ui/tooltip.tsx +55 -0
  158. package/src/components/user-info.tsx +33 -0
  159. package/src/components/user-menu-content.tsx +53 -0
  160. package/src/components/web/SiteFooter.tsx +154 -0
  161. package/src/components/web/SiteHeader.tsx +159 -0
  162. package/src/components/workflows/workflow-canvas.tsx +321 -0
  163. package/src/controls/Blockquote.tsx +25 -0
  164. package/src/controls/Button.tsx +101 -0
  165. package/src/controls/Checkbox.tsx +29 -0
  166. package/src/controls/DateField.tsx +37 -0
  167. package/src/controls/FormField.tsx +20 -0
  168. package/src/controls/Heading.tsx +28 -0
  169. package/src/controls/Input.tsx +21 -0
  170. package/src/controls/Label.tsx +18 -0
  171. package/src/controls/Paragraph.tsx +39 -0
  172. package/src/controls/PasswordInput.tsx +40 -0
  173. package/src/controls/RadioGroup.tsx +70 -0
  174. package/src/controls/Select.tsx +24 -0
  175. package/src/controls/Slider.tsx +33 -0
  176. package/src/controls/Switch.tsx +31 -0
  177. package/src/controls/Textarea.tsx +22 -0
  178. package/src/elements/ConfirmPasswordForm.tsx +43 -0
  179. package/src/elements/DeviceStatusBadge.tsx +38 -0
  180. package/src/elements/DriverCard.tsx +67 -0
  181. package/src/elements/ForgotPasswordForm.tsx +64 -0
  182. package/src/elements/IncidentCard.tsx +67 -0
  183. package/src/elements/LoginForm.tsx +100 -0
  184. package/src/elements/OtpForm.tsx +71 -0
  185. package/src/elements/RegisterForm.tsx +150 -0
  186. package/src/elements/ResetPasswordForm.tsx +72 -0
  187. package/src/elements/SmsChallengeForm.tsx +104 -0
  188. package/src/elements/VehicleCard.tsx +73 -0
  189. package/src/elements/VerifyEmailForm.tsx +39 -0
  190. package/src/hooks/use-appearance.tsx +117 -0
  191. package/src/hooks/use-applied-theme.ts +98 -0
  192. package/src/hooks/use-clipboard.ts +34 -0
  193. package/src/hooks/use-current-url.ts +83 -0
  194. package/src/hooks/use-dark-mode.ts +48 -0
  195. package/src/hooks/use-flash-toast.ts +29 -0
  196. package/src/hooks/use-initials.tsx +24 -0
  197. package/src/hooks/use-mobile-navigation.ts +12 -0
  198. package/src/hooks/use-mobile.tsx +38 -0
  199. package/src/index.ts +408 -0
  200. package/src/layouts/AppLayout.tsx +60 -0
  201. package/src/layouts/AuthLayout.tsx +32 -0
  202. package/src/layouts/SettingsLayout.tsx +21 -0
  203. package/src/layouts/app/AIChatLayout.tsx +73 -0
  204. package/src/layouts/app/AsideSidebarLayout.tsx +3 -0
  205. package/src/layouts/app/CalendarSidebarLayout.tsx +69 -0
  206. package/src/layouts/app/CommunitiesNavbarLayout.tsx +3 -0
  207. package/src/layouts/app/DualNavbarSidebarLayout.tsx +3 -0
  208. package/src/layouts/app/FocusSidebarLayout.tsx +75 -0
  209. package/src/layouts/app/MailLayout.tsx +69 -0
  210. package/src/layouts/app/MegaMenuHeaderLayout.tsx +3 -0
  211. package/src/layouts/app/MegaMenuLayout.tsx +81 -0
  212. package/src/layouts/app/MegaMenuNavbarLayout.tsx +88 -0
  213. package/src/layouts/app/MegaMenuSearchNavbarLayout.tsx +3 -0
  214. package/src/layouts/app/NavbarCollapsibleLayout.tsx +88 -0
  215. package/src/layouts/app/NavbarCollapsibleLinksLayout.tsx +3 -0
  216. package/src/layouts/app/NavbarMinimalLayout.tsx +3 -0
  217. package/src/layouts/app/NavbarMinimalSidebarLayout.tsx +3 -0
  218. package/src/layouts/app/NavbarSidebarDashboardLayout.tsx +3 -0
  219. package/src/layouts/app/NavbarSidebarLayout.tsx +92 -0
  220. package/src/layouts/app/NavbarSimpleSidebarLayout.tsx +3 -0
  221. package/src/layouts/app/NavbarTitledSidebarLayout.tsx +3 -0
  222. package/src/layouts/app/PanelSidebarLayout.tsx +3 -0
  223. package/src/layouts/app/SearchNavbarSidebarLayout.tsx +3 -0
  224. package/src/layouts/app/SidebarBreadcrumbLayout.tsx +3 -0
  225. package/src/layouts/app/SidebarCleanLayout.tsx +3 -0
  226. package/src/layouts/app/SidebarCommunitiesLayout.tsx +3 -0
  227. package/src/layouts/app/SidebarContentLayout.tsx +3 -0
  228. package/src/layouts/app/SidebarDualMenuLayout.tsx +104 -0
  229. package/src/layouts/app/SidebarFixedLayout.tsx +166 -0
  230. package/src/layouts/app/SidebarFooterNavbarLayout.tsx +3 -0
  231. package/src/layouts/app/SidebarHeaderMenuLayout.tsx +3 -0
  232. package/src/layouts/app/SidebarMegaMenuLayout.tsx +4 -0
  233. package/src/layouts/app/SidebarMinimalLayout.tsx +70 -0
  234. package/src/layouts/app/SidebarMobileSearchLayout.tsx +3 -0
  235. package/src/layouts/app/SidebarMultiPanelLayout.tsx +3 -0
  236. package/src/layouts/app/SidebarPrimarySecondaryLayout.tsx +3 -0
  237. package/src/layouts/app/SidebarSearchHeaderLayout.tsx +103 -0
  238. package/src/layouts/app/SidebarSearchToolbarLayout.tsx +3 -0
  239. package/src/layouts/app/SidebarTabsDualLayout.tsx +3 -0
  240. package/src/layouts/app/SidebarTabsLayout.tsx +98 -0
  241. package/src/layouts/app/SidebarTreeLayout.tsx +3 -0
  242. package/src/layouts/app/SplitNavbarLayout.tsx +3 -0
  243. package/src/layouts/app/SplitSidebarDashboardLayout.tsx +3 -0
  244. package/src/layouts/app/SplitSidebarLayout.tsx +99 -0
  245. package/src/layouts/app/TopNavLayout.tsx +105 -0
  246. package/src/layouts/app/TopNavLinksLayout.tsx +3 -0
  247. package/src/layouts/app/WorkspaceBreadcrumbLayout.tsx +3 -0
  248. package/src/layouts/app/WorkspaceCommunitiesLayout.tsx +3 -0
  249. package/src/layouts/app/WorkspaceNavbarLayout.tsx +3 -0
  250. package/src/layouts/app/WorkspaceSidebarLayout.tsx +98 -0
  251. package/src/layouts/app/WorkspaceSidebarTitleLayout.tsx +3 -0
  252. package/src/layouts/app/app-header-layout.tsx +45 -0
  253. package/src/layouts/app/app-sidebar-layout.tsx +56 -0
  254. package/src/layouts/app/layout-context.tsx +44 -0
  255. package/src/layouts/app/layout-types.ts +47 -0
  256. package/src/layouts/app/partials/Footer.tsx +35 -0
  257. package/src/layouts/app/partials/HeaderTopbar.tsx +96 -0
  258. package/src/layouts/app/partials/Navbar.tsx +85 -0
  259. package/src/layouts/app/partials/Toolbar.tsx +47 -0
  260. package/src/layouts/app-layout.tsx +29 -0
  261. package/src/layouts/auth/AuthBrandedLayout.tsx +58 -0
  262. package/src/layouts/auth/AuthCardLayout.tsx +31 -0
  263. package/src/layouts/auth/AuthCenteredLayout.tsx +41 -0
  264. package/src/layouts/auth/AuthClassicLayout.tsx +41 -0
  265. package/src/layouts/auth/AuthSimpleLayout.tsx +33 -0
  266. package/src/layouts/auth/AuthSplitLayout.tsx +89 -0
  267. package/src/layouts/web-app-layout.tsx +162 -0
  268. package/src/layouts/web-layout.tsx +23 -0
  269. package/src/lib/datetime.ts +188 -0
  270. package/src/lib/google-maps-loader.ts +99 -0
  271. package/src/lib/location.ts +127 -0
  272. package/src/lib/lucide-icon-map.ts +132 -0
  273. package/src/lib/map-markers.ts +124 -0
  274. package/src/lib/map-styles.ts +351 -0
  275. package/src/lib/utils.ts +11 -0
  276. package/src/platform/adapters/default.tsx +156 -0
  277. package/src/platform/adapters/inertia.tsx +88 -0
  278. package/src/platform/adapters/nextjs.ts +86 -0
  279. package/src/platform/context.tsx +106 -0
  280. package/src/platform/index.ts +27 -0
  281. package/src/platform/types.ts +105 -0
  282. package/src/styles/layouts/sidebar-fixed.css +161 -0
  283. package/src/styles/themes.css +583 -0
  284. package/src/types/assets.d.ts +5 -0
  285. package/src/types/auth.ts +25 -0
  286. package/src/types/global.d.ts +13 -0
  287. package/src/types/index.ts +9 -0
  288. package/src/types/navigation.ts +15 -0
  289. package/src/types/ui.ts +32 -0
@@ -0,0 +1,724 @@
1
+ 'use client';
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+
4
+ import * as React from 'react';
5
+ import { cn } from '../../lib/utils';
6
+ import {
7
+ defaultDropAnimation,
8
+ defaultDropAnimationSideEffects,
9
+ DndContext,
10
+ DragEndEvent,
11
+ DragOverEvent,
12
+ DragOverlay,
13
+ DragStartEvent,
14
+ DropAnimation,
15
+ KeyboardSensor,
16
+ PointerSensor,
17
+ UniqueIdentifier,
18
+ useSensor,
19
+ useSensors,
20
+ type DraggableAttributes,
21
+ type DraggableSyntheticListeners,
22
+ } from '@dnd-kit/core';
23
+ import {
24
+ arrayMove,
25
+ rectSortingStrategy,
26
+ SortableContext,
27
+ sortableKeyboardCoordinates,
28
+ useSortable,
29
+ verticalListSortingStrategy,
30
+ } from '@dnd-kit/sortable';
31
+ import { CSS } from '@dnd-kit/utilities';
32
+ import { Slot } from '@radix-ui/react-slot';
33
+
34
+ interface KanbanContextProps<T> {
35
+ columns: Record<string, T[]>;
36
+ setColumns: (columns: Record<string, T[]>) => void;
37
+ getItemId: (item: T) => string;
38
+ columnIds: string[];
39
+ activeId: UniqueIdentifier | null;
40
+ setActiveId: (id: UniqueIdentifier | null) => void;
41
+ findContainer: (id: UniqueIdentifier) => string | undefined;
42
+ isColumn: (id: UniqueIdentifier) => boolean;
43
+ }
44
+
45
+ const KanbanContext = React.createContext<KanbanContextProps<any>>({
46
+ columns: {},
47
+ setColumns: () => {},
48
+ getItemId: () => '',
49
+ columnIds: [],
50
+ activeId: null,
51
+ setActiveId: () => {},
52
+ findContainer: () => undefined,
53
+ isColumn: () => false,
54
+ });
55
+
56
+ const ColumnContext = React.createContext<{
57
+ attributes: DraggableAttributes;
58
+ listeners: DraggableSyntheticListeners | undefined;
59
+ isDragging?: boolean;
60
+ disabled?: boolean;
61
+ }>({
62
+ attributes: {} as DraggableAttributes,
63
+ listeners: undefined,
64
+ isDragging: false,
65
+ disabled: false,
66
+ });
67
+
68
+ const ItemContext = React.createContext<{
69
+ listeners: DraggableSyntheticListeners | undefined;
70
+ isDragging?: boolean;
71
+ disabled?: boolean;
72
+ }>({
73
+ listeners: undefined,
74
+ isDragging: false,
75
+ disabled: false,
76
+ });
77
+
78
+ const dropAnimationConfig: DropAnimation = {
79
+ ...defaultDropAnimation,
80
+ sideEffects: defaultDropAnimationSideEffects({
81
+ styles: {
82
+ active: {
83
+ opacity: '0.4',
84
+ },
85
+ },
86
+ }),
87
+ };
88
+
89
+ export interface KanbanMoveEvent {
90
+ event: DragEndEvent;
91
+ activeContainer: string;
92
+ activeIndex: number;
93
+ overContainer: string;
94
+ overIndex: number;
95
+ }
96
+
97
+ export interface KanbanRootProps<T> {
98
+ value: Record<string, T[]>;
99
+ onValueChange: (value: Record<string, T[]>) => void;
100
+ getItemValue: (item: T) => string;
101
+ children: React.ReactNode;
102
+ className?: string;
103
+ onMove?: (event: KanbanMoveEvent) => void;
104
+ }
105
+
106
+ function Kanban<T>({ value, onValueChange, getItemValue, children, className, onMove }: KanbanRootProps<T>) {
107
+ const columns = value;
108
+ const setColumns = onValueChange;
109
+ const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null);
110
+
111
+ const sensors = useSensors(
112
+ useSensor(PointerSensor, {
113
+ activationConstraint: {
114
+ distance: 10,
115
+ },
116
+ }),
117
+ useSensor(KeyboardSensor, {
118
+ coordinateGetter: sortableKeyboardCoordinates,
119
+ }),
120
+ );
121
+
122
+ const columnIds = React.useMemo(() => Object.keys(columns), [columns]);
123
+
124
+ const isColumn = React.useCallback((id: UniqueIdentifier) => columnIds.includes(id as string), [columnIds]);
125
+
126
+ const findContainer = React.useCallback(
127
+ (id: UniqueIdentifier) => {
128
+ if (isColumn(id)) return id as string;
129
+ return columnIds.find((key) => columns[key].some((item) => getItemValue(item) === id));
130
+ },
131
+ [columns, columnIds, getItemValue, isColumn],
132
+ );
133
+
134
+ const handleDragStart = React.useCallback((event: DragStartEvent) => {
135
+ setActiveId(event.active.id);
136
+ }, []);
137
+
138
+ const handleDragOver = React.useCallback(
139
+ (event: DragOverEvent) => {
140
+ if (onMove) {
141
+ return;
142
+ }
143
+
144
+ const { active, over } = event;
145
+ if (!over) return;
146
+
147
+ if (isColumn(active.id)) return;
148
+
149
+ const activeContainer = findContainer(active.id);
150
+ const overContainer = findContainer(over.id);
151
+
152
+ // Only handle moving items between different columns
153
+ if (!activeContainer || !overContainer || activeContainer === overContainer) {
154
+ return;
155
+ }
156
+
157
+ const activeItems = columns[activeContainer];
158
+ const overItems = columns[overContainer];
159
+
160
+ const activeIndex = activeItems.findIndex((item: T) => getItemValue(item) === active.id);
161
+ let overIndex = overItems.findIndex((item: T) => getItemValue(item) === over.id);
162
+
163
+ // If dropping on the column itself, not an item
164
+ if (isColumn(over.id)) {
165
+ overIndex = overItems.length;
166
+ }
167
+
168
+ const newOverItems = [...overItems];
169
+ const [movedItem] = activeItems.splice(activeIndex, 1);
170
+ newOverItems.splice(overIndex, 0, movedItem);
171
+
172
+ setColumns({
173
+ ...columns,
174
+ [activeContainer]: [...activeItems],
175
+ [overContainer]: newOverItems,
176
+ });
177
+ },
178
+ [findContainer, getItemValue, isColumn, setColumns, columns, onMove],
179
+ );
180
+
181
+ const handleDragEnd = React.useCallback(
182
+ (event: DragEndEvent) => {
183
+ const { active, over } = event;
184
+ setActiveId(null);
185
+
186
+ if (!over) return;
187
+
188
+ // Handle item move callback
189
+ if (onMove && !isColumn(active.id)) {
190
+ const activeContainer = findContainer(active.id);
191
+ const overContainer = findContainer(over.id);
192
+
193
+ if (activeContainer && overContainer) {
194
+ const activeIndex = columns[activeContainer].findIndex((item: T) => getItemValue(item) === active.id);
195
+ const overIndex = isColumn(over.id)
196
+ ? columns[overContainer].length
197
+ : columns[overContainer].findIndex((item: T) => getItemValue(item) === over.id);
198
+
199
+ onMove({
200
+ event,
201
+ activeContainer,
202
+ activeIndex,
203
+ overContainer,
204
+ overIndex,
205
+ });
206
+ }
207
+ return;
208
+ }
209
+
210
+ // Handle column reordering
211
+ if (isColumn(active.id) && isColumn(over.id)) {
212
+ const activeIndex = columnIds.indexOf(active.id as string);
213
+ const overIndex = columnIds.indexOf(over.id as string);
214
+ if (activeIndex !== overIndex) {
215
+ const newOrder = arrayMove(Object.keys(columns), activeIndex, overIndex);
216
+ const newColumns: Record<string, T[]> = {};
217
+ newOrder.forEach((key) => {
218
+ newColumns[key] = columns[key];
219
+ });
220
+ setColumns(newColumns);
221
+ }
222
+ return;
223
+ }
224
+
225
+ const activeContainer = findContainer(active.id);
226
+ const overContainer = findContainer(over.id);
227
+
228
+ // Handle item reordering within the same column
229
+ if (activeContainer && overContainer && activeContainer === overContainer) {
230
+ const container = activeContainer;
231
+ const activeIndex = columns[container].findIndex((item: T) => getItemValue(item) === active.id);
232
+ const overIndex = columns[container].findIndex((item: T) => getItemValue(item) === over.id);
233
+
234
+ if (activeIndex !== overIndex) {
235
+ setColumns({
236
+ ...columns,
237
+ [container]: arrayMove(columns[container], activeIndex, overIndex),
238
+ });
239
+ }
240
+ }
241
+ },
242
+ [columnIds, columns, findContainer, getItemValue, isColumn, setColumns, onMove],
243
+ );
244
+
245
+ const contextValue = React.useMemo(
246
+ () => ({
247
+ columns,
248
+ setColumns,
249
+ getItemId: getItemValue,
250
+ columnIds,
251
+ activeId,
252
+ setActiveId,
253
+ findContainer,
254
+ isColumn,
255
+ }),
256
+ [columns, setColumns, getItemValue, columnIds, activeId, findContainer, isColumn],
257
+ );
258
+
259
+ return (
260
+ <KanbanContext.Provider value={contextValue}>
261
+ <DndContext sensors={sensors} onDragStart={handleDragStart} onDragOver={handleDragOver} onDragEnd={handleDragEnd}>
262
+ <div data-slot="kanban" data-dragging={activeId !== null} className={cn(className)}>
263
+ {children}
264
+ </div>
265
+ </DndContext>
266
+ </KanbanContext.Provider>
267
+ );
268
+ }
269
+
270
+ export interface KanbanBoardProps {
271
+ className?: string;
272
+ children: React.ReactNode;
273
+ }
274
+
275
+ function KanbanBoard({ children, className }: KanbanBoardProps) {
276
+ const { columnIds } = React.useContext(KanbanContext);
277
+
278
+ return (
279
+ <SortableContext items={columnIds} strategy={rectSortingStrategy}>
280
+ <div data-slot="kanban-board" className={cn('grid auto-rows-fr sm:grid-cols-3 gap-4', className)}>
281
+ {children}
282
+ </div>
283
+ </SortableContext>
284
+ );
285
+ }
286
+
287
+ export interface KanbanColumnProps {
288
+ value: string;
289
+ className?: string;
290
+ children: React.ReactNode;
291
+ disabled?: boolean;
292
+ }
293
+
294
+ function KanbanColumn({ value, className, children, disabled }: KanbanColumnProps) {
295
+ const {
296
+ setNodeRef,
297
+ transform,
298
+ transition,
299
+ attributes,
300
+ listeners,
301
+ isDragging: isSortableDragging,
302
+ } = useSortable({
303
+ id: value,
304
+ disabled,
305
+ });
306
+
307
+ const { activeId, isColumn } = React.useContext(KanbanContext);
308
+ const isColumnDragging = activeId ? isColumn(activeId) : false;
309
+
310
+ const style = {
311
+ transition,
312
+ transform: CSS.Translate.toString(transform),
313
+ } as React.CSSProperties;
314
+
315
+ return (
316
+ <ColumnContext.Provider value={{ attributes, listeners, isDragging: isColumnDragging, disabled }}>
317
+ <div
318
+ data-slot="kanban-column"
319
+ data-value={value}
320
+ data-dragging={isSortableDragging}
321
+ data-disabled={disabled}
322
+ ref={setNodeRef}
323
+ style={style}
324
+ className={cn(
325
+ 'group/kanban-column flex flex-col',
326
+ isSortableDragging && 'opacity-50',
327
+ disabled && 'opacity-50',
328
+ className,
329
+ )}
330
+ >
331
+ {children}
332
+ </div>
333
+ </ColumnContext.Provider>
334
+ );
335
+ }
336
+
337
+ export interface KanbanColumnHandleProps {
338
+ asChild?: boolean;
339
+ className?: string;
340
+ children?: React.ReactNode;
341
+ cursor?: boolean;
342
+ }
343
+
344
+ function KanbanColumnHandle({ asChild, className, children, cursor = true }: KanbanColumnHandleProps) {
345
+ const { attributes, listeners, isDragging, disabled } = React.useContext(ColumnContext);
346
+
347
+ const Comp = asChild ? Slot : 'div';
348
+
349
+ return (
350
+ <Comp
351
+ data-slot="kanban-column-handle"
352
+ data-dragging={isDragging}
353
+ data-disabled={disabled}
354
+ {...attributes}
355
+ {...listeners}
356
+ className={cn(
357
+ 'opacity-0 transition-opacity group-hover/kanban-column:opacity-100',
358
+ cursor && (isDragging ? '!cursor-grabbing' : '!cursor-grab'),
359
+ className,
360
+ )}
361
+ >
362
+ {children}
363
+ </Comp>
364
+ );
365
+ }
366
+
367
+ export interface KanbanItemProps {
368
+ value: string;
369
+ asChild?: boolean;
370
+ className?: string;
371
+ children: React.ReactNode;
372
+ disabled?: boolean;
373
+ }
374
+
375
+ function KanbanItem({ value, asChild = false, className, children, disabled }: KanbanItemProps) {
376
+ const {
377
+ setNodeRef,
378
+ transform,
379
+ transition,
380
+ attributes,
381
+ listeners,
382
+ isDragging: isSortableDragging,
383
+ } = useSortable({
384
+ id: value,
385
+ disabled,
386
+ });
387
+
388
+ const { activeId, isColumn } = React.useContext(KanbanContext);
389
+ const isItemDragging = activeId ? !isColumn(activeId) : false;
390
+
391
+ const style = {
392
+ transition,
393
+ transform: CSS.Translate.toString(transform),
394
+ } as React.CSSProperties;
395
+
396
+ const Comp = asChild ? Slot : 'div';
397
+
398
+ return (
399
+ <ItemContext.Provider value={{ listeners, isDragging: isItemDragging, disabled }}>
400
+ <Comp
401
+ data-slot="kanban-item"
402
+ data-value={value}
403
+ data-dragging={isSortableDragging}
404
+ data-disabled={disabled}
405
+ ref={setNodeRef}
406
+ style={style}
407
+ {...attributes}
408
+ className={cn(isSortableDragging && 'opacity-50', disabled && 'opacity-50', className)}
409
+ >
410
+ {children}
411
+ </Comp>
412
+ </ItemContext.Provider>
413
+ );
414
+ }
415
+
416
+ export interface KanbanItemHandleProps {
417
+ asChild?: boolean;
418
+ className?: string;
419
+ children?: React.ReactNode;
420
+ cursor?: boolean;
421
+ }
422
+
423
+ function KanbanItemHandle({ asChild, className, children, cursor = true }: KanbanItemHandleProps) {
424
+ const { listeners, isDragging, disabled } = React.useContext(ItemContext);
425
+
426
+ const Comp = asChild ? Slot : 'div';
427
+
428
+ return (
429
+ <Comp
430
+ data-slot="kanban-item-handle"
431
+ data-dragging={isDragging}
432
+ data-disabled={disabled}
433
+ {...listeners}
434
+ className={cn(cursor && (isDragging ? '!cursor-grabbing' : '!cursor-grab'), className)}
435
+ >
436
+ {children}
437
+ </Comp>
438
+ );
439
+ }
440
+
441
+ export interface KanbanColumnContentProps {
442
+ value: string;
443
+ className?: string;
444
+ children: React.ReactNode;
445
+ }
446
+
447
+ function KanbanColumnContent({ value, className, children }: KanbanColumnContentProps) {
448
+ const { columns, getItemId } = React.useContext(KanbanContext);
449
+
450
+ const itemIds = React.useMemo(() => columns[value].map(getItemId), [columns, getItemId, value]);
451
+
452
+ return (
453
+ <SortableContext items={itemIds} strategy={verticalListSortingStrategy}>
454
+ <div data-slot="kanban-column-content" className={cn('flex flex-col gap-2', className)}>
455
+ {children}
456
+ </div>
457
+ </SortableContext>
458
+ );
459
+ }
460
+
461
+ export interface KanbanOverlayProps {
462
+ className?: string;
463
+ children?: React.ReactNode | ((params: { value: UniqueIdentifier; variant: 'column' | 'item' }) => React.ReactNode);
464
+ }
465
+
466
+ function KanbanOverlay({ children, className }: KanbanOverlayProps) {
467
+ const { activeId, isColumn } = React.useContext(KanbanContext);
468
+ const [dimensions, setDimensions] = React.useState<{ width: number; height: number } | null>(null);
469
+
470
+ React.useEffect(() => {
471
+ if (activeId) {
472
+ const element = document.querySelector(
473
+ `[data-slot="kanban-${isColumn(activeId) ? 'column' : 'item'}"][data-value="${activeId}"]`,
474
+ );
475
+ if (element) {
476
+ const rect = element.getBoundingClientRect();
477
+ setDimensions({ width: rect.width, height: rect.height });
478
+ }
479
+ } else {
480
+ setDimensions(null);
481
+ }
482
+ }, [activeId, isColumn]);
483
+
484
+ const style = {
485
+ width: dimensions?.width,
486
+ height: dimensions?.height,
487
+ } as React.CSSProperties;
488
+
489
+ const content = React.useMemo(() => {
490
+ if (!activeId) return null;
491
+ if (typeof children === 'function') {
492
+ return children({
493
+ value: activeId,
494
+ variant: isColumn(activeId) ? 'column' : 'item',
495
+ });
496
+ }
497
+ return children;
498
+ }, [activeId, children, isColumn]);
499
+
500
+ return (
501
+ <DragOverlay dropAnimation={dropAnimationConfig}>
502
+ <div
503
+ data-slot="kanban-overlay"
504
+ data-dragging={true}
505
+ style={style}
506
+ className={cn('pointer-events-none', className, activeId ? '!cursor-grabbing' : '')}
507
+ >
508
+ {content}
509
+ </div>
510
+ </DragOverlay>
511
+ );
512
+ }
513
+
514
+ // Sortable Item Context
515
+ const SortableItemContext = React.createContext<{
516
+ listeners: DraggableSyntheticListeners | undefined;
517
+ isDragging?: boolean;
518
+ disabled?: boolean;
519
+ }>({
520
+ listeners: undefined,
521
+ isDragging: false,
522
+ disabled: false,
523
+ });
524
+
525
+ // Multipurpose Sortable Component
526
+ export interface SortableRootProps<T> {
527
+ value: T[];
528
+ onValueChange: (value: T[]) => void;
529
+ getItemValue: (item: T) => string;
530
+ children: React.ReactNode;
531
+ className?: string;
532
+ onMove?: (event: { event: DragEndEvent; activeIndex: number; overIndex: number }) => void;
533
+ strategy?: 'horizontal' | 'vertical' | 'grid';
534
+ onDragStart?: (event: DragStartEvent) => void;
535
+ onDragEnd?: (event: DragEndEvent) => void;
536
+ }
537
+
538
+ function Sortable<T>({
539
+ value,
540
+ onValueChange,
541
+ getItemValue,
542
+ children,
543
+ className,
544
+ onMove,
545
+ strategy = 'vertical',
546
+ onDragStart,
547
+ onDragEnd
548
+ }: SortableRootProps<T>) {
549
+ const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null);
550
+
551
+ const sensors = useSensors(
552
+ useSensor(PointerSensor, {
553
+ activationConstraint: {
554
+ distance: 10,
555
+ },
556
+ }),
557
+ useSensor(KeyboardSensor, {
558
+ coordinateGetter: sortableKeyboardCoordinates,
559
+ }),
560
+ );
561
+
562
+ const handleDragStart = React.useCallback((event: DragStartEvent) => {
563
+ setActiveId(event.active.id);
564
+ onDragStart?.(event);
565
+ }, [onDragStart]);
566
+
567
+ const handleDragEnd = React.useCallback(
568
+ (event: DragEndEvent) => {
569
+ const { active, over } = event;
570
+ setActiveId(null);
571
+ onDragEnd?.(event);
572
+
573
+ if (!over) return;
574
+
575
+ // Handle item reordering
576
+ const activeIndex = value.findIndex((item: T) => getItemValue(item) === active.id);
577
+ const overIndex = value.findIndex((item: T) => getItemValue(item) === over.id);
578
+
579
+ if (activeIndex !== overIndex) {
580
+ if (onMove) {
581
+ onMove({ event, activeIndex, overIndex });
582
+ } else {
583
+ const newValue = arrayMove(value, activeIndex, overIndex);
584
+ onValueChange(newValue);
585
+ }
586
+ }
587
+ },
588
+ [value, getItemValue, onValueChange, onMove, onDragEnd],
589
+ );
590
+
591
+ const getStrategy = () => {
592
+ switch (strategy) {
593
+ case 'horizontal':
594
+ return rectSortingStrategy;
595
+ case 'grid':
596
+ return rectSortingStrategy;
597
+ case 'vertical':
598
+ default:
599
+ return verticalListSortingStrategy;
600
+ }
601
+ };
602
+
603
+ const itemIds = React.useMemo(() => value.map(getItemValue), [value, getItemValue]);
604
+
605
+ return (
606
+ <DndContext sensors={sensors} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
607
+ <SortableContext items={itemIds} strategy={getStrategy()}>
608
+ <div
609
+ data-slot="sortable"
610
+ data-dragging={activeId !== null}
611
+ className={cn(className)}
612
+ >
613
+ {children}
614
+ </div>
615
+ </SortableContext>
616
+
617
+ <DragOverlay>
618
+ {activeId ? (
619
+ <div className="z-50">
620
+ {React.Children.map(children, (child) => {
621
+ if (React.isValidElement(child) && (child.props as any).value === activeId) {
622
+ return React.cloneElement(child as React.ReactElement<any>, {
623
+ ...(child.props as any),
624
+ className: cn((child.props as any).className, 'z-50 shadow-lg'),
625
+ });
626
+ }
627
+ return null;
628
+ })}
629
+ </div>
630
+ ) : null}
631
+ </DragOverlay>
632
+ </DndContext>
633
+ );
634
+ }
635
+
636
+ export interface SortableItemProps {
637
+ value: string;
638
+ asChild?: boolean;
639
+ className?: string;
640
+ children: React.ReactNode;
641
+ disabled?: boolean;
642
+ }
643
+
644
+ function SortableItem({ value, asChild = false, className, children, disabled }: SortableItemProps) {
645
+ const {
646
+ setNodeRef,
647
+ transform,
648
+ transition,
649
+ attributes,
650
+ listeners,
651
+ isDragging: isSortableDragging,
652
+ } = useSortable({
653
+ id: value,
654
+ disabled,
655
+ });
656
+
657
+ const style = {
658
+ transition,
659
+ transform: CSS.Translate.toString(transform),
660
+ } as React.CSSProperties;
661
+
662
+ const Comp = asChild ? Slot : 'div';
663
+
664
+ return (
665
+ <SortableItemContext.Provider value={{ listeners, isDragging: isSortableDragging, disabled }}>
666
+ <Comp
667
+ data-slot="sortable-item"
668
+ data-value={value}
669
+ data-dragging={isSortableDragging}
670
+ data-disabled={disabled}
671
+ ref={setNodeRef}
672
+ style={style}
673
+ {...attributes}
674
+ className={cn(
675
+ isSortableDragging && 'opacity-50 z-50',
676
+ disabled && 'opacity-50',
677
+ className
678
+ )}
679
+ >
680
+ {children}
681
+ </Comp>
682
+ </SortableItemContext.Provider>
683
+ );
684
+ }
685
+
686
+ export interface SortableItemHandleProps {
687
+ asChild?: boolean;
688
+ className?: string;
689
+ children?: React.ReactNode;
690
+ cursor?: boolean;
691
+ }
692
+
693
+ function SortableItemHandle({ asChild, className, children, cursor = true }: SortableItemHandleProps) {
694
+ const { listeners, isDragging, disabled } = React.useContext(SortableItemContext);
695
+
696
+ const Comp = asChild ? Slot : 'div';
697
+
698
+ return (
699
+ <Comp
700
+ data-slot="sortable-item-handle"
701
+ data-dragging={isDragging}
702
+ data-disabled={disabled}
703
+ {...listeners}
704
+ className={cn(cursor && (isDragging ? '!cursor-grabbing' : '!cursor-grab'), className)}
705
+ >
706
+ {children}
707
+ </Comp>
708
+ );
709
+ }
710
+
711
+ export {
712
+ Kanban,
713
+ KanbanBoard,
714
+ KanbanColumn,
715
+ KanbanColumnHandle,
716
+ KanbanItem,
717
+ KanbanItemHandle,
718
+ KanbanColumnContent,
719
+ KanbanOverlay,
720
+ // New multipurpose sortable components
721
+ Sortable,
722
+ SortableItem,
723
+ SortableItemHandle,
724
+ };