@nextlyhq/ui 0.0.2-alpha.51 → 0.0.2-alpha.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,8 +72,10 @@ as a Tailwind v3 preset from `@nextlyhq/ui/tailwind-preset`.
72
72
  **Display:** `Badge`, `Card`, `Alert`, `Separator`, `Skeleton`, `Progress`
73
73
  **Toggles:** `Checkbox`, `RadioGroup`, `Switch`, `Collapsible`
74
74
  **Layout and disclosure:** `Accordion`, `Avatar`, `Tabs`, `Tooltip`, `Popover`
75
+ **Resizable regions:** `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle`
76
+ **Hierarchies:** `TreeView` (virtualized, keyboard-operable)
75
77
  **Overlays:** `Dialog`, `AlertDialog`, `Sheet`
76
- **Menus and command palette:** `DropdownMenu`, `Select`, `Command`
78
+ **Menus and command palette:** `DropdownMenu`, `ContextMenu`, `Select`, `Command`
77
79
  **Feedback:** `Spinner`, `Toaster` (with `toast()` helper)
78
80
  **Tables:** `Table` primitives, `ResponsiveTable`, `TableSearch`, `TablePagination`, `TableSkeleton`, `TableEmpty`, `TableError`, `TableLoading`
79
81
  **Providers:** `PortalProvider`, `usePortalContainer`
@@ -118,7 +120,8 @@ Tailwind's internal `--tw-*` registrations, and the ancestor classes `dark:` and
118
120
 
119
121
  ### Overlays need a portal container
120
122
 
121
- AlertDialog, Command, Dialog, DropdownMenu, Popover, Select, Sheet and Tooltip render
123
+ AlertDialog, Command, ContextMenu, Dialog, DropdownMenu, Popover, Select, Sheet and
124
+ Tooltip render
122
125
  their overlay through a portal, which defaults to `document.body` — outside the wrapper,
123
126
  where the scoped rules and tokens do not reach. Triggers would look right and the menus
124
127
  they open would not. Point them back inside with `PortalProvider`:
package/dist/index.cjs CHANGED
@@ -74,6 +74,20 @@ __export(index_exports, {
74
74
  CommandList: () => CommandList,
75
75
  CommandSeparator: () => CommandSeparator,
76
76
  CommandShortcut: () => CommandShortcut,
77
+ ContextMenu: () => ContextMenu,
78
+ ContextMenuCheckboxItem: () => ContextMenuCheckboxItem,
79
+ ContextMenuContent: () => ContextMenuContent,
80
+ ContextMenuGroup: () => ContextMenuGroup,
81
+ ContextMenuItem: () => ContextMenuItem,
82
+ ContextMenuLabel: () => ContextMenuLabel,
83
+ ContextMenuRadioGroup: () => ContextMenuRadioGroup,
84
+ ContextMenuRadioItem: () => ContextMenuRadioItem,
85
+ ContextMenuSeparator: () => ContextMenuSeparator,
86
+ ContextMenuShortcut: () => ContextMenuShortcut,
87
+ ContextMenuSub: () => ContextMenuSub,
88
+ ContextMenuSubContent: () => ContextMenuSubContent,
89
+ ContextMenuSubTrigger: () => ContextMenuSubTrigger,
90
+ ContextMenuTrigger: () => ContextMenuTrigger,
77
91
  Dialog: () => Dialog,
78
92
  DialogClose: () => DialogClose,
79
93
  DialogContent: () => DialogContent,
@@ -111,6 +125,9 @@ __export(index_exports, {
111
125
  Progress: () => Progress,
112
126
  RadioGroup: () => RadioGroup,
113
127
  RadioGroupItem: () => RadioGroupItem,
128
+ ResizableHandle: () => ResizableHandle,
129
+ ResizablePanel: () => ResizablePanel,
130
+ ResizablePanelGroup: () => ResizablePanelGroup,
114
131
  Select: () => Select,
115
132
  SelectContent: () => SelectContent,
116
133
  SelectGroup: () => SelectGroup,
@@ -160,6 +177,7 @@ __export(index_exports, {
160
177
  TooltipContent: () => TooltipContent,
161
178
  TooltipProvider: () => TooltipProvider,
162
179
  TooltipTrigger: () => TooltipTrigger,
180
+ TreeView: () => TreeView,
163
181
  alertVariants: () => alertVariants,
164
182
  avatarVariants: () => avatarVariants,
165
183
  badgeVariants: () => badgeVariants,
@@ -1167,10 +1185,12 @@ var DialogOverlay = (0, import_react14.forwardRef)(({ className, ...props }, ref
1167
1185
  ref,
1168
1186
  "data-slot": "dialog-overlay",
1169
1187
  className: cn(
1170
- // A modal scrim: a black wash is the point, so it stays a literal
1171
- // rather than a surface token. Painting it from `background` would
1172
- // make it a white veil in light mode and near-invisible in dark.
1173
- "fixed inset-0 z-50 bg-black/80 backdrop-blur-sm transition-opacity duration-150 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1188
+ // A modal scrim, from the `--nx-overlay` token rather than a surface
1189
+ // token. A black wash is the point — painting it from `background` would
1190
+ // make it a white veil in light mode but the alpha still has to differ
1191
+ // per mode, because what it composites over is white in one and mid-tone
1192
+ // in the other.
1193
+ "fixed inset-0 z-50 bg-overlay backdrop-blur-sm transition-opacity duration-150 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1174
1194
  className
1175
1195
  ),
1176
1196
  ...props
@@ -1287,10 +1307,12 @@ var AlertDialogOverlay = (0, import_react15.forwardRef)(({ className, ...props }
1287
1307
  {
1288
1308
  ref,
1289
1309
  className: cn(
1290
- // A modal scrim: a black wash is the point, so it stays a literal
1291
- // rather than a surface token. Painting it from `background` would
1292
- // make it a white veil in light mode and near-invisible in dark.
1293
- "fixed inset-0 z-50 bg-black/80 backdrop-blur-sm transition-opacity duration-150 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1310
+ // A modal scrim, from the `--nx-overlay` token rather than a surface
1311
+ // token. A black wash is the point — painting it from `background` would
1312
+ // make it a white veil in light mode but the alpha still has to differ
1313
+ // per mode, because what it composites over is white in one and mid-tone
1314
+ // in the other.
1315
+ "fixed inset-0 z-50 bg-overlay backdrop-blur-sm transition-opacity duration-150 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1294
1316
  className
1295
1317
  ),
1296
1318
  ...props
@@ -1712,10 +1734,12 @@ var SheetOverlay = React7.forwardRef(({ className, ...props }, ref) => /* @__PUR
1712
1734
  DialogPrimitive2.Overlay,
1713
1735
  {
1714
1736
  className: cn(
1715
- // A modal scrim: a black wash is the point, so it stays a literal
1716
- // rather than a surface token. Painting it from `background` would
1717
- // make it a white veil in light mode and near-invisible in dark.
1718
- "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm",
1737
+ // A modal scrim, from the `--nx-overlay` token rather than a surface
1738
+ // token. A black wash is the point — painting it from `background` would
1739
+ // make it a white veil in light mode but the alpha still has to differ
1740
+ // per mode, because what it composites over is white in one and mid-tone
1741
+ // in the other.
1742
+ "fixed inset-0 z-50 bg-overlay backdrop-blur-sm",
1719
1743
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
1720
1744
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1721
1745
  className
@@ -1830,10 +1854,12 @@ var CommandDialogOverlay = (0, import_react17.forwardRef)(({ className, ...props
1830
1854
  {
1831
1855
  ref,
1832
1856
  className: cn(
1833
- // A modal scrim: a black wash is the point, so it stays a literal
1834
- // rather than a surface token. Painting it from `background` would
1835
- // make it a white veil in light mode and near-invisible in dark.
1836
- "fixed inset-0 z-[99] bg-black/80 backdrop-blur-sm",
1857
+ // A modal scrim, from the `--nx-overlay` token rather than a surface
1858
+ // token. A black wash is the point — painting it from `background` would
1859
+ // make it a white veil in light mode but the alpha still has to differ
1860
+ // per mode, because what it composites over is white in one and mid-tone
1861
+ // in the other.
1862
+ "fixed inset-0 z-[99] bg-overlay backdrop-blur-sm",
1837
1863
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
1838
1864
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1839
1865
  "transition-opacity duration-200",
@@ -2278,6 +2304,511 @@ var TableSkeleton = ({
2278
2304
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "table-wrapper rounded-md border border-border bg-card overflow-hidden", children: content });
2279
2305
  };
2280
2306
  TableSkeleton.displayName = "TableSkeleton";
2307
+
2308
+ // src/components/context-menu.tsx
2309
+ var ContextMenuPrimitive = __toESM(require("@radix-ui/react-context-menu"), 1);
2310
+ var import_lucide_react12 = require("lucide-react");
2311
+ var React9 = __toESM(require("react"), 1);
2312
+ var import_jsx_runtime34 = require("react/jsx-runtime");
2313
+ var menuItemBase2 = "cursor-pointer transition-colors data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
2314
+ var menuSurface = "z-50 max-h-[var(--radix-context-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 origin-[--radix-context-menu-content-transform-origin]";
2315
+ var ContextMenu = ContextMenuPrimitive.Root;
2316
+ var ContextMenuTrigger = ContextMenuPrimitive.Trigger;
2317
+ var ContextMenuGroup = ContextMenuPrimitive.Group;
2318
+ var ContextMenuSub = ContextMenuPrimitive.Sub;
2319
+ var ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
2320
+ var ContextMenuSubTrigger = React9.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2321
+ ContextMenuPrimitive.SubTrigger,
2322
+ {
2323
+ ref,
2324
+ className: cn(
2325
+ "flex select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-muted [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
2326
+ menuItemBase2,
2327
+ inset && "pl-8",
2328
+ className
2329
+ ),
2330
+ ...props,
2331
+ children: [
2332
+ children,
2333
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.ChevronRight, { className: "ml-auto" })
2334
+ ]
2335
+ }
2336
+ ));
2337
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
2338
+ var ContextMenuSubContent = React9.forwardRef(({ className, ...props }, ref) => {
2339
+ const portalContainer = usePortalContainer();
2340
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2341
+ ContextMenuPrimitive.SubContent,
2342
+ {
2343
+ ref,
2344
+ className: cn(menuSurface, className),
2345
+ ...props
2346
+ }
2347
+ ) });
2348
+ });
2349
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
2350
+ var ContextMenuContent = React9.forwardRef(({ className, ...props }, ref) => {
2351
+ const portalContainer = usePortalContainer();
2352
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2353
+ ContextMenuPrimitive.Content,
2354
+ {
2355
+ ref,
2356
+ className: cn(menuSurface, className),
2357
+ ...props
2358
+ }
2359
+ ) });
2360
+ });
2361
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
2362
+ var ContextMenuItem = React9.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2363
+ ContextMenuPrimitive.Item,
2364
+ {
2365
+ ref,
2366
+ className: cn(
2367
+ "relative flex select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
2368
+ menuItemBase2,
2369
+ inset && "pl-8",
2370
+ className
2371
+ ),
2372
+ ...props
2373
+ }
2374
+ ));
2375
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
2376
+ var ContextMenuCheckboxItem = React9.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2377
+ ContextMenuPrimitive.CheckboxItem,
2378
+ {
2379
+ ref,
2380
+ className: cn(
2381
+ "relative flex select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
2382
+ menuItemBase2,
2383
+ className
2384
+ ),
2385
+ checked,
2386
+ ...props,
2387
+ children: [
2388
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ContextMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Check, { className: "h-4 w-4" }) }) }),
2389
+ children
2390
+ ]
2391
+ }
2392
+ ));
2393
+ ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
2394
+ var ContextMenuRadioItem = React9.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2395
+ ContextMenuPrimitive.RadioItem,
2396
+ {
2397
+ ref,
2398
+ className: cn(
2399
+ "relative flex select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
2400
+ menuItemBase2,
2401
+ className
2402
+ ),
2403
+ ...props,
2404
+ children: [
2405
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ContextMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Circle, { className: "h-2 w-2 fill-current" }) }) }),
2406
+ children
2407
+ ]
2408
+ }
2409
+ ));
2410
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
2411
+ var ContextMenuLabel = React9.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2412
+ ContextMenuPrimitive.Label,
2413
+ {
2414
+ ref,
2415
+ className: cn(
2416
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
2417
+ inset && "pl-8",
2418
+ className
2419
+ ),
2420
+ ...props
2421
+ }
2422
+ ));
2423
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
2424
+ var ContextMenuSeparator = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2425
+ ContextMenuPrimitive.Separator,
2426
+ {
2427
+ ref,
2428
+ className: cn("-mx-1 my-1 h-px bg-border", className),
2429
+ ...props
2430
+ }
2431
+ ));
2432
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
2433
+ var ContextMenuShortcut = ({
2434
+ className,
2435
+ ...props
2436
+ }) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2437
+ "span",
2438
+ {
2439
+ className: cn(
2440
+ "ml-auto text-xs tracking-widest text-muted-foreground",
2441
+ className
2442
+ ),
2443
+ ...props
2444
+ }
2445
+ );
2446
+ ContextMenuShortcut.displayName = "ContextMenuShortcut";
2447
+
2448
+ // src/components/resizable.tsx
2449
+ var import_lucide_react13 = require("lucide-react");
2450
+ var ResizablePrimitive = __toESM(require("react-resizable-panels"), 1);
2451
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2452
+ var ResizablePanelGroup = ({
2453
+ className,
2454
+ ...props
2455
+ }) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2456
+ ResizablePrimitive.Group,
2457
+ {
2458
+ className: cn("h-full w-full", className),
2459
+ ...props
2460
+ }
2461
+ );
2462
+ var ResizablePanel = ResizablePrimitive.Panel;
2463
+ var ResizableHandle = ({
2464
+ withGrip,
2465
+ className,
2466
+ children,
2467
+ ...props
2468
+ }) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2469
+ ResizablePrimitive.Separator,
2470
+ {
2471
+ className: cn(
2472
+ // The drawn line is 1px; the element around it is wider and transparent, so the pointer
2473
+ // target is comfortably larger than what it appears to grab (WCAG 2.5.8).
2474
+ "relative flex items-center justify-center bg-border",
2475
+ // Keyed off `aria-orientation`, which is what the separator actually carries, and read as
2476
+ // the ARIA spec defines it: the attribute describes the SEPARATOR, not the group. A
2477
+ // horizontal group puts its panels side by side, so its separator is a vertical line.
2478
+ "aria-[orientation=vertical]:w-px aria-[orientation=horizontal]:h-px",
2479
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
2480
+ // Reachable without precision pointing, without drawing a heavier divider: the hit area
2481
+ // grows across the line, so a vertical line widens and a horizontal one deepens.
2482
+ "after:absolute after:inset-0",
2483
+ "aria-[orientation=vertical]:after:-inset-x-1 aria-[orientation=horizontal]:after:-inset-y-1",
2484
+ className
2485
+ ),
2486
+ ...props,
2487
+ children: [
2488
+ withGrip ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "z-10 flex h-4 w-3 items-center justify-center rounded-sm border border-border bg-border", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.GripVertical, { className: "h-2.5 w-2.5 text-muted-foreground" }) }) : null,
2489
+ children
2490
+ ]
2491
+ }
2492
+ );
2493
+
2494
+ // src/components/tree-view.tsx
2495
+ var import_react_virtual = require("@tanstack/react-virtual");
2496
+ var import_lucide_react14 = require("lucide-react");
2497
+ var React10 = __toESM(require("react"), 1);
2498
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2499
+ var ROW_HEIGHT = 28;
2500
+ var INDENT_PER_LEVEL = 12;
2501
+ function textOf(node) {
2502
+ if (typeof node.textValue === "string") return node.textValue;
2503
+ return typeof node.label === "string" ? node.label : "";
2504
+ }
2505
+ function flatten(nodes, expanded) {
2506
+ const rows = [];
2507
+ const pending = [{ list: nodes, index: 0, level: 0 }];
2508
+ while (pending.length > 0) {
2509
+ const frame = pending[pending.length - 1];
2510
+ if (frame === void 0 || frame.index >= frame.list.length) {
2511
+ pending.pop();
2512
+ continue;
2513
+ }
2514
+ const node = frame.list[frame.index];
2515
+ const posInSet = frame.index;
2516
+ frame.index += 1;
2517
+ if (node === void 0) continue;
2518
+ const hasChildren = node.children !== void 0;
2519
+ rows.push({
2520
+ node,
2521
+ level: frame.level,
2522
+ setSize: frame.list.length,
2523
+ posInSet,
2524
+ parentId: frame.parentId,
2525
+ hasChildren
2526
+ });
2527
+ if (hasChildren && expanded.has(node.id)) {
2528
+ pending.push({
2529
+ list: node.children ?? [],
2530
+ index: 0,
2531
+ level: frame.level + 1,
2532
+ parentId: node.id
2533
+ });
2534
+ }
2535
+ }
2536
+ return rows;
2537
+ }
2538
+ function useControllable(controlled, fallback) {
2539
+ const [uncontrolled, setUncontrolled] = React10.useState(fallback);
2540
+ return [
2541
+ controlled === void 0 ? uncontrolled : controlled,
2542
+ setUncontrolled
2543
+ ];
2544
+ }
2545
+ var TreeView = React10.forwardRef(
2546
+ ({
2547
+ nodes,
2548
+ expandedIds,
2549
+ defaultExpandedIds,
2550
+ onExpandedChange,
2551
+ selectedId,
2552
+ defaultSelectedId,
2553
+ onSelectedChange,
2554
+ className,
2555
+ "aria-label": ariaLabel,
2556
+ "aria-labelledby": ariaLabelledBy,
2557
+ "aria-describedby": ariaDescribedBy,
2558
+ ...props
2559
+ }, forwardedRef) => {
2560
+ const scrollRef = React10.useRef(null);
2561
+ const attachScroll = React10.useCallback(
2562
+ (node) => {
2563
+ scrollRef.current = node;
2564
+ if (typeof forwardedRef === "function") forwardedRef(node);
2565
+ else if (forwardedRef !== null && forwardedRef !== void 0) {
2566
+ forwardedRef.current = node;
2567
+ }
2568
+ },
2569
+ [forwardedRef]
2570
+ );
2571
+ const [expandedState, setExpandedState] = useControllable(
2572
+ expandedIds === void 0 ? void 0 : [...expandedIds],
2573
+ [...defaultExpandedIds ?? []]
2574
+ );
2575
+ const expanded = React10.useMemo(
2576
+ () => new Set(expandedIds ?? expandedState),
2577
+ [expandedIds, expandedState]
2578
+ );
2579
+ const [selected, setSelected] = useControllable(
2580
+ selectedId === void 0 ? void 0 : selectedId,
2581
+ defaultSelectedId ?? null
2582
+ );
2583
+ const rows = React10.useMemo(
2584
+ () => flatten(nodes, expanded),
2585
+ [nodes, expanded]
2586
+ );
2587
+ const [activeId, setActiveId] = React10.useState(null);
2588
+ const activeIndex = Math.max(
2589
+ 0,
2590
+ rows.findIndex((row) => row.node.id === (activeId ?? selected))
2591
+ );
2592
+ const virtualizer = (0, import_react_virtual.useVirtualizer)({
2593
+ count: rows.length,
2594
+ getScrollElement: () => scrollRef.current,
2595
+ estimateSize: () => ROW_HEIGHT,
2596
+ overscan: 8
2597
+ });
2598
+ const commitExpanded = (next) => {
2599
+ const ids = [...next];
2600
+ if (expandedIds === void 0) setExpandedState(ids);
2601
+ onExpandedChange?.(ids);
2602
+ };
2603
+ const setExpansion = (id, open) => {
2604
+ const next = new Set(expanded);
2605
+ if (open) next.add(id);
2606
+ else next.delete(id);
2607
+ commitExpanded(next);
2608
+ };
2609
+ const choose = (id) => {
2610
+ if (selectedId === void 0) setSelected(id);
2611
+ onSelectedChange?.(id);
2612
+ };
2613
+ const focusRow = (index) => {
2614
+ const row = rows[index];
2615
+ if (row === void 0) return;
2616
+ setActiveId(row.node.id);
2617
+ virtualizer.scrollToIndex(index, { align: "auto" });
2618
+ requestAnimationFrame(() => {
2619
+ const element = scrollRef.current?.querySelector(
2620
+ `[data-tree-index="${index}"]`
2621
+ );
2622
+ element?.focus();
2623
+ });
2624
+ };
2625
+ const step = (from, delta) => {
2626
+ for (let index = from + delta; index >= 0 && index < rows.length; index += delta) {
2627
+ if (rows[index]?.node.disabled !== true) return index;
2628
+ }
2629
+ return from;
2630
+ };
2631
+ const typeahead = React10.useRef({ query: "", at: 0 });
2632
+ const onKeyDown = (event) => {
2633
+ const index = activeIndex;
2634
+ const row = rows[index];
2635
+ if (row === void 0) return;
2636
+ switch (event.key) {
2637
+ case "ArrowDown":
2638
+ event.preventDefault();
2639
+ focusRow(step(index, 1));
2640
+ return;
2641
+ case "ArrowUp":
2642
+ event.preventDefault();
2643
+ focusRow(step(index, -1));
2644
+ return;
2645
+ case "ArrowRight":
2646
+ event.preventDefault();
2647
+ if (row.hasChildren && !expanded.has(row.node.id)) {
2648
+ setExpansion(row.node.id, true);
2649
+ } else if (row.hasChildren) {
2650
+ for (let child = index + 1; child < rows.length && (rows[child]?.level ?? 0) > row.level; child += 1) {
2651
+ if (rows[child]?.parentId === row.node.id && rows[child]?.node.disabled !== true) {
2652
+ focusRow(child);
2653
+ break;
2654
+ }
2655
+ }
2656
+ }
2657
+ return;
2658
+ case "ArrowLeft":
2659
+ event.preventDefault();
2660
+ if (row.hasChildren && expanded.has(row.node.id)) {
2661
+ setExpansion(row.node.id, false);
2662
+ } else if (row.parentId !== void 0) {
2663
+ let ancestor = row.parentId;
2664
+ while (ancestor !== void 0) {
2665
+ const at = rows.findIndex(
2666
+ (candidate) => candidate.node.id === ancestor
2667
+ );
2668
+ if (at < 0) break;
2669
+ if (rows[at]?.node.disabled !== true) {
2670
+ focusRow(at);
2671
+ break;
2672
+ }
2673
+ ancestor = rows[at]?.parentId;
2674
+ }
2675
+ }
2676
+ return;
2677
+ case "Home":
2678
+ event.preventDefault();
2679
+ focusRow(rows[0]?.node.disabled === true ? step(0, 1) : 0);
2680
+ return;
2681
+ case "End": {
2682
+ event.preventDefault();
2683
+ const last = rows.length - 1;
2684
+ focusRow(rows[last]?.node.disabled === true ? step(last, -1) : last);
2685
+ return;
2686
+ }
2687
+ case "Enter":
2688
+ case " ":
2689
+ event.preventDefault();
2690
+ if (row.node.disabled !== true) choose(row.node.id);
2691
+ return;
2692
+ case "*": {
2693
+ event.preventDefault();
2694
+ const next = new Set(expanded);
2695
+ for (const sibling of rows) {
2696
+ if (sibling.parentId === row.parentId && sibling.hasChildren) {
2697
+ next.add(sibling.node.id);
2698
+ }
2699
+ }
2700
+ commitExpanded(next);
2701
+ return;
2702
+ }
2703
+ default:
2704
+ break;
2705
+ }
2706
+ if (event.key.length !== 1 || event.metaKey || event.ctrlKey || event.altKey) {
2707
+ return;
2708
+ }
2709
+ event.preventDefault();
2710
+ const now = Date.now();
2711
+ const state = typeahead.current;
2712
+ state.query = now - state.at > 500 ? event.key : state.query + event.key;
2713
+ state.at = now;
2714
+ const query = state.query.toLowerCase();
2715
+ for (let offset = 1; offset <= rows.length; offset += 1) {
2716
+ const candidate = rows[(index + offset) % rows.length];
2717
+ if (candidate === void 0 || candidate.node.disabled === true)
2718
+ continue;
2719
+ if (textOf(candidate.node).toLowerCase().startsWith(query)) {
2720
+ focusRow(rows.indexOf(candidate));
2721
+ return;
2722
+ }
2723
+ }
2724
+ };
2725
+ const virtualItems = virtualizer.getVirtualItems();
2726
+ const usable = (index) => rows[index]?.node.disabled !== true;
2727
+ const tabStopIndex = virtualItems.some((item) => item.index === activeIndex) && usable(activeIndex) ? activeIndex : virtualItems.find((item) => usable(item.index))?.index ?? -1;
2728
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2729
+ "div",
2730
+ {
2731
+ ref: attachScroll,
2732
+ className: cn("overflow-auto", className),
2733
+ ...props,
2734
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2735
+ "div",
2736
+ {
2737
+ role: "tree",
2738
+ "aria-label": ariaLabel,
2739
+ "aria-labelledby": ariaLabelledBy,
2740
+ "aria-describedby": ariaDescribedBy,
2741
+ onKeyDown,
2742
+ style: { height: virtualizer.getTotalSize(), position: "relative" },
2743
+ children: virtualItems.map((item) => {
2744
+ const row = rows[item.index];
2745
+ if (row === void 0) return null;
2746
+ const isSelected = selected === row.node.id;
2747
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2748
+ "div",
2749
+ {
2750
+ "data-tree-index": item.index,
2751
+ role: "treeitem",
2752
+ "aria-level": row.level + 1,
2753
+ "aria-setsize": row.setSize,
2754
+ "aria-posinset": row.posInSet + 1,
2755
+ "aria-selected": isSelected,
2756
+ "aria-expanded": row.hasChildren ? expanded.has(row.node.id) : void 0,
2757
+ "aria-disabled": row.node.disabled === true ? true : void 0,
2758
+ tabIndex: item.index === tabStopIndex ? 0 : -1,
2759
+ onFocus: () => setActiveId(row.node.id),
2760
+ onClick: () => {
2761
+ if (row.node.disabled === true) return;
2762
+ setActiveId(row.node.id);
2763
+ choose(row.node.id);
2764
+ },
2765
+ className: cn(
2766
+ "absolute left-0 flex w-full select-none items-center gap-1 rounded-sm pr-2 text-sm outline-none",
2767
+ "focus-visible:ring-1 focus-visible:ring-ring",
2768
+ row.node.disabled === true ? "pointer-events-none opacity-50" : "cursor-pointer",
2769
+ isSelected ? "bg-muted text-foreground" : "hover:bg-muted/50"
2770
+ ),
2771
+ style: {
2772
+ height: item.size,
2773
+ transform: `translateY(${item.start}px)`,
2774
+ paddingLeft: 4 + row.level * INDENT_PER_LEVEL
2775
+ },
2776
+ children: [
2777
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2778
+ "span",
2779
+ {
2780
+ "aria-hidden": "true",
2781
+ className: "flex size-4 shrink-0 items-center justify-center",
2782
+ onClick: (event) => {
2783
+ if (!row.hasChildren) return;
2784
+ event.stopPropagation();
2785
+ setExpansion(row.node.id, !expanded.has(row.node.id));
2786
+ },
2787
+ children: row.hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2788
+ import_lucide_react14.ChevronRight,
2789
+ {
2790
+ className: cn(
2791
+ "size-3.5 text-muted-foreground transition-transform",
2792
+ expanded.has(row.node.id) && "rotate-90"
2793
+ )
2794
+ }
2795
+ ) : null
2796
+ }
2797
+ ),
2798
+ row.node.icon !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "flex size-4 shrink-0 items-center justify-center text-muted-foreground", children: row.node.icon }) : null,
2799
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "truncate", children: row.node.label })
2800
+ ]
2801
+ },
2802
+ row.node.id
2803
+ );
2804
+ })
2805
+ }
2806
+ )
2807
+ }
2808
+ );
2809
+ }
2810
+ );
2811
+ TreeView.displayName = "TreeView";
2281
2812
  // Annotate the CommonJS export names for ESM import in node:
2282
2813
  0 && (module.exports = {
2283
2814
  Accordion,
@@ -2323,6 +2854,20 @@ TableSkeleton.displayName = "TableSkeleton";
2323
2854
  CommandList,
2324
2855
  CommandSeparator,
2325
2856
  CommandShortcut,
2857
+ ContextMenu,
2858
+ ContextMenuCheckboxItem,
2859
+ ContextMenuContent,
2860
+ ContextMenuGroup,
2861
+ ContextMenuItem,
2862
+ ContextMenuLabel,
2863
+ ContextMenuRadioGroup,
2864
+ ContextMenuRadioItem,
2865
+ ContextMenuSeparator,
2866
+ ContextMenuShortcut,
2867
+ ContextMenuSub,
2868
+ ContextMenuSubContent,
2869
+ ContextMenuSubTrigger,
2870
+ ContextMenuTrigger,
2326
2871
  Dialog,
2327
2872
  DialogClose,
2328
2873
  DialogContent,
@@ -2360,6 +2905,9 @@ TableSkeleton.displayName = "TableSkeleton";
2360
2905
  Progress,
2361
2906
  RadioGroup,
2362
2907
  RadioGroupItem,
2908
+ ResizableHandle,
2909
+ ResizablePanel,
2910
+ ResizablePanelGroup,
2363
2911
  Select,
2364
2912
  SelectContent,
2365
2913
  SelectGroup,
@@ -2409,6 +2957,7 @@ TableSkeleton.displayName = "TableSkeleton";
2409
2957
  TooltipContent,
2410
2958
  TooltipProvider,
2411
2959
  TooltipTrigger,
2960
+ TreeView,
2412
2961
  alertVariants,
2413
2962
  avatarVariants,
2414
2963
  badgeVariants,