@thangnqdev/ui 0.1.2 → 0.2.1

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 (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -206
  3. package/bin/cli.js +125 -0
  4. package/dist/bento/index.cjs.map +1 -1
  5. package/dist/bento/index.js +2 -2
  6. package/dist/bento/styles.css +2 -46
  7. package/dist/buttons/index.cjs +111 -18
  8. package/dist/buttons/index.cjs.map +1 -1
  9. package/dist/buttons/index.d.cts +1 -1
  10. package/dist/buttons/index.d.ts +1 -1
  11. package/dist/buttons/index.js +3 -3
  12. package/dist/buttons/styles.css +2 -6
  13. package/dist/{chunk-4S6L5V7X.js → chunk-2YJBW4E2.js} +2 -2
  14. package/dist/chunk-2YJBW4E2.js.map +1 -0
  15. package/dist/{chunk-F5Q7BTMH.js → chunk-4CRFNALF.js} +1011 -556
  16. package/dist/chunk-4CRFNALF.js.map +1 -0
  17. package/dist/{chunk-LWFRUJRU.js → chunk-4MS6ZHFQ.js} +225 -41
  18. package/dist/chunk-4MS6ZHFQ.js.map +1 -0
  19. package/dist/{chunk-2OXZK6IP.js → chunk-77KTYOMQ.js} +3 -3
  20. package/dist/chunk-77KTYOMQ.js.map +1 -0
  21. package/dist/{chunk-IABPUCAQ.js → chunk-ADO3SVUA.js} +2 -2
  22. package/dist/chunk-ADO3SVUA.js.map +1 -0
  23. package/dist/{chunk-GXI2H5XA.js → chunk-D7FB3TCA.js} +3 -3
  24. package/dist/chunk-D7FB3TCA.js.map +1 -0
  25. package/dist/chunk-DSEL7ELZ.js +314 -0
  26. package/dist/chunk-DSEL7ELZ.js.map +1 -0
  27. package/dist/{chunk-KJRBRTQE.js → chunk-EXFR2BFJ.js} +37 -28
  28. package/dist/chunk-EXFR2BFJ.js.map +1 -0
  29. package/dist/{chunk-PF4ZHGVW.js → chunk-GXWB6BSY.js} +116 -23
  30. package/dist/chunk-GXWB6BSY.js.map +1 -0
  31. package/dist/{chunk-66VWUJEI.js → chunk-I2FQBMVW.js} +3 -3
  32. package/dist/chunk-I2FQBMVW.js.map +1 -0
  33. package/dist/{chunk-EU2LPUYP.js → chunk-I5O2WCGF.js} +3 -3
  34. package/dist/chunk-I5O2WCGF.js.map +1 -0
  35. package/dist/{chunk-ATYGDTMK.js → chunk-ME4XILG5.js} +5 -5
  36. package/dist/chunk-ME4XILG5.js.map +1 -0
  37. package/dist/{chunk-5FZ6ZFII.js → chunk-PC5SZFZC.js} +373 -150
  38. package/dist/chunk-PC5SZFZC.js.map +1 -0
  39. package/dist/{chunk-C4SUXMI3.js → chunk-S6XJMYME.js} +3 -3
  40. package/dist/chunk-S6XJMYME.js.map +1 -0
  41. package/dist/{chunk-A7P3O5Z5.js → chunk-V52JOSDF.js} +3 -3
  42. package/dist/chunk-V52JOSDF.js.map +1 -0
  43. package/dist/{chunk-NFVONJUK.js → chunk-W5WNWTQC.js} +455 -211
  44. package/dist/chunk-W5WNWTQC.js.map +1 -0
  45. package/dist/{chunk-WAFHC4DQ.js → chunk-YJ64RI7J.js} +3 -3
  46. package/dist/chunk-YJ64RI7J.js.map +1 -0
  47. package/dist/chunk-YOCGBORV.js +155 -0
  48. package/dist/chunk-YOCGBORV.js.map +1 -0
  49. package/dist/chunk-ZEDXARXW.js +96 -0
  50. package/dist/chunk-ZEDXARXW.js.map +1 -0
  51. package/dist/components.css +2 -0
  52. package/dist/core/index.cjs +33 -24
  53. package/dist/core/index.cjs.map +1 -1
  54. package/dist/core/index.js +2 -2
  55. package/dist/core/styles.css +2 -134
  56. package/dist/data-display/index.cjs +770 -247
  57. package/dist/data-display/index.cjs.map +1 -1
  58. package/dist/data-display/index.d.cts +1 -1
  59. package/dist/data-display/index.d.ts +1 -1
  60. package/dist/data-display/index.js +5 -4
  61. package/dist/data-display/styles.css +2 -4
  62. package/dist/feedback/index.cjs.map +1 -1
  63. package/dist/feedback/index.js +2 -2
  64. package/dist/feedback/styles.css +2 -4
  65. package/dist/forms/index.cjs +1211 -459
  66. package/dist/forms/index.cjs.map +1 -1
  67. package/dist/forms/index.d.cts +1 -1
  68. package/dist/forms/index.d.ts +1 -1
  69. package/dist/forms/index.js +8 -8
  70. package/dist/forms/styles.css +2 -6
  71. package/dist/glass/index.cjs.map +1 -1
  72. package/dist/glass/index.js +2 -2
  73. package/dist/glass/styles.css +2 -81
  74. package/dist/{index-D6fE7w-b.d.cts → index-BGt_hecO.d.cts} +1 -0
  75. package/dist/{index-D6fE7w-b.d.ts → index-BGt_hecO.d.ts} +1 -0
  76. package/dist/{index-DzcWh1Hs.d.cts → index-BtDvpZCf.d.cts} +7 -0
  77. package/dist/{index-DzcWh1Hs.d.ts → index-BtDvpZCf.d.ts} +7 -0
  78. package/dist/{index-Cle6QzQl.d.ts → index-COX1sBUN.d.ts} +27 -3
  79. package/dist/{index-BkRqGNG1.d.ts → index-Cg8MuP7u.d.cts} +40 -13
  80. package/dist/{index-BkRqGNG1.d.cts → index-Cg8MuP7u.d.ts} +40 -13
  81. package/dist/{index-CenuF_P2.d.ts → index-DHkPRTvE.d.cts} +39 -8
  82. package/dist/{index-CenuF_P2.d.cts → index-DHkPRTvE.d.ts} +39 -8
  83. package/dist/{index-TyK-Ldpy.d.cts → index-Du_oht9k.d.cts} +27 -3
  84. package/dist/index.cjs +2481 -917
  85. package/dist/index.cjs.map +1 -1
  86. package/dist/index.d.cts +5 -5
  87. package/dist/index.d.ts +5 -5
  88. package/dist/index.js +19 -19
  89. package/dist/layout/index.cjs.map +1 -1
  90. package/dist/layout/index.js +2 -2
  91. package/dist/layout/styles.css +2 -8
  92. package/dist/navigation/index.cjs +288 -48
  93. package/dist/navigation/index.cjs.map +1 -1
  94. package/dist/navigation/index.d.cts +1 -1
  95. package/dist/navigation/index.d.ts +1 -1
  96. package/dist/navigation/index.js +6 -6
  97. package/dist/navigation/styles.css +2 -4
  98. package/dist/overlays/index.cjs +566 -229
  99. package/dist/overlays/index.cjs.map +1 -1
  100. package/dist/overlays/index.d.cts +1 -1
  101. package/dist/overlays/index.d.ts +1 -1
  102. package/dist/overlays/index.js +7 -7
  103. package/dist/overlays/styles.css +2 -8
  104. package/dist/reset.css +45 -0
  105. package/dist/styles.css +1 -1
  106. package/dist/tokens.css +11 -0
  107. package/dist/typography/index.cjs.map +1 -1
  108. package/dist/typography/index.js +4 -4
  109. package/dist/typography/styles.css +2 -5
  110. package/llms.txt +211 -113
  111. package/package.json +167 -116
  112. package/scripts/postinstall.js +125 -0
  113. package/src/bento/BentoCard.tsx +140 -140
  114. package/src/bento/BentoGrid.tsx +50 -50
  115. package/src/bento/index.ts +2 -2
  116. package/src/bento/tokens.css +46 -46
  117. package/src/buttons/Button.tsx +104 -104
  118. package/src/buttons/ButtonGroup.tsx +43 -43
  119. package/src/buttons/IconButton.tsx +79 -79
  120. package/src/buttons/SplitButton.tsx +186 -86
  121. package/src/buttons/ToggleButton.tsx +157 -157
  122. package/src/buttons/index.ts +5 -5
  123. package/src/buttons/tokens.css +6 -6
  124. package/src/core/ThemeProvider.tsx +135 -123
  125. package/src/core/index.ts +2 -2
  126. package/src/core/tokens.css +134 -134
  127. package/src/data-display/Accordion.tsx +147 -147
  128. package/src/data-display/Avatar.tsx +134 -134
  129. package/src/data-display/Card.tsx +112 -112
  130. package/src/data-display/Chip.tsx +74 -74
  131. package/src/data-display/DataTablePro.tsx +299 -121
  132. package/src/data-display/StatCard.tsx +60 -60
  133. package/src/data-display/Table.tsx +74 -74
  134. package/src/data-display/Timeline.tsx +62 -62
  135. package/src/data-display/TreeView.tsx +243 -104
  136. package/src/data-display/tokens.css +4 -4
  137. package/src/feedback/Alert.tsx +80 -80
  138. package/src/feedback/EmptyState.tsx +45 -45
  139. package/src/feedback/Skeleton.tsx +66 -66
  140. package/src/feedback/Spinner.tsx +65 -65
  141. package/src/feedback/index.ts +5 -5
  142. package/src/feedback/tokens.css +4 -4
  143. package/src/forms/Checkbox.tsx +149 -69
  144. package/src/forms/CheckboxGroup.tsx +76 -29
  145. package/src/forms/Combobox.tsx +172 -28
  146. package/src/forms/DatePicker.tsx +385 -81
  147. package/src/forms/FileUpload.tsx +51 -7
  148. package/src/forms/FormField.tsx +115 -115
  149. package/src/forms/Input.tsx +151 -151
  150. package/src/forms/NumberInput.tsx +34 -20
  151. package/src/forms/PasswordInput.tsx +35 -35
  152. package/src/forms/PinInput.tsx +139 -125
  153. package/src/forms/Radio.tsx +142 -142
  154. package/src/forms/RangeSlider.tsx +237 -111
  155. package/src/forms/SearchInput.tsx +34 -34
  156. package/src/forms/Select.tsx +103 -103
  157. package/src/forms/Slider.tsx +85 -85
  158. package/src/forms/Textarea.tsx +114 -114
  159. package/src/forms/tokens.css +6 -6
  160. package/src/glass/GlassButton.tsx +59 -59
  161. package/src/glass/GlassCard.tsx +71 -71
  162. package/src/glass/index.ts +3 -3
  163. package/src/glass/tokens.css +81 -81
  164. package/src/index.ts +26 -26
  165. package/src/layout/AspectRatio.tsx +39 -39
  166. package/src/layout/Container.tsx +53 -53
  167. package/src/layout/Divider.tsx +61 -61
  168. package/src/layout/Flex.tsx +79 -79
  169. package/src/layout/Grid.tsx +124 -124
  170. package/src/layout/PageLayout.tsx +59 -59
  171. package/src/layout/Section.tsx +47 -47
  172. package/src/layout/Spacer.tsx +30 -30
  173. package/src/layout/Stack.tsx +88 -88
  174. package/src/layout/tokens.css +8 -8
  175. package/src/navigation/Breadcrumb.tsx +73 -73
  176. package/src/navigation/CommandPalette.tsx +142 -142
  177. package/src/navigation/DropdownMenu.tsx +244 -160
  178. package/src/navigation/Pagination.tsx +114 -114
  179. package/src/navigation/Stepper.tsx +93 -93
  180. package/src/navigation/Tabs.tsx +265 -133
  181. package/src/navigation/index.ts +6 -6
  182. package/src/navigation/tokens.css +4 -4
  183. package/src/overlays/AlertDialog.tsx +63 -54
  184. package/src/overlays/BottomSheet.tsx +30 -10
  185. package/src/overlays/ContextMenu.tsx +51 -9
  186. package/src/overlays/Drawer.tsx +160 -145
  187. package/src/overlays/FocusTrap.tsx +122 -49
  188. package/src/overlays/Modal.tsx +296 -219
  189. package/src/overlays/Popover.tsx +55 -25
  190. package/src/overlays/Portal.tsx +23 -23
  191. package/src/overlays/Toast.tsx +175 -137
  192. package/src/overlays/Tooltip.tsx +125 -100
  193. package/src/overlays/tokens.css +8 -8
  194. package/src/registry/components.ts +1368 -0
  195. package/src/styles/components.css +39 -0
  196. package/src/styles/index.css +2 -93
  197. package/src/styles/reset.css +45 -0
  198. package/src/styles/tokens.css +11 -2
  199. package/src/typography/Blockquote.tsx +32 -32
  200. package/src/typography/Code.tsx +89 -89
  201. package/src/typography/Heading.tsx +56 -56
  202. package/src/typography/Kbd.tsx +25 -25
  203. package/src/typography/Label.tsx +47 -47
  204. package/src/typography/Link.tsx +56 -56
  205. package/src/typography/Text.tsx +62 -62
  206. package/src/typography/index.ts +7 -7
  207. package/src/typography/tokens.css +5 -5
  208. package/src/utils/cn.ts +6 -6
  209. package/src/utils/scroll-lock.ts +19 -0
  210. package/src/utils/useControllableState.ts +54 -0
  211. package/ui-contract.json +192 -35
  212. package/dist/chunk-2OXZK6IP.js.map +0 -1
  213. package/dist/chunk-4S6L5V7X.js.map +0 -1
  214. package/dist/chunk-5FZ6ZFII.js.map +0 -1
  215. package/dist/chunk-66VWUJEI.js.map +0 -1
  216. package/dist/chunk-A7P3O5Z5.js.map +0 -1
  217. package/dist/chunk-ATYGDTMK.js.map +0 -1
  218. package/dist/chunk-C4SUXMI3.js.map +0 -1
  219. package/dist/chunk-EU2LPUYP.js.map +0 -1
  220. package/dist/chunk-F5Q7BTMH.js.map +0 -1
  221. package/dist/chunk-GXI2H5XA.js.map +0 -1
  222. package/dist/chunk-H3LCNPMC.js +0 -101
  223. package/dist/chunk-H3LCNPMC.js.map +0 -1
  224. package/dist/chunk-IABPUCAQ.js.map +0 -1
  225. package/dist/chunk-KJRBRTQE.js.map +0 -1
  226. package/dist/chunk-LBUBXUYQ.js +0 -40
  227. package/dist/chunk-LBUBXUYQ.js.map +0 -1
  228. package/dist/chunk-LWFRUJRU.js.map +0 -1
  229. package/dist/chunk-NFVONJUK.js.map +0 -1
  230. package/dist/chunk-PF4ZHGVW.js.map +0 -1
  231. package/dist/chunk-WAFHC4DQ.js.map +0 -1
  232. package/dist/chunk-X76KBH4Y.js +0 -71
  233. package/dist/chunk-X76KBH4Y.js.map +0 -1
@@ -1,10 +1,10 @@
1
- import { chipVariants, Chip } from './chunk-X76KBH4Y.js';
2
- import { Button } from './chunk-66VWUJEI.js';
3
- import { __export, cn } from './chunk-4S6L5V7X.js';
4
- import React9, { forwardRef, useState, createContext, useContext, useMemo, useRef, useEffect } from 'react';
1
+ import { chipVariants, Chip, Checkbox } from './chunk-DSEL7ELZ.js';
2
+ import { Button } from './chunk-I2FQBMVW.js';
3
+ import { __export, cn } from './chunk-2YJBW4E2.js';
4
+ import React9, { forwardRef, useState, createContext, useContext, useMemo, useEffect, useRef } from 'react';
5
5
  import { cva } from 'class-variance-authority';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
- import { ChevronDown, ArrowUpRight, ArrowDownRight, Minus, Search, Download, ChevronUp, ChevronsUpDown, ChevronLeft, ChevronRight, FolderOpen, Folder, FileCode, Image, FileText } from 'lucide-react';
7
+ import { ChevronDown, ArrowUpRight, ArrowDownRight, Minus, Search, Download, Loader2, ChevronUp, ChevronsUpDown, ChevronLeft, ChevronRight, FolderOpen, Folder, FileCode, Image, FileText } from 'lucide-react';
8
8
 
9
9
  // src/data-display/index.ts
10
10
  var data_display_exports = {};
@@ -433,79 +433,183 @@ function DataTablePro({
433
433
  searchable = true,
434
434
  searchPlaceholder = "Search all records...",
435
435
  selectable = true,
436
+ selectedKeys: controlledSelectedKeys,
437
+ defaultSelectedKeys = [],
436
438
  onSelectionChange,
437
439
  pageSize: initialPageSize = 10,
438
440
  pageSizeOptions = [5, 10, 25, 50],
439
441
  title,
440
442
  description,
441
443
  actions,
442
- className
444
+ className,
445
+ loading = false,
446
+ // Multi-column sorting
447
+ multiSort = true,
448
+ sorting: controlledSorting,
449
+ defaultSorting = [],
450
+ onSortingChange,
451
+ // Server-side / manual mode props
452
+ manualPagination = false,
453
+ manualSorting = false,
454
+ manualFiltering = false,
455
+ pageIndex: controlledPageIndex,
456
+ pageCount: controlledPageCount,
457
+ totalCount: controlledTotalCount,
458
+ onPaginationChange,
459
+ searchQuery: controlledSearchQuery,
460
+ onSearchChange,
461
+ onSelectAllDataset,
462
+ emptyText = "No matching records found."
443
463
  }) {
444
- const [search, setSearch] = useState("");
445
- const [sortKey, setSortKey] = useState(null);
446
- const [sortDirection, setSortDirection] = useState("asc");
447
- const [currentPage, setCurrentPage] = useState(1);
448
- const [pageSize, setPageSize] = useState(initialPageSize);
449
- const [selectedKeys, setSelectedKeys] = useState([]);
464
+ const [internalSearch, setInternalSearch] = useState("");
465
+ const search = controlledSearchQuery !== void 0 ? controlledSearchQuery : internalSearch;
466
+ const [internalSorting, setInternalSorting] = useState(defaultSorting);
467
+ const currentSorting = controlledSorting !== void 0 ? controlledSorting ?? [] : internalSorting;
468
+ const [internalCurrentPage, setInternalCurrentPage] = useState(1);
469
+ const currentPage = controlledPageIndex !== void 0 ? controlledPageIndex : internalCurrentPage;
470
+ const [internalPageSize, setInternalPageSize] = useState(initialPageSize);
471
+ const pageSize = internalPageSize;
472
+ const [internalSelectedKeys, setInternalSelectedKeys] = useState(defaultSelectedKeys);
473
+ const isSelectedControlled = controlledSelectedKeys !== void 0;
474
+ const selectedKeys = isSelectedControlled ? controlledSelectedKeys : internalSelectedKeys;
475
+ const handleSelectionUpdate = (nextKeys) => {
476
+ if (!isSelectedControlled) {
477
+ setInternalSelectedKeys(nextKeys);
478
+ }
479
+ onSelectionChange?.(nextKeys);
480
+ };
450
481
  const filteredData = useMemo(() => {
451
- if (!search.trim()) return data;
482
+ if (manualFiltering || !search.trim()) return data;
452
483
  const q = search.toLowerCase();
453
484
  return data.filter((row) => {
454
- return Object.values(row).some((val) => {
485
+ return columns.some((col) => {
486
+ const val = col.accessor ? col.accessor(row) : row[col.key];
455
487
  if (val === null || val === void 0) return false;
456
488
  return String(val).toLowerCase().includes(q);
457
489
  });
458
490
  });
459
- }, [data, search]);
491
+ }, [data, search, columns, manualFiltering]);
460
492
  const sortedData = useMemo(() => {
461
- if (!sortKey) return filteredData;
462
- const col = columns.find((c) => c.key === sortKey);
493
+ if (manualSorting || currentSorting.length === 0) return filteredData;
463
494
  return [...filteredData].sort((a, b) => {
464
- const valA = col?.accessor ? col.accessor(a) : a[sortKey];
465
- const valB = col?.accessor ? col.accessor(b) : b[sortKey];
466
- if (valA === valB) return 0;
467
- if (valA === null || valA === void 0) return 1;
468
- if (valB === null || valB === void 0) return -1;
469
- const comp = valA < valB ? -1 : 1;
470
- return sortDirection === "asc" ? comp : -comp;
495
+ for (const { key, direction } of currentSorting) {
496
+ const col = columns.find((c) => c.key === key);
497
+ let comp = 0;
498
+ if (col?.sortComparator) {
499
+ comp = col.sortComparator(a, b);
500
+ } else {
501
+ const valA = col?.accessor ? col.accessor(a) : a[key];
502
+ const valB = col?.accessor ? col.accessor(b) : b[key];
503
+ if (valA === valB) continue;
504
+ if (valA === null || valA === void 0) return 1;
505
+ if (valB === null || valB === void 0) return -1;
506
+ comp = valA < valB ? -1 : 1;
507
+ }
508
+ if (comp !== 0) {
509
+ return direction === "asc" ? comp : -comp;
510
+ }
511
+ }
512
+ return 0;
471
513
  });
472
- }, [filteredData, sortKey, sortDirection, columns]);
473
- const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
514
+ }, [filteredData, currentSorting, columns, manualSorting]);
515
+ const totalItems = controlledTotalCount !== void 0 ? controlledTotalCount : sortedData.length;
516
+ const totalPages = controlledPageCount !== void 0 ? Math.max(1, controlledPageCount) : Math.max(1, Math.ceil(totalItems / pageSize));
517
+ useEffect(() => {
518
+ if (!manualPagination && currentPage > totalPages && totalPages > 0) {
519
+ setInternalCurrentPage(totalPages);
520
+ onPaginationChange?.(totalPages, pageSize);
521
+ }
522
+ }, [totalPages, currentPage, manualPagination, pageSize, onPaginationChange]);
474
523
  const paginatedData = useMemo(() => {
524
+ if (manualPagination) return data;
475
525
  const start = (currentPage - 1) * pageSize;
476
526
  return sortedData.slice(start, start + pageSize);
477
- }, [sortedData, currentPage, pageSize]);
478
- const handleSort = (key) => {
479
- if (sortKey === key) {
480
- if (sortDirection === "asc") {
481
- setSortDirection("desc");
527
+ }, [data, sortedData, currentPage, pageSize, manualPagination]);
528
+ const handleSort = (key, e) => {
529
+ const isShift = e?.shiftKey || false;
530
+ let nextSorting = [...currentSorting];
531
+ const existingIdx = nextSorting.findIndex((s) => s.key === key);
532
+ if (isShift && multiSort) {
533
+ if (existingIdx >= 0) {
534
+ if (nextSorting[existingIdx].direction === "asc") {
535
+ nextSorting[existingIdx] = { key, direction: "desc" };
536
+ } else {
537
+ nextSorting.splice(existingIdx, 1);
538
+ }
482
539
  } else {
483
- setSortKey(null);
484
- setSortDirection("asc");
540
+ nextSorting.push({ key, direction: "asc" });
485
541
  }
486
542
  } else {
487
- setSortKey(key);
488
- setSortDirection("asc");
543
+ if (existingIdx >= 0 && nextSorting.length === 1) {
544
+ if (nextSorting[0].direction === "asc") {
545
+ nextSorting = [{ key, direction: "desc" }];
546
+ } else {
547
+ nextSorting = [];
548
+ }
549
+ } else {
550
+ nextSorting = [{ key, direction: "asc" }];
551
+ }
552
+ }
553
+ if (controlledSorting === void 0) {
554
+ setInternalSorting(nextSorting);
489
555
  }
556
+ onSortingChange?.(nextSorting);
490
557
  };
491
- const toggleSelectAll = () => {
492
- if (selectedKeys.length === paginatedData.length) {
493
- setSelectedKeys([]);
494
- onSelectionChange?.([]);
558
+ const handlePageChange = (newPage) => {
559
+ const clamped = Math.max(1, Math.min(totalPages, newPage));
560
+ if (controlledPageIndex === void 0) {
561
+ setInternalCurrentPage(clamped);
562
+ }
563
+ onPaginationChange?.(clamped, pageSize);
564
+ };
565
+ const handlePageSizeChange = (newSize) => {
566
+ setInternalPageSize(newSize);
567
+ if (controlledPageIndex === void 0) {
568
+ setInternalCurrentPage(1);
569
+ }
570
+ onPaginationChange?.(1, newSize);
571
+ };
572
+ const handleSearchChange = (query) => {
573
+ if (controlledSearchQuery === void 0) {
574
+ setInternalSearch(query);
575
+ if (controlledPageIndex === void 0) {
576
+ setInternalCurrentPage(1);
577
+ }
578
+ }
579
+ onSearchChange?.(query);
580
+ };
581
+ const currentPageKeys = useMemo(() => paginatedData.map(keyExtractor), [paginatedData, keyExtractor]);
582
+ const isAllCurrentPageSelected = currentPageKeys.length > 0 && currentPageKeys.every((k) => selectedKeys.includes(k));
583
+ const isSomeCurrentPageSelected = currentPageKeys.some((k) => selectedKeys.includes(k)) && !isAllCurrentPageSelected;
584
+ const toggleSelectAllCurrentPage = () => {
585
+ if (isAllCurrentPageSelected) {
586
+ const next = selectedKeys.filter((k) => !currentPageKeys.includes(k));
587
+ handleSelectionUpdate(next);
495
588
  } else {
496
- const allKeys = paginatedData.map(keyExtractor);
497
- setSelectedKeys(allKeys);
498
- onSelectionChange?.(allKeys);
589
+ const combined = Array.from(/* @__PURE__ */ new Set([...selectedKeys, ...currentPageKeys]));
590
+ handleSelectionUpdate(combined);
499
591
  }
500
592
  };
501
593
  const toggleSelectRow = (key) => {
502
594
  const next = selectedKeys.includes(key) ? selectedKeys.filter((k) => k !== key) : [...selectedKeys, key];
503
- setSelectedKeys(next);
504
- onSelectionChange?.(next);
595
+ handleSelectionUpdate(next);
596
+ };
597
+ const canSelectAllDataset = !manualPagination || typeof onSelectAllDataset === "function";
598
+ const selectAllDataset = () => {
599
+ if (manualPagination) {
600
+ onSelectAllDataset?.();
601
+ return;
602
+ }
603
+ const allKeys = sortedData.map(keyExtractor);
604
+ handleSelectionUpdate(allKeys);
605
+ };
606
+ const clearSelection = () => {
607
+ handleSelectionUpdate([]);
505
608
  };
506
609
  const exportCSV = () => {
610
+ const exportRows = manualPagination ? data : sortedData;
507
611
  const headers = columns.map((c) => typeof c.header === "string" ? c.header : c.key);
508
- const rows = sortedData.map(
612
+ const rows = exportRows.map(
509
613
  (row) => columns.map((c) => {
510
614
  const val = c.accessor ? c.accessor(row) : row[c.key];
511
615
  return `"${String(val ?? "").replace(/"/g, '""')}"`;
@@ -520,7 +624,6 @@ function DataTablePro({
520
624
  link.click();
521
625
  document.body.removeChild(link);
522
626
  };
523
- const isAllSelected = paginatedData.length > 0 && selectedKeys.length === paginatedData.length;
524
627
  return /* @__PURE__ */ jsxs("div", { className: cn("w-full space-y-4", className), children: [
525
628
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-4", children: [
526
629
  (title || description) && /* @__PURE__ */ jsxs("div", { children: [
@@ -536,10 +639,7 @@ function DataTablePro({
536
639
  type: "text",
537
640
  placeholder: searchPlaceholder,
538
641
  value: search,
539
- onChange: (e) => {
540
- setSearch(e.target.value);
541
- setCurrentPage(1);
542
- },
642
+ onChange: (e) => handleSearchChange(e.target.value),
543
643
  className: "w-full h-8 pl-8 pr-3 text-xs rounded-xl bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)] focus:outline-none focus:ring-2 focus:ring-[var(--ui-primary)]/40 text-[var(--ui-text-primary)] placeholder:text-[var(--ui-text-muted)] transition-all shadow-inner"
544
644
  }
545
645
  )
@@ -548,83 +648,110 @@ function DataTablePro({
548
648
  actions
549
649
  ] })
550
650
  ] }),
551
- selectedKeys.length > 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 rounded-xl bg-[var(--ui-primary)]/15 border border-[var(--ui-primary)]/30 flex items-center justify-between animate-in fade-in duration-200", children: [
552
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 text-xs font-semibold text-[var(--ui-primary)]", children: /* @__PURE__ */ jsxs("span", { children: [
553
- "Selected ",
554
- selectedKeys.length,
555
- " of ",
556
- sortedData.length,
557
- " records"
558
- ] }) }),
651
+ selectedKeys.length > 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 rounded-xl bg-[var(--ui-primary)]/15 border border-[var(--ui-primary)]/30 flex flex-wrap items-center justify-between gap-2 animate-in fade-in duration-200", children: [
652
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs font-semibold text-[var(--ui-primary)]", children: [
653
+ /* @__PURE__ */ jsxs("span", { children: [
654
+ "Selected ",
655
+ selectedKeys.length,
656
+ " records"
657
+ ] }),
658
+ canSelectAllDataset && selectedKeys.length < totalItems && /* @__PURE__ */ jsxs(
659
+ "button",
660
+ {
661
+ type: "button",
662
+ onClick: selectAllDataset,
663
+ className: "underline hover:text-[var(--ui-primary)]/80 cursor-pointer font-bold ml-1",
664
+ children: [
665
+ "Select all ",
666
+ totalItems,
667
+ " records"
668
+ ]
669
+ }
670
+ )
671
+ ] }),
559
672
  /* @__PURE__ */ jsx(
560
673
  Button,
561
674
  {
562
675
  size: "xs",
563
676
  variant: "ghost",
564
677
  className: "text-xs text-rose-500 hover:text-rose-600 hover:bg-rose-500/10",
565
- onClick: () => {
566
- setSelectedKeys([]);
567
- onSelectionChange?.([]);
568
- },
678
+ onClick: clearSelection,
569
679
  children: "Clear Selection"
570
680
  }
571
681
  )
572
682
  ] }),
573
- /* @__PURE__ */ jsx("div", { className: "relative w-full overflow-x-auto rounded-2xl border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] shadow-sm", children: /* @__PURE__ */ jsxs(Table, { children: [
574
- /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
575
- selectable && /* @__PURE__ */ jsx(TableHead, { className: "w-10 px-4", children: /* @__PURE__ */ jsx(
576
- "input",
683
+ /* @__PURE__ */ jsxs("div", { className: "relative w-full overflow-x-auto rounded-2xl border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] shadow-sm", children: [
684
+ loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-20 bg-[var(--ui-bg-surface)]/60 backdrop-blur-xs flex items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 animate-spin text-[var(--ui-primary)]" }) }),
685
+ /* @__PURE__ */ jsxs(Table, { children: [
686
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
687
+ selectable && /* @__PURE__ */ jsx(TableHead, { className: "w-10 px-4", children: /* @__PURE__ */ jsx(
688
+ Checkbox,
689
+ {
690
+ checked: isAllCurrentPageSelected,
691
+ indeterminate: isSomeCurrentPageSelected,
692
+ onCheckedChange: toggleSelectAllCurrentPage,
693
+ "aria-label": "Select all on page"
694
+ }
695
+ ) }),
696
+ columns.map((col) => {
697
+ const sortIndex = currentSorting.findIndex((s) => s.key === col.key);
698
+ const sortRule = sortIndex >= 0 ? currentSorting[sortIndex] : null;
699
+ return /* @__PURE__ */ jsx(
700
+ TableHead,
701
+ {
702
+ style: { width: col.width },
703
+ onClick: (e) => col.sortable !== false && handleSort(col.key, e),
704
+ className: cn(
705
+ col.sortable !== false && "cursor-pointer select-none hover:text-[var(--ui-text-primary)] transition-colors"
706
+ ),
707
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
708
+ /* @__PURE__ */ jsx("span", { children: col.header }),
709
+ col.sortable !== false && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 text-[var(--ui-text-muted)]", children: [
710
+ sortRule ? sortRule.direction === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "w-3 h-3 opacity-40" }),
711
+ currentSorting.length > 1 && sortIndex >= 0 && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono font-bold text-[var(--ui-primary)]", children: sortIndex + 1 })
712
+ ] })
713
+ ] })
714
+ },
715
+ col.key
716
+ );
717
+ })
718
+ ] }) }),
719
+ /* @__PURE__ */ jsx(TableBody, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
720
+ TableCell,
577
721
  {
578
- type: "checkbox",
579
- checked: isAllSelected,
580
- onChange: toggleSelectAll,
581
- className: "rounded border-[var(--ui-border-default)] text-[var(--ui-primary)] focus:ring-[var(--ui-primary)] cursor-pointer"
722
+ colSpan: columns.length + (selectable ? 1 : 0),
723
+ className: "h-32 text-center text-xs text-[var(--ui-text-muted)]",
724
+ children: emptyText
582
725
  }
583
- ) }),
584
- columns.map((col) => /* @__PURE__ */ jsx(
585
- TableHead,
586
- {
587
- style: { width: col.width },
588
- onClick: () => col.sortable !== false && handleSort(col.key),
589
- className: cn(
590
- col.sortable !== false && "cursor-pointer select-none hover:text-[var(--ui-text-primary)] transition-colors"
591
- ),
592
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
593
- /* @__PURE__ */ jsx("span", { children: col.header }),
594
- col.sortable !== false && /* @__PURE__ */ jsx("span", { className: "text-[var(--ui-text-muted)]", children: sortKey === col.key ? sortDirection === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "w-3.5 h-3.5 text-[var(--ui-primary)] font-bold" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "w-3 h-3 opacity-40" }) })
595
- ] })
596
- },
597
- col.key
598
- ))
599
- ] }) }),
600
- /* @__PURE__ */ jsx(TableBody, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: columns.length + (selectable ? 1 : 0), className: "h-32 text-center text-xs text-[var(--ui-text-muted)]", children: "No matching records found." }) }) : paginatedData.map((row, rowIdx) => {
601
- const key = keyExtractor(row);
602
- const isSelected = selectedKeys.includes(key);
603
- return /* @__PURE__ */ jsxs(
604
- TableRow,
605
- {
606
- "data-state": isSelected ? "selected" : void 0,
607
- className: cn(isSelected && "bg-[var(--ui-primary)]/10"),
608
- children: [
609
- selectable && /* @__PURE__ */ jsx(TableCell, { className: "w-10 px-4", children: /* @__PURE__ */ jsx(
610
- "input",
611
- {
612
- type: "checkbox",
613
- checked: isSelected,
614
- onChange: () => toggleSelectRow(key),
615
- className: "rounded border-[var(--ui-border-default)] text-[var(--ui-primary)] focus:ring-[var(--ui-primary)] cursor-pointer"
616
- }
617
- ) }),
618
- columns.map((col) => {
619
- const cellContent = col.render ? col.render(row, rowIdx) : col.accessor ? col.accessor(row) : row[col.key];
620
- return /* @__PURE__ */ jsx(TableCell, { children: cellContent }, col.key);
621
- })
622
- ]
623
- },
624
- key
625
- );
626
- }) })
627
- ] }) }),
726
+ ) }) : paginatedData.map((row, rowIdx) => {
727
+ const key = keyExtractor(row);
728
+ const isSelected = selectedKeys.includes(key);
729
+ return /* @__PURE__ */ jsxs(
730
+ TableRow,
731
+ {
732
+ "data-state": isSelected ? "selected" : void 0,
733
+ className: cn(isSelected && "bg-[var(--ui-primary)]/10"),
734
+ children: [
735
+ selectable && /* @__PURE__ */ jsx(TableCell, { className: "w-10 px-4", children: /* @__PURE__ */ jsx(
736
+ Checkbox,
737
+ {
738
+ checked: isSelected,
739
+ onCheckedChange: () => toggleSelectRow(key),
740
+ "aria-label": `Select row ${key}`
741
+ }
742
+ ) }),
743
+ columns.map((col) => {
744
+ const val = col.accessor ? col.accessor(row) : row[col.key];
745
+ const cellContent = col.render ? col.render(row, rowIdx, val) : val;
746
+ return /* @__PURE__ */ jsx(TableCell, { children: cellContent }, col.key);
747
+ })
748
+ ]
749
+ },
750
+ key
751
+ );
752
+ }) })
753
+ ] })
754
+ ] }),
628
755
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-3 px-1 text-xs text-[var(--ui-text-muted)] font-mono", children: [
629
756
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
630
757
  /* @__PURE__ */ jsx("span", { children: "Rows per page:" }),
@@ -632,21 +759,18 @@ function DataTablePro({
632
759
  "select",
633
760
  {
634
761
  value: pageSize,
635
- onChange: (e) => {
636
- setPageSize(Number(e.target.value));
637
- setCurrentPage(1);
638
- },
762
+ onChange: (e) => handlePageSizeChange(Number(e.target.value)),
639
763
  className: "px-2 py-1 rounded-lg bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] text-[var(--ui-text-primary)] font-mono text-xs focus:outline-none",
640
764
  children: pageSizeOptions.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
641
765
  }
642
766
  ),
643
767
  /* @__PURE__ */ jsxs("span", { className: "text-[var(--ui-text-muted)]", children: [
644
768
  "Showing ",
645
- Math.min((currentPage - 1) * pageSize + 1, sortedData.length),
769
+ totalItems === 0 ? 0 : Math.min((currentPage - 1) * pageSize + 1, totalItems),
646
770
  "\u2013",
647
- Math.min(currentPage * pageSize, sortedData.length),
771
+ Math.min(currentPage * pageSize, totalItems),
648
772
  " of ",
649
- sortedData.length
773
+ totalItems
650
774
  ] })
651
775
  ] }),
652
776
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -655,8 +779,9 @@ function DataTablePro({
655
779
  {
656
780
  size: "xs",
657
781
  variant: "outline",
658
- disabled: currentPage === 1,
659
- onClick: () => setCurrentPage((p) => Math.max(1, p - 1)),
782
+ disabled: currentPage <= 1 || loading,
783
+ onClick: () => handlePageChange(currentPage - 1),
784
+ "aria-label": "Previous page",
660
785
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-3.5 h-3.5" })
661
786
  }
662
787
  ),
@@ -670,8 +795,9 @@ function DataTablePro({
670
795
  {
671
796
  size: "xs",
672
797
  variant: "outline",
673
- disabled: currentPage === totalPages,
674
- onClick: () => setCurrentPage((p) => Math.min(totalPages, p + 1)),
798
+ disabled: currentPage >= totalPages || loading,
799
+ onClick: () => handlePageChange(currentPage + 1),
800
+ "aria-label": "Next page",
675
801
  children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-3.5 h-3.5" })
676
802
  }
677
803
  )
@@ -679,100 +805,218 @@ function DataTablePro({
679
805
  ] })
680
806
  ] });
681
807
  }
682
- function TreeNode({ item, level, selectedId, onSelect }) {
683
- const [isExpanded, setIsExpanded] = useState(Boolean(item.defaultExpanded));
684
- const hasChildren = Boolean(item.children && item.children.length > 0);
685
- const isSelected = selectedId === item.id;
686
- const handleClick = (e) => {
687
- e.stopPropagation();
688
- if (hasChildren) {
689
- setIsExpanded(!isExpanded);
690
- }
691
- onSelect?.(item);
692
- };
693
- const getIcon = () => {
694
- if (item.icon) return item.icon;
695
- if (hasChildren) {
696
- return isExpanded ? /* @__PURE__ */ jsx(FolderOpen, { className: "w-4 h-4 text-amber-500 shrink-0" }) : /* @__PURE__ */ jsx(Folder, { className: "w-4 h-4 text-amber-500 shrink-0" });
697
- }
698
- if (item.label.endsWith(".tsx") || item.label.endsWith(".ts") || item.label.endsWith(".js")) {
699
- return /* @__PURE__ */ jsx(FileCode, { className: "w-4 h-4 text-blue-500 shrink-0" });
700
- }
701
- if (item.label.endsWith(".png") || item.label.endsWith(".jpg") || item.label.endsWith(".svg")) {
702
- return /* @__PURE__ */ jsx(Image, { className: "w-4 h-4 text-purple-500 shrink-0" });
703
- }
704
- return /* @__PURE__ */ jsx(FileText, { className: "w-4 h-4 text-[var(--ui-text-muted)] shrink-0" });
705
- };
706
- return /* @__PURE__ */ jsxs("div", { className: "select-none text-xs", children: [
707
- /* @__PURE__ */ jsxs(
708
- "div",
709
- {
710
- onClick: handleClick,
711
- style: { paddingLeft: `${level * 16 + 8}px` },
712
- className: cn(
713
- "flex items-center justify-between py-1.5 pr-2.5 rounded-xl cursor-pointer transition-colors group",
714
- isSelected ? "bg-[var(--ui-primary)] text-white font-semibold shadow-xs" : "text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)]"
715
- ),
716
- children: [
717
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 truncate", children: [
718
- hasChildren ? /* @__PURE__ */ jsx("span", { className: cn("p-0.5 rounded transition-transform duration-200", isExpanded && "rotate-90"), children: /* @__PURE__ */ jsx(ChevronRight, { className: cn("w-3 h-3", isSelected ? "text-white" : "text-[var(--ui-text-muted)]") }) }) : /* @__PURE__ */ jsx("span", { className: "w-4" }),
719
- getIcon(),
720
- /* @__PURE__ */ jsx("span", { className: "truncate", children: item.label })
721
- ] }),
722
- item.badge !== void 0 && /* @__PURE__ */ jsx(
723
- "span",
808
+ var TreeView = forwardRef(
809
+ ({
810
+ data,
811
+ selectedId,
812
+ defaultExpandedIds,
813
+ onSelect,
814
+ onExpandedChange,
815
+ className,
816
+ "aria-label": ariaLabel = "Tree View"
817
+ }, ref) => {
818
+ const initialExpanded = useMemo(() => {
819
+ if (defaultExpandedIds) return new Set(defaultExpandedIds);
820
+ const set = /* @__PURE__ */ new Set();
821
+ const collect = (items) => {
822
+ for (const it of items) {
823
+ if (it.defaultExpanded) set.add(it.id);
824
+ if (it.children) collect(it.children);
825
+ }
826
+ };
827
+ collect(data);
828
+ return set;
829
+ }, [data, defaultExpandedIds]);
830
+ const [expandedSet, setExpandedSet] = useState(initialExpanded);
831
+ const [focusedId, setFocusedId] = useState(null);
832
+ const itemRefs = useRef(/* @__PURE__ */ new Map());
833
+ const focusNode = (id) => {
834
+ setFocusedId(id);
835
+ const el = itemRefs.current.get(id);
836
+ if (el) {
837
+ el.focus();
838
+ }
839
+ };
840
+ const toggleExpand = (id) => {
841
+ setExpandedSet((prev) => {
842
+ const next = new Set(prev);
843
+ if (next.has(id)) next.delete(id);
844
+ else next.add(id);
845
+ onExpandedChange?.(Array.from(next));
846
+ return next;
847
+ });
848
+ };
849
+ const expandNode = (id) => {
850
+ if (!expandedSet.has(id)) {
851
+ setExpandedSet((prev) => {
852
+ const next = new Set(prev).add(id);
853
+ onExpandedChange?.(Array.from(next));
854
+ return next;
855
+ });
856
+ }
857
+ };
858
+ const collapseNode = (id) => {
859
+ if (expandedSet.has(id)) {
860
+ setExpandedSet((prev) => {
861
+ const next = new Set(prev);
862
+ next.delete(id);
863
+ onExpandedChange?.(Array.from(next));
864
+ return next;
865
+ });
866
+ }
867
+ };
868
+ const visibleList = useMemo(() => {
869
+ const list = [];
870
+ const traverse = (items, level, parentId) => {
871
+ for (const item of items) {
872
+ const hasChildren = Boolean(item.children && item.children.length > 0);
873
+ list.push({ item, level, parentId, hasChildren });
874
+ if (hasChildren && expandedSet.has(item.id)) {
875
+ traverse(item.children, level + 1, item.id);
876
+ }
877
+ }
878
+ };
879
+ traverse(data, 0, null);
880
+ return list;
881
+ }, [data, expandedSet]);
882
+ const handleKeyDown = (e) => {
883
+ if (visibleList.length === 0) return;
884
+ const currentIdx = focusedId ? visibleList.findIndex((f) => f.item.id === focusedId) : 0;
885
+ const currentFlat = visibleList[currentIdx] || visibleList[0];
886
+ if (e.key === "ArrowDown") {
887
+ e.preventDefault();
888
+ const nextIdx = Math.min(visibleList.length - 1, currentIdx + 1);
889
+ focusNode(visibleList[nextIdx].item.id);
890
+ } else if (e.key === "ArrowUp") {
891
+ e.preventDefault();
892
+ const prevIdx = Math.max(0, currentIdx - 1);
893
+ focusNode(visibleList[prevIdx].item.id);
894
+ } else if (e.key === "ArrowRight") {
895
+ e.preventDefault();
896
+ if (currentFlat.hasChildren) {
897
+ if (!expandedSet.has(currentFlat.item.id)) {
898
+ expandNode(currentFlat.item.id);
899
+ } else {
900
+ const nextIdx = currentIdx + 1;
901
+ if (nextIdx < visibleList.length) {
902
+ focusNode(visibleList[nextIdx].item.id);
903
+ }
904
+ }
905
+ }
906
+ } else if (e.key === "ArrowLeft") {
907
+ e.preventDefault();
908
+ if (currentFlat.hasChildren && expandedSet.has(currentFlat.item.id)) {
909
+ collapseNode(currentFlat.item.id);
910
+ } else if (currentFlat.parentId) {
911
+ focusNode(currentFlat.parentId);
912
+ }
913
+ } else if (e.key === "Home") {
914
+ e.preventDefault();
915
+ focusNode(visibleList[0].item.id);
916
+ } else if (e.key === "End") {
917
+ e.preventDefault();
918
+ focusNode(visibleList[visibleList.length - 1].item.id);
919
+ } else if (e.key === "Enter" || e.key === " ") {
920
+ e.preventDefault();
921
+ if (currentFlat) {
922
+ if (currentFlat.hasChildren) {
923
+ toggleExpand(currentFlat.item.id);
924
+ }
925
+ onSelect?.(currentFlat.item);
926
+ }
927
+ }
928
+ };
929
+ const getIcon = (item, hasChildren, isExpanded) => {
930
+ if (item.icon) return item.icon;
931
+ if (hasChildren) {
932
+ return isExpanded ? /* @__PURE__ */ jsx(FolderOpen, { className: "w-4 h-4 text-amber-500 shrink-0" }) : /* @__PURE__ */ jsx(Folder, { className: "w-4 h-4 text-amber-500 shrink-0" });
933
+ }
934
+ if (item.label.endsWith(".tsx") || item.label.endsWith(".ts") || item.label.endsWith(".js")) {
935
+ return /* @__PURE__ */ jsx(FileCode, { className: "w-4 h-4 text-blue-500 shrink-0" });
936
+ }
937
+ if (item.label.endsWith(".png") || item.label.endsWith(".jpg") || item.label.endsWith(".svg")) {
938
+ return /* @__PURE__ */ jsx(Image, { className: "w-4 h-4 text-purple-500 shrink-0" });
939
+ }
940
+ return /* @__PURE__ */ jsx(FileText, { className: "w-4 h-4 text-[var(--ui-text-muted)] shrink-0" });
941
+ };
942
+ const renderNodes = (items, level) => {
943
+ return items.map((item, index) => {
944
+ const hasChildren = Boolean(item.children && item.children.length > 0);
945
+ const isExpanded = expandedSet.has(item.id);
946
+ const isSelected = selectedId === item.id;
947
+ const isFocused = focusedId === item.id || !focusedId && level === 0 && index === 0;
948
+ return /* @__PURE__ */ jsxs("div", { role: "none", className: "select-none text-xs", children: [
949
+ /* @__PURE__ */ jsxs(
950
+ "div",
724
951
  {
952
+ ref: (el) => {
953
+ if (el) itemRefs.current.set(item.id, el);
954
+ else itemRefs.current.delete(item.id);
955
+ },
956
+ role: "treeitem",
957
+ tabIndex: isFocused ? 0 : -1,
958
+ "aria-expanded": hasChildren ? isExpanded : void 0,
959
+ "aria-selected": isSelected,
960
+ "aria-level": level + 1,
961
+ "aria-posinset": index + 1,
962
+ "aria-setsize": items.length,
963
+ onClick: (e) => {
964
+ e.stopPropagation();
965
+ focusNode(item.id);
966
+ if (hasChildren) {
967
+ toggleExpand(item.id);
968
+ }
969
+ onSelect?.(item);
970
+ },
971
+ style: { paddingLeft: `${level * 16 + 8}px` },
725
972
  className: cn(
726
- "px-1.5 py-0.5 rounded-md text-[10px] font-mono",
727
- isSelected ? "bg-white/20 text-white" : "bg-[var(--ui-bg-secondary)] text-[var(--ui-text-muted)]"
973
+ "flex items-center justify-between py-1.5 pr-2.5 rounded-xl cursor-pointer transition-colors group outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)]",
974
+ isSelected ? "bg-[var(--ui-primary)] text-white font-semibold shadow-xs" : "text-[var(--ui-text-primary)] hover:bg-[var(--ui-bg-tertiary)]"
728
975
  ),
729
- children: item.badge
976
+ children: [
977
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 truncate", children: [
978
+ hasChildren ? /* @__PURE__ */ jsx("span", { className: cn("p-0.5 rounded transition-transform duration-200", isExpanded && "rotate-90"), children: /* @__PURE__ */ jsx(ChevronRight, { className: cn("w-3 h-3", isSelected ? "text-white" : "text-[var(--ui-text-muted)]") }) }) : /* @__PURE__ */ jsx("span", { className: "w-4" }),
979
+ getIcon(item, hasChildren, isExpanded),
980
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: item.label })
981
+ ] }),
982
+ item.badge !== void 0 && /* @__PURE__ */ jsx(
983
+ "span",
984
+ {
985
+ className: cn(
986
+ "px-1.5 py-0.5 rounded-md text-[10px] font-mono",
987
+ isSelected ? "bg-white/20 text-white" : "bg-[var(--ui-bg-secondary)] text-[var(--ui-text-muted)]"
988
+ ),
989
+ children: item.badge
990
+ }
991
+ )
992
+ ]
730
993
  }
731
- )
732
- ]
733
- }
734
- ),
735
- hasChildren && isExpanded && /* @__PURE__ */ jsxs("div", { className: "relative space-y-0.5 animate-in fade-in duration-150", children: [
736
- /* @__PURE__ */ jsx(
737
- "div",
738
- {
739
- className: "absolute left-[15px] top-0 bottom-0 w-[1px] bg-[var(--ui-border-subtle)]",
740
- style: { left: `${level * 16 + 15}px` }
741
- }
742
- ),
743
- item.children.map((child) => /* @__PURE__ */ jsx(
744
- TreeNode,
745
- {
746
- item: child,
747
- level: level + 1,
748
- selectedId,
749
- onSelect
750
- },
751
- child.id
752
- ))
753
- ] })
754
- ] });
755
- }
756
- var TreeView = forwardRef(
757
- ({ data, selectedId, onSelect, className }, ref) => {
994
+ ),
995
+ hasChildren && isExpanded && /* @__PURE__ */ jsxs("div", { role: "group", className: "relative space-y-0.5 animate-in fade-in duration-150", children: [
996
+ /* @__PURE__ */ jsx(
997
+ "div",
998
+ {
999
+ className: "absolute left-[15px] top-0 bottom-0 w-[1px] bg-[var(--ui-border-subtle)]",
1000
+ style: { left: `${level * 16 + 15}px` }
1001
+ }
1002
+ ),
1003
+ renderNodes(item.children, level + 1)
1004
+ ] })
1005
+ ] }, item.id);
1006
+ });
1007
+ };
758
1008
  return /* @__PURE__ */ jsx(
759
1009
  "div",
760
1010
  {
761
1011
  ref,
1012
+ role: "tree",
1013
+ "aria-label": ariaLabel,
1014
+ onKeyDown: handleKeyDown,
762
1015
  className: cn(
763
- "w-full p-2 rounded-2xl bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] space-y-0.5 shadow-sm overflow-hidden",
1016
+ "w-full p-2 rounded-2xl bg-[var(--ui-bg-surface)] border border-[var(--ui-border-default)] space-y-0.5 shadow-sm overflow-hidden outline-none",
764
1017
  className
765
1018
  ),
766
- children: data.map((item) => /* @__PURE__ */ jsx(
767
- TreeNode,
768
- {
769
- item,
770
- level: 0,
771
- selectedId,
772
- onSelect
773
- },
774
- item.id
775
- ))
1019
+ children: renderNodes(data, 0)
776
1020
  }
777
1021
  );
778
1022
  }
@@ -878,5 +1122,5 @@ var Carousel = forwardRef(
878
1122
  Carousel.displayName = "Carousel";
879
1123
 
880
1124
  export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Avatar, AvatarGroup, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, DataTablePro, StatCard, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Timeline, TreeView, avatarVariants, cardVariants, data_display_exports, statusVariants, useAccordion };
881
- //# sourceMappingURL=chunk-NFVONJUK.js.map
882
- //# sourceMappingURL=chunk-NFVONJUK.js.map
1125
+ //# sourceMappingURL=chunk-W5WNWTQC.js.map
1126
+ //# sourceMappingURL=chunk-W5WNWTQC.js.map