@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,284 @@
1
+ 'use client';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+ import { cn } from '../../lib/utils';
4
+ import { Button } from '../../controls/Button';
5
+ import { useDataGrid } from './data-grid';
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuCheckboxItem,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuPortal,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuSub,
15
+ DropdownMenuSubContent,
16
+ DropdownMenuSubTrigger,
17
+ DropdownMenuTrigger,
18
+ } from './dropdown-menu';
19
+ import { Column } from '@tanstack/react-table';
20
+ import {
21
+ ArrowDown,
22
+ ArrowLeft,
23
+ ArrowLeftToLine,
24
+ ArrowRight,
25
+ ArrowRightToLine,
26
+ ArrowUp,
27
+ Check,
28
+ ChevronsUpDown,
29
+ PinOff,
30
+ Settings2,
31
+ } from 'lucide-react';
32
+
33
+ interface DataGridColumnHeaderProps<TData, TValue> extends HTMLAttributes<HTMLDivElement> {
34
+ column: Column<TData, TValue>;
35
+ title?: string;
36
+ icon?: ReactNode;
37
+ pinnable?: boolean;
38
+ filter?: ReactNode;
39
+ visibility?: boolean;
40
+ }
41
+
42
+ function DataGridColumnHeader<TData, TValue>({
43
+ column,
44
+ title = '',
45
+ icon,
46
+ className,
47
+ filter,
48
+ visibility = false,
49
+ }: DataGridColumnHeaderProps<TData, TValue>) {
50
+ const { isLoading, table, props, recordCount } = useDataGrid();
51
+
52
+ const moveColumn = (direction: 'left' | 'right') => {
53
+ const currentOrder = [...table.getState().columnOrder]; // Get current column order
54
+ const currentIndex = currentOrder.indexOf(column.id); // Get current index of the column
55
+
56
+ if (direction === 'left' && currentIndex > 0) {
57
+ // Move column left
58
+ const newOrder = [...currentOrder];
59
+ const [movedColumn] = newOrder.splice(currentIndex, 1);
60
+ newOrder.splice(currentIndex - 1, 0, movedColumn);
61
+ table.setColumnOrder(newOrder); // Update column order
62
+ }
63
+
64
+ if (direction === 'right' && currentIndex < currentOrder.length - 1) {
65
+ // Move column right
66
+ const newOrder = [...currentOrder];
67
+ const [movedColumn] = newOrder.splice(currentIndex, 1);
68
+ newOrder.splice(currentIndex + 1, 0, movedColumn);
69
+ table.setColumnOrder(newOrder); // Update column order
70
+ }
71
+ };
72
+
73
+ const canMove = (direction: 'left' | 'right'): boolean => {
74
+ const currentOrder = table.getState().columnOrder;
75
+ const currentIndex = currentOrder.indexOf(column.id);
76
+ if (direction === 'left') {
77
+ return currentIndex > 0;
78
+ } else {
79
+ return currentIndex < currentOrder.length - 1;
80
+ }
81
+ };
82
+
83
+ const headerLabel = () => {
84
+ return (
85
+ <div
86
+ className={cn(
87
+ 'text-accent-foreground font-normal inline-flex h-full items-center gap-1.5 text-[0.8125rem] leading-[calc(1.125/0.8125)] [&_svg]:size-3.5 [&_svg]:opacity-60',
88
+ className,
89
+ )}
90
+ >
91
+ {icon && icon}
92
+ {title}
93
+ </div>
94
+ );
95
+ };
96
+
97
+ const headerButton = () => {
98
+ return (
99
+ <Button
100
+ variant="ghost"
101
+ className={cn(
102
+ 'text-secondary-foreground rounded-md font-normal -ms-2 px-2 h-7 hover:bg-secondary data-[state=open]:bg-secondary hover:text-foreground data-[state=open]:text-foreground',
103
+ className,
104
+ )}
105
+ disabled={isLoading || recordCount === 0}
106
+ onClick={() => {
107
+ const isSorted = column.getIsSorted();
108
+ if (isSorted === 'asc') {
109
+ column.toggleSorting(true);
110
+ } else if (isSorted === 'desc') {
111
+ column.clearSorting();
112
+ } else {
113
+ column.toggleSorting(false);
114
+ }
115
+ }}
116
+ >
117
+ {icon && icon}
118
+ {title}
119
+
120
+ {column.getCanSort() &&
121
+ (column.getIsSorted() === 'desc' ? (
122
+ <ArrowDown className="size-[0.7rem]! mt-px" />
123
+ ) : column.getIsSorted() === 'asc' ? (
124
+ <ArrowUp className="size-[0.7rem]! mt-px" />
125
+ ) : (
126
+ <ChevronsUpDown className="size-[0.7rem]! mt-px" />
127
+ ))}
128
+ </Button>
129
+ );
130
+ };
131
+
132
+ const headerPin = () => {
133
+ return (
134
+ <Button
135
+ size="sm"
136
+ variant="ghost"
137
+ className="-me-1 size-7 rounded-md"
138
+ onClick={() => column.pin(false)}
139
+ aria-label={`Unpin ${title} column`}
140
+ title={`Unpin ${title} column`}
141
+ >
142
+ <PinOff className="size-3.5! opacity-50!" aria-hidden="true" />
143
+ </Button>
144
+ );
145
+ };
146
+
147
+ const headerControls = () => {
148
+ return (
149
+ <div className="flex items-center h-full gap-1.5 justify-between">
150
+ <DropdownMenu>
151
+ <DropdownMenuTrigger asChild>{headerButton()}</DropdownMenuTrigger>
152
+ <DropdownMenuContent className="w-40" align="start">
153
+ {filter && <DropdownMenuLabel>{filter}</DropdownMenuLabel>}
154
+
155
+ {filter && (column.getCanSort() || column.getCanPin() || visibility) && <DropdownMenuSeparator />}
156
+
157
+ {column.getCanSort() && (
158
+ <>
159
+ <DropdownMenuItem
160
+ onClick={() => {
161
+ if (column.getIsSorted() === 'asc') {
162
+ column.clearSorting();
163
+ } else {
164
+ column.toggleSorting(false);
165
+ }
166
+ }}
167
+ disabled={!column.getCanSort()}
168
+ >
169
+ <ArrowUp className="size-3.5!" />
170
+ <span className="grow">Asc</span>
171
+ {column.getIsSorted() === 'asc' && <Check className="size-4 opacity-100! text-primary" />}
172
+ </DropdownMenuItem>
173
+ <DropdownMenuItem
174
+ onClick={() => {
175
+ if (column.getIsSorted() === 'desc') {
176
+ column.clearSorting();
177
+ } else {
178
+ column.toggleSorting(true);
179
+ }
180
+ }}
181
+ disabled={!column.getCanSort()}
182
+ >
183
+ <ArrowDown className="size-3.5!" />
184
+ <span className="grow">Desc</span>
185
+ {column.getIsSorted() === 'desc' && <Check className="size-4 opacity-100! text-primary" />}
186
+ </DropdownMenuItem>
187
+ </>
188
+ )}
189
+
190
+ {(filter || column.getCanSort()) && (column.getCanSort() || column.getCanPin() || visibility) && (
191
+ <DropdownMenuSeparator />
192
+ )}
193
+
194
+ {props.tableLayout?.columnsPinnable && column.getCanPin() && (
195
+ <>
196
+ <DropdownMenuItem onClick={() => column.pin(column.getIsPinned() === 'left' ? false : 'left')}>
197
+ <ArrowLeftToLine className="size-3.5!" aria-hidden="true" />
198
+ <span className="grow">Pin to left</span>
199
+ {column.getIsPinned() === 'left' && <Check className="size-4 opacity-100! text-primary" />}
200
+ </DropdownMenuItem>
201
+ <DropdownMenuItem onClick={() => column.pin(column.getIsPinned() === 'right' ? false : 'right')}>
202
+ <ArrowRightToLine className="size-3.5!" aria-hidden="true" />
203
+ <span className="grow">Pin to right</span>
204
+ {column.getIsPinned() === 'right' && <Check className="size-4 opacity-100! text-primary" />}
205
+ </DropdownMenuItem>
206
+ </>
207
+ )}
208
+
209
+ {props.tableLayout?.columnsMovable && (
210
+ <>
211
+ <DropdownMenuSeparator />
212
+ <DropdownMenuItem
213
+ onClick={() => moveColumn('left')}
214
+ disabled={!canMove('left') || column.getIsPinned() !== false}
215
+ >
216
+ <ArrowLeft className="size-3.5!" aria-hidden="true" />
217
+ <span>Move to Left</span>
218
+ </DropdownMenuItem>
219
+ <DropdownMenuItem
220
+ onClick={() => moveColumn('right')}
221
+ disabled={!canMove('right') || column.getIsPinned() !== false}
222
+ >
223
+ <ArrowRight className="size-3.5!" aria-hidden="true" />
224
+ <span>Move to Right</span>
225
+ </DropdownMenuItem>
226
+ </>
227
+ )}
228
+
229
+ {props.tableLayout?.columnsVisibility &&
230
+ visibility &&
231
+ (column.getCanSort() || column.getCanPin() || filter) && <DropdownMenuSeparator />}
232
+
233
+ {props.tableLayout?.columnsVisibility && visibility && (
234
+ <DropdownMenuSub>
235
+ <DropdownMenuSubTrigger>
236
+ <Settings2 className="size-3.5!" />
237
+ <span>Columns</span>
238
+ </DropdownMenuSubTrigger>
239
+ <DropdownMenuPortal>
240
+ <DropdownMenuSubContent>
241
+ {table
242
+ .getAllColumns()
243
+ .filter((col) => typeof col.accessorFn !== 'undefined' && col.getCanHide())
244
+ .map((col) => {
245
+ return (
246
+ <DropdownMenuCheckboxItem
247
+ key={col.id}
248
+ checked={col.getIsVisible()}
249
+ onSelect={(event) => event.preventDefault()}
250
+ onCheckedChange={(value) => col.toggleVisibility(!!value)}
251
+ className="capitalize"
252
+ >
253
+ {col.columnDef.meta?.headerTitle || col.id}
254
+ </DropdownMenuCheckboxItem>
255
+ );
256
+ })}
257
+ </DropdownMenuSubContent>
258
+ </DropdownMenuPortal>
259
+ </DropdownMenuSub>
260
+ )}
261
+ </DropdownMenuContent>
262
+ </DropdownMenu>
263
+ {props.tableLayout?.columnsPinnable && column.getCanPin() && column.getIsPinned() && headerPin()}
264
+ </div>
265
+ );
266
+ };
267
+
268
+ if (
269
+ props.tableLayout?.columnsMovable ||
270
+ (props.tableLayout?.columnsVisibility && visibility) ||
271
+ (props.tableLayout?.columnsPinnable && column.getCanPin()) ||
272
+ filter
273
+ ) {
274
+ return headerControls();
275
+ }
276
+
277
+ if (column.getCanSort() || (props.tableLayout?.columnsResizable && column.getCanResize())) {
278
+ return <div className="flex items-center h-full">{headerButton()}</div>;
279
+ }
280
+
281
+ return headerLabel();
282
+ }
283
+
284
+ export { DataGridColumnHeader, type DataGridColumnHeaderProps };
@@ -0,0 +1,38 @@
1
+ import { ReactNode } from 'react';
2
+ import {
3
+ DropdownMenu,
4
+ DropdownMenuCheckboxItem,
5
+ DropdownMenuContent,
6
+ DropdownMenuLabel,
7
+ DropdownMenuTrigger,
8
+ } from './dropdown-menu';
9
+ import { Table } from '@tanstack/react-table';
10
+
11
+ function DataGridColumnVisibility<TData>({ table, trigger }: { table: Table<TData>; trigger: ReactNode }) {
12
+ return (
13
+ <DropdownMenu>
14
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
15
+ <DropdownMenuContent align="end" className="min-w-[150px]">
16
+ <DropdownMenuLabel className="font-medium">Toggle Columns</DropdownMenuLabel>
17
+ {table
18
+ .getAllColumns()
19
+ .filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide())
20
+ .map((column) => {
21
+ return (
22
+ <DropdownMenuCheckboxItem
23
+ key={column.id}
24
+ className="capitalize"
25
+ checked={column.getIsVisible()}
26
+ onSelect={(event) => event.preventDefault()}
27
+ onCheckedChange={(value) => column.toggleVisibility(!!value)}
28
+ >
29
+ {column.columnDef.meta?.headerTitle || column.id}
30
+ </DropdownMenuCheckboxItem>
31
+ );
32
+ })}
33
+ </DropdownMenuContent>
34
+ </DropdownMenu>
35
+ );
36
+ }
37
+
38
+ export { DataGridColumnVisibility };
@@ -0,0 +1,206 @@
1
+ 'use client';
2
+ import { ReactNode } from 'react';
3
+ import { Button } from '../../controls/Button';
4
+ import { useDataGrid } from './data-grid';
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select';
6
+ import { Skeleton } from './skeleton';
7
+ import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
8
+ import { cn } from '../../lib/utils';
9
+
10
+ interface DataGridPaginationProps {
11
+ sizes?: number[];
12
+ sizesInfo?: string;
13
+ sizesLabel?: string;
14
+ sizesDescription?: string;
15
+ sizesSkeleton?: ReactNode;
16
+ more?: boolean;
17
+ moreLimit?: number;
18
+ info?: string;
19
+ infoSkeleton?: ReactNode;
20
+ className?: string;
21
+ }
22
+
23
+ function DataGridPagination(props: DataGridPaginationProps) {
24
+ const { table, recordCount, isLoading } = useDataGrid();
25
+
26
+ const defaultProps: Partial<DataGridPaginationProps> = {
27
+ sizes: [5, 10, 25, 50, 100],
28
+ sizesLabel: 'Show',
29
+ sizesDescription: 'per page',
30
+ sizesSkeleton: <Skeleton className="h-8 w-44" />,
31
+ moreLimit: 5,
32
+ more: false,
33
+ info: '{from} - {to} of {count}',
34
+ infoSkeleton: <Skeleton className="h-8 w-60" />,
35
+ };
36
+
37
+ const mergedProps: DataGridPaginationProps = { ...defaultProps, ...props };
38
+
39
+ const btnBaseClasses = 'size-7 p-0 text-sm';
40
+ const btnArrowClasses = btnBaseClasses + ' rtl:transform rtl:rotate-180';
41
+ const pageIndex = table.getState().pagination.pageIndex;
42
+ const pageSize = table.getState().pagination.pageSize;
43
+ const from = pageIndex * pageSize + 1;
44
+ const to = Math.min((pageIndex + 1) * pageSize, recordCount);
45
+ const pageCount = table.getPageCount();
46
+
47
+ // Replace placeholders in paginationInfo
48
+ const paginationInfo = mergedProps?.info
49
+ ? mergedProps.info
50
+ .replace('{from}', from.toString())
51
+ .replace('{to}', to.toString())
52
+ .replace('{count}', recordCount.toString())
53
+ : `${from} - ${to} of ${recordCount}`;
54
+
55
+ // Pagination limit logic
56
+ const paginationMoreLimit = mergedProps?.moreLimit || 5;
57
+
58
+ // Determine the start and end of the pagination group
59
+ const currentGroupStart = Math.floor(pageIndex / paginationMoreLimit) * paginationMoreLimit;
60
+ const currentGroupEnd = Math.min(currentGroupStart + paginationMoreLimit, pageCount);
61
+
62
+ // Render page buttons based on the current group
63
+ const renderPageButtons = () => {
64
+ const buttons = [];
65
+ for (let i = currentGroupStart; i < currentGroupEnd; i++) {
66
+ buttons.push(
67
+ <Button
68
+ key={i}
69
+ size="sm"
70
+
71
+ variant="ghost"
72
+ className={cn(btnBaseClasses, 'text-muted-foreground', {
73
+ 'bg-accent text-accent-foreground': pageIndex === i,
74
+ })}
75
+ onClick={() => {
76
+ if (pageIndex !== i) {
77
+ table.setPageIndex(i);
78
+ }
79
+ }}
80
+ >
81
+ {i + 1}
82
+ </Button>,
83
+ );
84
+ }
85
+ return buttons;
86
+ };
87
+
88
+ // Render a "previous" ellipsis button if there are previous pages to show
89
+ const renderEllipsisPrevButton = () => {
90
+ if (currentGroupStart > 0) {
91
+ return (
92
+ <Button
93
+ size="sm"
94
+
95
+ className={btnBaseClasses}
96
+ variant="ghost"
97
+ onClick={() => table.setPageIndex(currentGroupStart - 1)}
98
+ >
99
+ ...
100
+ </Button>
101
+ );
102
+ }
103
+ return null;
104
+ };
105
+
106
+ // Render a "next" ellipsis button if there are more pages to show after the current group
107
+ const renderEllipsisNextButton = () => {
108
+ if (currentGroupEnd < pageCount) {
109
+ return (
110
+ <Button
111
+ className={btnBaseClasses}
112
+ variant="ghost"
113
+ size="sm"
114
+
115
+ onClick={() => table.setPageIndex(currentGroupEnd)}
116
+ >
117
+ ...
118
+ </Button>
119
+ );
120
+ }
121
+ return null;
122
+ };
123
+
124
+ return (
125
+ <div
126
+ data-slot="data-grid-pagination"
127
+ className={cn(
128
+ 'flex flex-wrap flex-col sm:flex-row justify-between items-center gap-2.5 py-2.5 sm:py-0 grow',
129
+ mergedProps?.className,
130
+ )}
131
+ >
132
+ <div className="flex flex-wrap items-center space-x-2.5 pb-2.5 sm:pb-0 order-2 sm:order-1">
133
+ {isLoading ? (
134
+ mergedProps?.sizesSkeleton
135
+ ) : (
136
+ <>
137
+ <div className="text-sm text-muted-foreground">Rows per page</div>
138
+ <Select
139
+ value={`${pageSize}`}
140
+ indicatorPosition="right"
141
+ onValueChange={(value) => {
142
+ const newPageSize = Number(value);
143
+ table.setPageSize(newPageSize);
144
+ }}
145
+ >
146
+ <SelectTrigger className="w-fit" size="sm">
147
+ <SelectValue placeholder={`${pageSize}`} />
148
+ </SelectTrigger>
149
+ <SelectContent side="top" className="min-w-[50px]">
150
+ {mergedProps?.sizes?.map((size: number) => (
151
+ <SelectItem key={size} value={`${size}`}>
152
+ {size}
153
+ </SelectItem>
154
+ ))}
155
+ </SelectContent>
156
+ </Select>
157
+ </>
158
+ )}
159
+ </div>
160
+ <div className="flex flex-col sm:flex-row justify-center sm:justify-end items-center gap-2.5 pt-2.5 sm:pt-0 order-1 sm:order-2">
161
+ {isLoading ? (
162
+ mergedProps?.infoSkeleton
163
+ ) : (
164
+ <>
165
+ <div className="text-sm text-muted-foreground text-nowrap order-2 sm:order-1">{paginationInfo}</div>
166
+ {pageCount > 1 && (
167
+ <div className="flex items-center space-x-1 order-1 sm:order-2">
168
+ <Button
169
+ size="sm"
170
+
171
+ variant="ghost"
172
+ className={btnArrowClasses}
173
+ onClick={() => table.previousPage()}
174
+ disabled={!table.getCanPreviousPage()}
175
+ >
176
+ <span className="sr-only">Go to previous page</span>
177
+ <ChevronLeftIcon className="size-4" />
178
+ </Button>
179
+
180
+ {renderEllipsisPrevButton()}
181
+
182
+ {renderPageButtons()}
183
+
184
+ {renderEllipsisNextButton()}
185
+
186
+ <Button
187
+ size="sm"
188
+
189
+ variant="ghost"
190
+ className={btnArrowClasses}
191
+ onClick={() => table.nextPage()}
192
+ disabled={!table.getCanNextPage()}
193
+ >
194
+ <span className="sr-only">Go to next page</span>
195
+ <ChevronRightIcon className="size-4" />
196
+ </Button>
197
+ </div>
198
+ )}
199
+ </>
200
+ )}
201
+ </div>
202
+ </div>
203
+ );
204
+ }
205
+
206
+ export { DataGridPagination, type DataGridPaginationProps };
@@ -0,0 +1,147 @@
1
+ 'use client';
2
+ import { CSSProperties, useId } from 'react';
3
+ import { Button } from '../../controls/Button';
4
+ import { useDataGrid } from './data-grid';
5
+ import {
6
+ DataGridTableBase,
7
+ DataGridTableBody,
8
+ DataGridTableBodyRow,
9
+ DataGridTableBodyRowCell,
10
+ DataGridTableBodyRowSkeleton,
11
+ DataGridTableBodyRowSkeletonCell,
12
+ DataGridTableEmpty,
13
+ DataGridTableHead,
14
+ DataGridTableHeadRow,
15
+ DataGridTableHeadRowCell,
16
+ DataGridTableHeadRowCellResize,
17
+ DataGridTableRowSpacer,
18
+ } from './data-grid-table';
19
+ import {
20
+ closestCenter,
21
+ DndContext,
22
+ KeyboardSensor,
23
+ MouseSensor,
24
+ TouchSensor,
25
+ UniqueIdentifier,
26
+ useSensor,
27
+ useSensors,
28
+ type DragEndEvent,
29
+ } from '@dnd-kit/core';
30
+ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
31
+ import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
32
+ import { CSS } from '@dnd-kit/utilities';
33
+ import { Cell, flexRender, HeaderGroup, Row } from '@tanstack/react-table';
34
+ import { GripHorizontal } from 'lucide-react';
35
+
36
+ function DataGridTableDndRowHandle({ rowId }: { rowId: string }) {
37
+ const { attributes, listeners } = useSortable({
38
+ id: rowId,
39
+ });
40
+
41
+ return (
42
+ <Button variant="ghost" size="sm" className="size-7" {...attributes} {...listeners}>
43
+ <GripHorizontal />
44
+ </Button>
45
+ );
46
+ }
47
+
48
+ function DataGridTableDndRow<TData>({ row }: { row: Row<TData> }) {
49
+ const { transform, transition, setNodeRef, isDragging } = useSortable({
50
+ id: row.id,
51
+ });
52
+
53
+ const style: CSSProperties = {
54
+ transform: CSS.Transform.toString(transform), //let dnd-kit do its thing
55
+ transition: transition,
56
+ opacity: isDragging ? 0.8 : 1,
57
+ zIndex: isDragging ? 1 : 0,
58
+ position: 'relative',
59
+ };
60
+ return (
61
+ <DataGridTableBodyRow row={row} dndRef={setNodeRef} dndStyle={style} key={row.id}>
62
+ {row.getVisibleCells().map((cell: Cell<TData, unknown>, colIndex) => {
63
+ return (
64
+ <DataGridTableBodyRowCell cell={cell} key={colIndex}>
65
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
66
+ </DataGridTableBodyRowCell>
67
+ );
68
+ })}
69
+ </DataGridTableBodyRow>
70
+ );
71
+ }
72
+
73
+ function DataGridTableDndRows<TData>({
74
+ handleDragEnd,
75
+ dataIds,
76
+ }: {
77
+ handleDragEnd: (event: DragEndEvent) => void;
78
+ dataIds: UniqueIdentifier[];
79
+ }) {
80
+ const { table, isLoading, props } = useDataGrid();
81
+ const pagination = table.getState().pagination;
82
+
83
+ const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {}));
84
+
85
+ return (
86
+ <DndContext
87
+ id={useId()}
88
+ collisionDetection={closestCenter}
89
+ modifiers={[restrictToVerticalAxis]}
90
+ onDragEnd={handleDragEnd}
91
+ sensors={sensors}
92
+ >
93
+ <div className="relative">
94
+ <DataGridTableBase>
95
+ <DataGridTableHead>
96
+ {table.getHeaderGroups().map((headerGroup: HeaderGroup<TData>, index) => {
97
+ return (
98
+ <DataGridTableHeadRow headerGroup={headerGroup} key={index}>
99
+ {headerGroup.headers.map((header, index) => {
100
+ const { column } = header;
101
+
102
+ return (
103
+ <DataGridTableHeadRowCell header={header} key={index}>
104
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
105
+ {props.tableLayout?.columnsResizable && column.getCanResize() && (
106
+ <DataGridTableHeadRowCellResize header={header} />
107
+ )}
108
+ </DataGridTableHeadRowCell>
109
+ );
110
+ })}
111
+ </DataGridTableHeadRow>
112
+ );
113
+ })}
114
+ </DataGridTableHead>
115
+
116
+ {(props.tableLayout?.stripped || !props.tableLayout?.rowBorder) && <DataGridTableRowSpacer />}
117
+
118
+ <DataGridTableBody>
119
+ {props.loadingMode === 'skeleton' && isLoading && pagination?.pageSize ? (
120
+ Array.from({ length: pagination.pageSize }).map((_, rowIndex) => (
121
+ <DataGridTableBodyRowSkeleton key={rowIndex}>
122
+ {table.getVisibleFlatColumns().map((column, colIndex) => {
123
+ return (
124
+ <DataGridTableBodyRowSkeletonCell column={column} key={colIndex}>
125
+ {column.columnDef.meta?.skeleton}
126
+ </DataGridTableBodyRowSkeletonCell>
127
+ );
128
+ })}
129
+ </DataGridTableBodyRowSkeleton>
130
+ ))
131
+ ) : table.getRowModel().rows.length ? (
132
+ <SortableContext items={dataIds} strategy={verticalListSortingStrategy}>
133
+ {table.getRowModel().rows.map((row: Row<TData>) => {
134
+ return <DataGridTableDndRow row={row} key={row.id} />;
135
+ })}
136
+ </SortableContext>
137
+ ) : (
138
+ <DataGridTableEmpty />
139
+ )}
140
+ </DataGridTableBody>
141
+ </DataGridTableBase>
142
+ </div>
143
+ </DndContext>
144
+ );
145
+ }
146
+
147
+ export { DataGridTableDndRowHandle, DataGridTableDndRows };