@nextlyhq/ui 0.0.2-alpha.60 → 0.0.2-alpha.64

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/dist/index.mjs CHANGED
@@ -998,15 +998,23 @@ var tabsListVariants = cva8(
998
998
  defaultVariants: { variant: "default" }
999
999
  }
1000
1000
  );
1001
- var TabsList = forwardRef15(({ className, variant, ...props }, ref) => /* @__PURE__ */ jsx20(
1002
- List,
1003
- {
1004
- ref,
1005
- "data-slot": "tabs-list",
1006
- className: cn(tabsListVariants({ variant }), className),
1007
- ...props
1008
- }
1009
- ));
1001
+ var TabsList = forwardRef15(({ className, variant, scrollable = false, ...props }, ref) => {
1002
+ const list = /* @__PURE__ */ jsx20(
1003
+ List,
1004
+ {
1005
+ ref,
1006
+ "data-slot": "tabs-list",
1007
+ className: cn(
1008
+ tabsListVariants({ variant }),
1009
+ scrollable && "w-max min-w-full",
1010
+ className
1011
+ ),
1012
+ ...props
1013
+ }
1014
+ );
1015
+ if (!scrollable) return list;
1016
+ return /* @__PURE__ */ jsx20("div", { "data-slot": "tabs-list-scroller", className: "w-full overflow-x-auto", children: list });
1017
+ });
1010
1018
  TabsList.displayName = List.displayName;
1011
1019
  var TABS_TRIGGER_SIZES = {
1012
1020
  default: "text-sm",
@@ -1657,6 +1665,7 @@ var SheetPortal = DialogPrimitive2.Portal;
1657
1665
  var SheetOverlay = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx26(
1658
1666
  DialogPrimitive2.Overlay,
1659
1667
  {
1668
+ "data-slot": "sheet-overlay",
1660
1669
  className: cn(
1661
1670
  // A modal scrim, from the `--nx-overlay` token rather than a surface
1662
1671
  // token. A black wash is the point — painting it from `background` would
@@ -1756,7 +1765,11 @@ SheetDescription.displayName = DialogPrimitive2.Description.displayName;
1756
1765
 
1757
1766
  // src/components/command.tsx
1758
1767
  import * as DialogPrimitive3 from "@radix-ui/react-dialog";
1759
- import { Command as CommandPrimitive, defaultFilter } from "cmdk";
1768
+ import {
1769
+ Command as CommandPrimitive,
1770
+ defaultFilter,
1771
+ useCommandState as useCommandStatePrimitive
1772
+ } from "cmdk";
1760
1773
  import { Search } from "lucide-react";
1761
1774
  import { forwardRef as forwardRef22 } from "react";
1762
1775
  import { jsx as jsx27, jsxs as jsxs9 } from "react/jsx-runtime";
@@ -1983,6 +1996,7 @@ var CommandShortcut = forwardRef22(
1983
1996
  );
1984
1997
  CommandShortcut.displayName = "CommandShortcut";
1985
1998
  var commandDefaultFilter = defaultFilter;
1999
+ var useCommandHighlight = () => useCommandStatePrimitive((state) => state.value);
1986
2000
 
1987
2001
  // src/components/spinner.tsx
1988
2002
  import { cva as cva12 } from "class-variance-authority";
@@ -2257,7 +2271,7 @@ TableSkeleton.displayName = "TableSkeleton";
2257
2271
 
2258
2272
  // src/components/color-picker.tsx
2259
2273
  import { Pipette } from "lucide-react";
2260
- import * as React9 from "react";
2274
+ import * as React10 from "react";
2261
2275
 
2262
2276
  // src/lib/color/convert.ts
2263
2277
  var clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
@@ -2362,6 +2376,10 @@ function hueSliderValue(hue, max) {
2362
2376
  return step > max ? max : step;
2363
2377
  }
2364
2378
 
2379
+ // src/lib/isomorphic-layout-effect.ts
2380
+ import * as React9 from "react";
2381
+ var useIsomorphicLayoutEffect = typeof document === "undefined" ? React9.useEffect : React9.useLayoutEffect;
2382
+
2365
2383
  // src/components/color-picker.tsx
2366
2384
  import { Fragment as Fragment2, jsx as jsx34, jsxs as jsxs13 } from "react/jsx-runtime";
2367
2385
  function toHsva(hex) {
@@ -2383,6 +2401,9 @@ var CHECKERBOARD = "repeating-conic-gradient(#c8c8c8 0% 25%, #ffffff 0% 50%)";
2383
2401
  function eyeDropperSupported() {
2384
2402
  return typeof window !== "undefined" && "EyeDropper" in window;
2385
2403
  }
2404
+ function isNode(value) {
2405
+ return value !== null && typeof value.nodeType === "number";
2406
+ }
2386
2407
  function ColorPicker({
2387
2408
  color,
2388
2409
  onColorChange,
@@ -2392,20 +2413,100 @@ function ColorPicker({
2392
2413
  showAlpha = false,
2393
2414
  className
2394
2415
  }) {
2395
- const fieldId = React9.useId();
2396
- const surfaceRef = React9.useRef(null);
2397
- const [hsva, setHsva] = React9.useState(() => toHsva(color));
2398
- const [draftHex, setDraftHex] = React9.useState(null);
2416
+ const fieldId = React10.useId();
2417
+ const rootRef = React10.useRef(null);
2418
+ const insidePress = React10.useRef(false);
2419
+ const owedFinish = React10.useRef(false);
2420
+ const pendingTap = React10.useRef(null);
2421
+ const latestCommit = React10.useRef(() => void 0);
2422
+ const surfaceRef = React10.useRef(null);
2423
+ const [hsva, setHsva] = React10.useState(() => toHsva(color));
2424
+ const [draftHex, setDraftHex] = React10.useState(null);
2425
+ const draftRef = React10.useRef(null);
2399
2426
  const rendered = toHexString(hsva, showAlpha);
2400
- React9.useEffect(() => {
2427
+ useIsomorphicLayoutEffect(() => {
2401
2428
  const incoming = parseHex(color);
2402
2429
  if (incoming && toHex(incoming, showAlpha ? incoming.alpha : 1) !== rendered) {
2430
+ dropDraft();
2403
2431
  setHsva((prev) => hsvaFrom(incoming, incoming.alpha, prev.h));
2404
2432
  }
2405
2433
  }, [color, rendered, showAlpha]);
2434
+ const dropDraft = () => {
2435
+ owedFinish.current = false;
2436
+ draftRef.current = null;
2437
+ setDraftHex(null);
2438
+ };
2439
+ const commitDraft = () => {
2440
+ owedFinish.current = false;
2441
+ const text = draftRef.current;
2442
+ draftRef.current = null;
2443
+ if (text === null) return;
2444
+ setDraftHex(null);
2445
+ const parsed = parseHex(text);
2446
+ if (!parsed) return;
2447
+ setHsva((prev) => hsvaFrom(parsed, parsed.alpha, prev.h));
2448
+ onColorChange(toHex(parsed, showAlpha ? parsed.alpha : 1));
2449
+ };
2450
+ useIsomorphicLayoutEffect(() => {
2451
+ const root = rootRef.current;
2452
+ if (root === null) return;
2453
+ latestCommit.current = commitDraft;
2454
+ const onPointerDown = (event) => {
2455
+ pendingTap.current?.();
2456
+ pendingTap.current = null;
2457
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
2458
+ const inside = path.includes(root) || isNode(event.target) && root.contains(event.target);
2459
+ insidePress.current = true;
2460
+ if (inside) return;
2461
+ const pointerType = event.pointerType;
2462
+ if (pointerType === "touch") {
2463
+ const onClick = () => {
2464
+ owner.removeEventListener("click", onClick, true);
2465
+ pendingTap.current = null;
2466
+ latestCommit.current();
2467
+ };
2468
+ owner.addEventListener("click", onClick, true);
2469
+ pendingTap.current = () => {
2470
+ owner.removeEventListener("click", onClick, true);
2471
+ };
2472
+ return;
2473
+ }
2474
+ commitDraft();
2475
+ };
2476
+ const owner = root.ownerDocument;
2477
+ const onPointerCancel = () => {
2478
+ insidePress.current = false;
2479
+ pendingTap.current?.();
2480
+ pendingTap.current = null;
2481
+ };
2482
+ const onPointerUp = () => {
2483
+ insidePress.current = false;
2484
+ };
2485
+ const onClickResolve = () => {
2486
+ if (!owedFinish.current) return;
2487
+ latestCommit.current();
2488
+ };
2489
+ owner.addEventListener("pointerdown", onPointerDown, true);
2490
+ owner.addEventListener("pointercancel", onPointerCancel, true);
2491
+ owner.addEventListener("pointerup", onPointerUp, true);
2492
+ owner.addEventListener("click", onClickResolve, false);
2493
+ return () => {
2494
+ owner.removeEventListener("pointerdown", onPointerDown, true);
2495
+ owner.removeEventListener("pointercancel", onPointerCancel, true);
2496
+ owner.removeEventListener("pointerup", onPointerUp, true);
2497
+ owner.removeEventListener("click", onClickResolve, false);
2498
+ };
2499
+ });
2500
+ useIsomorphicLayoutEffect(
2501
+ () => () => {
2502
+ pendingTap.current?.();
2503
+ pendingTap.current = null;
2504
+ },
2505
+ []
2506
+ );
2406
2507
  const commit = (next) => {
2407
2508
  setHsva(next);
2408
- setDraftHex(null);
2509
+ dropDraft();
2409
2510
  onColorChange(toHexString(next, showAlpha));
2410
2511
  };
2411
2512
  const trackPointer = (event) => {
@@ -2435,8 +2536,8 @@ function ColorPicker({
2435
2536
  event.preventDefault();
2436
2537
  nudge(move[0], move[1]);
2437
2538
  };
2438
- const [canPickFromScreen, setCanPickFromScreen] = React9.useState(false);
2439
- React9.useEffect(() => {
2539
+ const [canPickFromScreen, setCanPickFromScreen] = React10.useState(false);
2540
+ React10.useEffect(() => {
2440
2541
  setCanPickFromScreen(eyeDropperSupported());
2441
2542
  }, []);
2442
2543
  const handle = surfacePointFor(hsva.s, hsva.v);
@@ -2444,163 +2545,187 @@ function ColorPicker({
2444
2545
  const pickFromScreen = async () => {
2445
2546
  const ctor = window.EyeDropper;
2446
2547
  if (!ctor) return;
2548
+ const owed = owedFinish.current;
2549
+ owedFinish.current = false;
2447
2550
  let sampled;
2448
2551
  try {
2449
2552
  sampled = (await new ctor().open()).sRGBHex;
2450
2553
  } catch {
2554
+ if (owed) commitDraft();
2451
2555
  return;
2452
2556
  }
2453
2557
  const parsed = parseHex(sampled);
2454
2558
  if (parsed) commit(hsvaFrom(parsed, hsva.a, hsva.h));
2455
2559
  };
2456
- return /* @__PURE__ */ jsxs13("div", { className: cn("w-64 space-y-3", className), children: [
2457
- /* @__PURE__ */ jsx34(
2458
- "div",
2459
- {
2460
- ref: surfaceRef,
2461
- role: "application",
2462
- tabIndex: 0,
2463
- "aria-label": `Saturation and brightness: ${Math.round(hsva.s * 100)}% saturation, ${Math.round(hsva.v * 100)}% brightness. Arrow keys adjust.`,
2464
- className: "ring-offset-background focus-visible:ring-ring relative h-40 w-full cursor-crosshair touch-none rounded-md focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
2465
- style: {
2466
- backgroundColor: hueOnly,
2467
- // Value on TOP of saturation. CSS paints the first layer nearest the
2468
- // viewer, so the reverse order lets the opaque white end of the
2469
- // saturation ramp cover the black end of the value ramp: the
2470
- // bottom-left corner displays white while selecting black.
2471
- backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)"
2472
- },
2473
- onKeyDown: handleSurfaceKey,
2474
- onPointerDown: (event) => {
2475
- event.currentTarget.setPointerCapture(event.pointerId);
2476
- trackPointer(event);
2477
- },
2478
- onPointerMove: (event) => {
2479
- if (event.buttons === 1) trackPointer(event);
2480
- },
2481
- children: /* @__PURE__ */ jsx34(
2482
- "span",
2483
- {
2484
- "aria-hidden": "true",
2485
- className: "pointer-events-none absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-sm ring-1 ring-black/60",
2486
- style: { left: `${handle.x * 100}%`, top: `${handle.y * 100}%` }
2487
- }
2488
- )
2489
- }
2490
- ),
2491
- /* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-hue`, children: "Hue" }),
2492
- /* @__PURE__ */ jsx34(
2493
- "input",
2494
- {
2495
- id: `${fieldId}-hue`,
2496
- type: "range",
2497
- min: 0,
2498
- max: HUE_MAX,
2499
- step: 1,
2500
- value: hueSliderValue(hsva.h, HUE_MAX),
2501
- onChange: (event) => commit({ ...hsva, h: hueAt(+event.target.value / (HUE_MAX + 1)) }),
2502
- className: SLIDER,
2503
- style: {
2504
- backgroundImage: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"
2560
+ return /* @__PURE__ */ jsxs13(
2561
+ "div",
2562
+ {
2563
+ ref: rootRef,
2564
+ className: cn("w-64 space-y-3", className),
2565
+ onBlur: (event) => {
2566
+ const next = event.relatedTarget;
2567
+ const root = rootRef.current;
2568
+ if (root !== null && isNode(next) && root.contains(next)) return;
2569
+ if (next === null && insidePress.current) {
2570
+ owedFinish.current = true;
2571
+ return;
2505
2572
  }
2506
- }
2507
- ),
2508
- showAlpha && /* @__PURE__ */ jsxs13(Fragment2, { children: [
2509
- /* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-alpha`, children: "Opacity" }),
2510
- /* @__PURE__ */ jsx34(
2511
- "input",
2512
- {
2513
- id: `${fieldId}-alpha`,
2514
- type: "range",
2515
- min: 0,
2516
- max: 100,
2517
- step: 1,
2518
- value: Math.round(hsva.a * 100),
2519
- onChange: (event) => commit({ ...hsva, a: +event.target.value / 100 }),
2520
- className: SLIDER,
2521
- style: {
2522
- // The ramp runs to the colour being edited, over a chequerboard,
2523
- // so the track shows what the slider actually controls. Without
2524
- // any background this rendered as a blank 12px strip whose only
2525
- // label was screen-reader-only.
2526
- backgroundImage: `linear-gradient(to right, transparent, ${toHexString({ ...hsva, a: 1 }, false)}), ${CHECKERBOARD}`,
2527
- backgroundSize: "100% 100%, 8px 8px"
2573
+ commitDraft();
2574
+ },
2575
+ children: [
2576
+ /* @__PURE__ */ jsx34(
2577
+ "div",
2578
+ {
2579
+ ref: surfaceRef,
2580
+ role: "application",
2581
+ tabIndex: 0,
2582
+ "aria-label": `Saturation and brightness: ${Math.round(hsva.s * 100)}% saturation, ${Math.round(hsva.v * 100)}% brightness. Arrow keys adjust.`,
2583
+ className: "ring-offset-background focus-visible:ring-ring relative h-40 w-full cursor-crosshair touch-none rounded-md focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
2584
+ style: {
2585
+ backgroundColor: hueOnly,
2586
+ // Value on TOP of saturation. CSS paints the first layer nearest the
2587
+ // viewer, so the reverse order lets the opaque white end of the
2588
+ // saturation ramp cover the black end of the value ramp: the
2589
+ // bottom-left corner displays white while selecting black.
2590
+ backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)"
2591
+ },
2592
+ onKeyDown: handleSurfaceKey,
2593
+ onPointerDown: (event) => {
2594
+ event.currentTarget.setPointerCapture(event.pointerId);
2595
+ trackPointer(event);
2596
+ },
2597
+ onPointerMove: (event) => {
2598
+ if (event.buttons === 1) trackPointer(event);
2599
+ },
2600
+ children: /* @__PURE__ */ jsx34(
2601
+ "span",
2602
+ {
2603
+ "aria-hidden": "true",
2604
+ className: "pointer-events-none absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-sm ring-1 ring-black/60",
2605
+ style: { left: `${handle.x * 100}%`, top: `${handle.y * 100}%` }
2606
+ }
2607
+ )
2528
2608
  }
2529
- }
2530
- )
2531
- ] }),
2532
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
2533
- /* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-hex`, children: "Hex colour" }),
2534
- /* @__PURE__ */ jsx34(
2535
- Input,
2536
- {
2537
- id: `${fieldId}-hex`,
2538
- className: "font-mono",
2539
- value: draftHex ?? rendered,
2540
- onChange: (event) => {
2541
- const text = event.target.value;
2542
- setDraftHex(text);
2543
- const parsed = parseHex(text);
2544
- if (parsed) {
2545
- setHsva(hsvaFrom(parsed, parsed.alpha, hsva.h));
2546
- onColorChange(toHex(parsed, showAlpha ? parsed.alpha : 1));
2609
+ ),
2610
+ /* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-hue`, children: "Hue" }),
2611
+ /* @__PURE__ */ jsx34(
2612
+ "input",
2613
+ {
2614
+ id: `${fieldId}-hue`,
2615
+ type: "range",
2616
+ min: 0,
2617
+ max: HUE_MAX,
2618
+ step: 1,
2619
+ value: hueSliderValue(hsva.h, HUE_MAX),
2620
+ onChange: (event) => commit({ ...hsva, h: hueAt(+event.target.value / (HUE_MAX + 1)) }),
2621
+ className: SLIDER,
2622
+ style: {
2623
+ backgroundImage: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"
2547
2624
  }
2548
- },
2549
- onBlur: () => setDraftHex(null)
2550
- }
2551
- ),
2552
- canPickFromScreen && /* @__PURE__ */ jsx34(
2553
- Button,
2554
- {
2555
- type: "button",
2556
- variant: "outline",
2557
- size: "icon",
2558
- "aria-label": "Pick a colour from the screen",
2559
- onClick: () => void pickFromScreen(),
2560
- children: /* @__PURE__ */ jsx34(Pipette, { className: "size-4" })
2561
- }
2562
- )
2563
- ] }),
2564
- swatches.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
2565
- /* @__PURE__ */ jsx34("p", { className: "text-muted-foreground mb-1 text-xs", children: "Presets" }),
2566
- /* @__PURE__ */ jsx34("div", { className: "flex flex-wrap gap-1", children: swatches.map((swatch) => /* @__PURE__ */ jsx34(
2567
- "button",
2568
- {
2569
- type: "button",
2570
- title: swatch.label,
2571
- "aria-label": swatch.label,
2572
- className: "size-6 rounded border shadow-sm",
2573
- style: { backgroundColor: swatch.color },
2574
- onClick: () => onSwatchSelect?.(swatch)
2575
- },
2576
- swatch.id
2577
- )) })
2578
- ] }),
2579
- recentColors.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
2580
- /* @__PURE__ */ jsx34("p", { className: "text-muted-foreground mb-1 text-xs", children: "Recent" }),
2581
- /* @__PURE__ */ jsx34("div", { className: "flex flex-wrap gap-1", children: recentColors.map((recent) => /* @__PURE__ */ jsx34(
2582
- "button",
2583
- {
2584
- type: "button",
2585
- title: recent,
2586
- "aria-label": recent,
2587
- className: "size-6 rounded border shadow-sm",
2588
- style: { backgroundColor: recent },
2589
- onClick: () => {
2590
- const parsed = parseHex(recent);
2591
- if (parsed) commit(hsvaFrom(parsed, parsed.alpha, hsva.h));
2592
2625
  }
2593
- },
2594
- recent
2595
- )) })
2596
- ] })
2597
- ] });
2626
+ ),
2627
+ showAlpha && /* @__PURE__ */ jsxs13(Fragment2, { children: [
2628
+ /* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-alpha`, children: "Opacity" }),
2629
+ /* @__PURE__ */ jsx34(
2630
+ "input",
2631
+ {
2632
+ id: `${fieldId}-alpha`,
2633
+ type: "range",
2634
+ min: 0,
2635
+ max: 100,
2636
+ step: 1,
2637
+ value: Math.round(hsva.a * 100),
2638
+ onChange: (event) => commit({ ...hsva, a: +event.target.value / 100 }),
2639
+ className: SLIDER,
2640
+ style: {
2641
+ // The ramp runs to the colour being edited, over a chequerboard,
2642
+ // so the track shows what the slider actually controls. Without
2643
+ // any background this rendered as a blank 12px strip whose only
2644
+ // label was screen-reader-only.
2645
+ backgroundImage: `linear-gradient(to right, transparent, ${toHexString({ ...hsva, a: 1 }, false)}), ${CHECKERBOARD}`,
2646
+ backgroundSize: "100% 100%, 8px 8px"
2647
+ }
2648
+ }
2649
+ )
2650
+ ] }),
2651
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
2652
+ /* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-hex`, children: "Hex colour" }),
2653
+ /* @__PURE__ */ jsx34(
2654
+ Input,
2655
+ {
2656
+ id: `${fieldId}-hex`,
2657
+ className: "font-mono",
2658
+ value: draftHex ?? rendered,
2659
+ onChange: (event) => {
2660
+ draftRef.current = event.target.value;
2661
+ setDraftHex(event.target.value);
2662
+ },
2663
+ onKeyDown: (event) => {
2664
+ if (event.key !== "Enter") return;
2665
+ if (event.nativeEvent.isComposing || event.keyCode === 229) return;
2666
+ event.preventDefault();
2667
+ commitDraft();
2668
+ }
2669
+ }
2670
+ ),
2671
+ canPickFromScreen && /* @__PURE__ */ jsx34(
2672
+ Button,
2673
+ {
2674
+ type: "button",
2675
+ variant: "outline",
2676
+ size: "icon",
2677
+ "aria-label": "Pick a colour from the screen",
2678
+ onClick: () => void pickFromScreen(),
2679
+ children: /* @__PURE__ */ jsx34(Pipette, { className: "size-4" })
2680
+ }
2681
+ )
2682
+ ] }),
2683
+ swatches.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
2684
+ /* @__PURE__ */ jsx34("p", { className: "text-muted-foreground mb-1 text-xs", children: "Presets" }),
2685
+ /* @__PURE__ */ jsx34("div", { className: "flex flex-wrap gap-1", children: swatches.map((swatch) => /* @__PURE__ */ jsx34(
2686
+ "button",
2687
+ {
2688
+ type: "button",
2689
+ title: swatch.label,
2690
+ "aria-label": swatch.label,
2691
+ className: "size-6 rounded border shadow-sm",
2692
+ style: { backgroundColor: swatch.color },
2693
+ onClick: () => {
2694
+ if (onSwatchSelect === void 0) return;
2695
+ dropDraft();
2696
+ onSwatchSelect(swatch);
2697
+ }
2698
+ },
2699
+ swatch.id
2700
+ )) })
2701
+ ] }),
2702
+ recentColors.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
2703
+ /* @__PURE__ */ jsx34("p", { className: "text-muted-foreground mb-1 text-xs", children: "Recent" }),
2704
+ /* @__PURE__ */ jsx34("div", { className: "flex flex-wrap gap-1", children: recentColors.map((recent) => /* @__PURE__ */ jsx34(
2705
+ "button",
2706
+ {
2707
+ type: "button",
2708
+ title: recent,
2709
+ "aria-label": recent,
2710
+ className: "size-6 rounded border shadow-sm",
2711
+ style: { backgroundColor: recent },
2712
+ onClick: () => {
2713
+ const parsed = parseHex(recent);
2714
+ if (parsed) commit(hsvaFrom(parsed, parsed.alpha, hsva.h));
2715
+ }
2716
+ },
2717
+ recent
2718
+ )) })
2719
+ ] })
2720
+ ]
2721
+ }
2722
+ );
2598
2723
  }
2599
2724
 
2600
2725
  // src/components/context-menu.tsx
2601
2726
  import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
2602
2727
  import { Check as Check4, ChevronRight as ChevronRight2, Circle as Circle2 } from "lucide-react";
2603
- import * as React10 from "react";
2728
+ import * as React11 from "react";
2604
2729
  import { jsx as jsx35, jsxs as jsxs14 } from "react/jsx-runtime";
2605
2730
  var menuItemBase2 = "cursor-pointer transition-colors data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
2606
2731
  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]";
@@ -2609,7 +2734,7 @@ var ContextMenuTrigger = ContextMenuPrimitive.Trigger;
2609
2734
  var ContextMenuGroup = ContextMenuPrimitive.Group;
2610
2735
  var ContextMenuSub = ContextMenuPrimitive.Sub;
2611
2736
  var ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
2612
- var ContextMenuSubTrigger = React10.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs14(
2737
+ var ContextMenuSubTrigger = React11.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs14(
2613
2738
  ContextMenuPrimitive.SubTrigger,
2614
2739
  {
2615
2740
  ref,
@@ -2627,7 +2752,7 @@ var ContextMenuSubTrigger = React10.forwardRef(({ className, inset, children, ..
2627
2752
  }
2628
2753
  ));
2629
2754
  ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
2630
- var ContextMenuSubContent = React10.forwardRef(({ className, ...props }, ref) => {
2755
+ var ContextMenuSubContent = React11.forwardRef(({ className, ...props }, ref) => {
2631
2756
  const portalContainer = usePortalContainer();
2632
2757
  return /* @__PURE__ */ jsx35(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx35(
2633
2758
  ContextMenuPrimitive.SubContent,
@@ -2639,7 +2764,7 @@ var ContextMenuSubContent = React10.forwardRef(({ className, ...props }, ref) =>
2639
2764
  ) });
2640
2765
  });
2641
2766
  ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
2642
- var ContextMenuContent = React10.forwardRef(({ className, ...props }, ref) => {
2767
+ var ContextMenuContent = React11.forwardRef(({ className, ...props }, ref) => {
2643
2768
  const portalContainer = usePortalContainer();
2644
2769
  return /* @__PURE__ */ jsx35(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx35(
2645
2770
  ContextMenuPrimitive.Content,
@@ -2651,7 +2776,7 @@ var ContextMenuContent = React10.forwardRef(({ className, ...props }, ref) => {
2651
2776
  ) });
2652
2777
  });
2653
2778
  ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
2654
- var ContextMenuItem = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx35(
2779
+ var ContextMenuItem = React11.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx35(
2655
2780
  ContextMenuPrimitive.Item,
2656
2781
  {
2657
2782
  ref,
@@ -2665,7 +2790,7 @@ var ContextMenuItem = React10.forwardRef(({ className, inset, ...props }, ref) =
2665
2790
  }
2666
2791
  ));
2667
2792
  ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
2668
- var ContextMenuCheckboxItem = React10.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs14(
2793
+ var ContextMenuCheckboxItem = React11.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs14(
2669
2794
  ContextMenuPrimitive.CheckboxItem,
2670
2795
  {
2671
2796
  ref,
@@ -2683,7 +2808,7 @@ var ContextMenuCheckboxItem = React10.forwardRef(({ className, children, checked
2683
2808
  }
2684
2809
  ));
2685
2810
  ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
2686
- var ContextMenuRadioItem = React10.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs14(
2811
+ var ContextMenuRadioItem = React11.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs14(
2687
2812
  ContextMenuPrimitive.RadioItem,
2688
2813
  {
2689
2814
  ref,
@@ -2700,7 +2825,7 @@ var ContextMenuRadioItem = React10.forwardRef(({ className, children, ...props }
2700
2825
  }
2701
2826
  ));
2702
2827
  ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
2703
- var ContextMenuLabel = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx35(
2828
+ var ContextMenuLabel = React11.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx35(
2704
2829
  ContextMenuPrimitive.Label,
2705
2830
  {
2706
2831
  ref,
@@ -2713,7 +2838,7 @@ var ContextMenuLabel = React10.forwardRef(({ className, inset, ...props }, ref)
2713
2838
  }
2714
2839
  ));
2715
2840
  ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
2716
- var ContextMenuSeparator = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx35(
2841
+ var ContextMenuSeparator = React11.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx35(
2717
2842
  ContextMenuPrimitive.Separator,
2718
2843
  {
2719
2844
  ref,
@@ -2786,7 +2911,7 @@ var ResizableHandle = ({
2786
2911
  // src/components/tree-view.tsx
2787
2912
  import { useVirtualizer } from "@tanstack/react-virtual";
2788
2913
  import { ChevronRight as ChevronRight3 } from "lucide-react";
2789
- import * as React11 from "react";
2914
+ import * as React12 from "react";
2790
2915
  import { jsx as jsx37, jsxs as jsxs16 } from "react/jsx-runtime";
2791
2916
  var ROW_HEIGHT = 28;
2792
2917
  var INDENT_PER_LEVEL = 12;
@@ -2828,19 +2953,28 @@ function flatten(nodes, expanded) {
2828
2953
  return rows;
2829
2954
  }
2830
2955
  function useControllable(controlled, fallback) {
2831
- const [uncontrolled, setUncontrolled] = React11.useState(fallback);
2956
+ const [uncontrolled, setUncontrolled] = React12.useState(fallback);
2832
2957
  return [
2833
2958
  controlled === void 0 ? uncontrolled : controlled,
2834
2959
  setUncontrolled
2835
2960
  ];
2836
2961
  }
2837
- var TreeView = React11.forwardRef(
2962
+ function treeSelectionMode(modifiers) {
2963
+ if (modifiers.shiftKey) return "extend";
2964
+ if (modifiers.metaKey || modifiers.ctrlKey) return "toggle";
2965
+ return "replace";
2966
+ }
2967
+ function isRowSelected(id, primary, selectedIds) {
2968
+ return selectedIds === void 0 ? primary === id : selectedIds.includes(id);
2969
+ }
2970
+ var TreeView = React12.forwardRef(
2838
2971
  ({
2839
2972
  nodes,
2840
2973
  expandedIds,
2841
2974
  defaultExpandedIds,
2842
2975
  onExpandedChange,
2843
2976
  selectedId,
2977
+ selectedIds,
2844
2978
  defaultSelectedId,
2845
2979
  onSelectedChange,
2846
2980
  className,
@@ -2849,8 +2983,8 @@ var TreeView = React11.forwardRef(
2849
2983
  "aria-describedby": ariaDescribedBy,
2850
2984
  ...props
2851
2985
  }, forwardedRef) => {
2852
- const scrollRef = React11.useRef(null);
2853
- const attachScroll = React11.useCallback(
2986
+ const scrollRef = React12.useRef(null);
2987
+ const attachScroll = React12.useCallback(
2854
2988
  (node) => {
2855
2989
  scrollRef.current = node;
2856
2990
  if (typeof forwardedRef === "function") forwardedRef(node);
@@ -2864,7 +2998,7 @@ var TreeView = React11.forwardRef(
2864
2998
  expandedIds === void 0 ? void 0 : [...expandedIds],
2865
2999
  [...defaultExpandedIds ?? []]
2866
3000
  );
2867
- const expanded = React11.useMemo(
3001
+ const expanded = React12.useMemo(
2868
3002
  () => new Set(expandedIds ?? expandedState),
2869
3003
  [expandedIds, expandedState]
2870
3004
  );
@@ -2872,11 +3006,11 @@ var TreeView = React11.forwardRef(
2872
3006
  selectedId === void 0 ? void 0 : selectedId,
2873
3007
  defaultSelectedId ?? null
2874
3008
  );
2875
- const rows = React11.useMemo(
3009
+ const rows = React12.useMemo(
2876
3010
  () => flatten(nodes, expanded),
2877
3011
  [nodes, expanded]
2878
3012
  );
2879
- const [activeId, setActiveId] = React11.useState(null);
3013
+ const [activeId, setActiveId] = React12.useState(null);
2880
3014
  const activeIndex = Math.max(
2881
3015
  0,
2882
3016
  rows.findIndex((row) => row.node.id === (activeId ?? selected))
@@ -2898,9 +3032,9 @@ var TreeView = React11.forwardRef(
2898
3032
  else next.delete(id);
2899
3033
  commitExpanded(next);
2900
3034
  };
2901
- const choose = (id) => {
3035
+ const choose = (id, mode = "replace") => {
2902
3036
  if (selectedId === void 0) setSelected(id);
2903
- onSelectedChange?.(id);
3037
+ onSelectedChange?.(id, mode);
2904
3038
  };
2905
3039
  const focusRow = (index) => {
2906
3040
  const row = rows[index];
@@ -2920,11 +3054,12 @@ var TreeView = React11.forwardRef(
2920
3054
  }
2921
3055
  return from;
2922
3056
  };
2923
- const typeahead = React11.useRef({ query: "", at: 0 });
3057
+ const typeahead = React12.useRef({ query: "", at: 0 });
2924
3058
  const onKeyDown = (event) => {
2925
3059
  const index = activeIndex;
2926
3060
  const row = rows[index];
2927
3061
  if (row === void 0) return;
3062
+ if (event.altKey) return;
2928
3063
  switch (event.key) {
2929
3064
  case "ArrowDown":
2930
3065
  event.preventDefault();
@@ -2979,7 +3114,9 @@ var TreeView = React11.forwardRef(
2979
3114
  case "Enter":
2980
3115
  case " ":
2981
3116
  event.preventDefault();
2982
- if (row.node.disabled !== true) choose(row.node.id);
3117
+ if (row.node.disabled !== true) {
3118
+ choose(row.node.id, treeSelectionMode(event));
3119
+ }
2983
3120
  return;
2984
3121
  case "*": {
2985
3122
  event.preventDefault();
@@ -3027,6 +3164,7 @@ var TreeView = React11.forwardRef(
3027
3164
  "div",
3028
3165
  {
3029
3166
  role: "tree",
3167
+ "aria-multiselectable": selectedIds === void 0 ? void 0 : true,
3030
3168
  "aria-label": ariaLabel,
3031
3169
  "aria-labelledby": ariaLabelledBy,
3032
3170
  "aria-describedby": ariaDescribedBy,
@@ -3035,7 +3173,12 @@ var TreeView = React11.forwardRef(
3035
3173
  children: virtualItems.map((item) => {
3036
3174
  const row = rows[item.index];
3037
3175
  if (row === void 0) return null;
3038
- const isSelected = selected === row.node.id;
3176
+ const isSelected = isRowSelected(
3177
+ row.node.id,
3178
+ selected,
3179
+ selectedIds
3180
+ );
3181
+ const isPrimary = selected === row.node.id;
3039
3182
  return /* @__PURE__ */ jsxs16(
3040
3183
  "div",
3041
3184
  {
@@ -3049,16 +3192,21 @@ var TreeView = React11.forwardRef(
3049
3192
  "aria-disabled": row.node.disabled === true ? true : void 0,
3050
3193
  tabIndex: item.index === tabStopIndex ? 0 : -1,
3051
3194
  onFocus: () => setActiveId(row.node.id),
3052
- onClick: () => {
3195
+ onClick: (event) => {
3053
3196
  if (row.node.disabled === true) return;
3054
3197
  setActiveId(row.node.id);
3055
- choose(row.node.id);
3198
+ choose(row.node.id, treeSelectionMode(event));
3056
3199
  },
3057
3200
  className: cn(
3058
3201
  "absolute left-0 flex w-full select-none items-center gap-1 rounded-sm pr-2 text-sm outline-none",
3059
3202
  "focus-visible:ring-1 focus-visible:ring-ring",
3060
3203
  row.node.disabled === true ? "pointer-events-none opacity-50" : "cursor-pointer",
3061
- isSelected ? "bg-muted text-foreground" : "hover:bg-muted/50"
3204
+ isSelected ? "bg-muted text-foreground" : "hover:bg-muted/50",
3205
+ // WEIGHT, not a second colour. Every row here is selected and
3206
+ // an action applies to all of them; what a reader needs is
3207
+ // which one the detail panel describes, and a second colour
3208
+ // would have to mean a second kind of selected.
3209
+ isSelected && isPrimary && "font-medium"
3062
3210
  ),
3063
3211
  style: {
3064
3212
  height: item.size,
@@ -3104,7 +3252,7 @@ TreeView.displayName = "TreeView";
3104
3252
 
3105
3253
  // src/components/slider.tsx
3106
3254
  import * as SliderPrimitive from "@radix-ui/react-slider";
3107
- import * as React12 from "react";
3255
+ import * as React13 from "react";
3108
3256
 
3109
3257
  // src/lib/dev-warn.ts
3110
3258
  var emitted = /* @__PURE__ */ new Set();
@@ -3130,7 +3278,7 @@ function thumbCount(value, defaultValue) {
3130
3278
  function hasAccessibleName(value) {
3131
3279
  return value !== void 0 && value.trim() !== "";
3132
3280
  }
3133
- var Slider = React12.forwardRef(
3281
+ var Slider = React13.forwardRef(
3134
3282
  ({
3135
3283
  className,
3136
3284
  value,
@@ -3141,7 +3289,7 @@ var Slider = React12.forwardRef(
3141
3289
  "aria-labelledby": ariaLabelledBy,
3142
3290
  ...props
3143
3291
  }, ref) => {
3144
- const initialUncontrolledCount = React12.useRef(
3292
+ const initialUncontrolledCount = React13.useRef(
3145
3293
  thumbCount(void 0, defaultValue)
3146
3294
  ).current;
3147
3295
  const count = value?.length ?? initialUncontrolledCount;
@@ -3255,7 +3403,7 @@ var Slider = React12.forwardRef(
3255
3403
  Slider.displayName = SliderPrimitive.Root.displayName;
3256
3404
 
3257
3405
  // src/lib/shortcuts/react.tsx
3258
- import * as React13 from "react";
3406
+ import * as React14 from "react";
3259
3407
 
3260
3408
  // src/lib/shortcuts/key-spec.ts
3261
3409
  function normalizeKey(key) {
@@ -3784,9 +3932,8 @@ var FIELD_KEYS = /* @__PURE__ */ new Set([
3784
3932
 
3785
3933
  // src/lib/shortcuts/react.tsx
3786
3934
  import { jsx as jsx39 } from "react/jsx-runtime";
3787
- var ShortcutContext = React13.createContext(null);
3935
+ var ShortcutContext = React14.createContext(null);
3788
3936
  var ownersByTarget = /* @__PURE__ */ new WeakMap();
3789
- var useIsomorphicLayoutEffect = typeof document === "undefined" ? React13.useEffect : React13.useLayoutEffect;
3790
3937
  function optionsFingerprint(options) {
3791
3938
  return [
3792
3939
  options.isApple ?? "auto",
@@ -3799,13 +3946,13 @@ function ShortcutProvider({
3799
3946
  target,
3800
3947
  ...managerOptions
3801
3948
  }) {
3802
- const parent = React13.useContext(ShortcutContext);
3949
+ const parent = React14.useContext(ShortcutContext);
3803
3950
  const resolvedTarget = target === null ? null : target ?? (typeof document === "undefined" ? null : document);
3804
3951
  const nestedOnSameTarget = parent !== null && parent.target === resolvedTarget;
3805
- const optionsRef = React13.useRef(managerOptions);
3806
- const ownManagers = React13.useRef(/* @__PURE__ */ new WeakMap());
3807
- const ownDetached = React13.useRef(null);
3808
- const detached = React13.useMemo(() => {
3952
+ const optionsRef = React14.useRef(managerOptions);
3953
+ const ownManagers = React14.useRef(/* @__PURE__ */ new WeakMap());
3954
+ const ownDetached = React14.useRef(null);
3955
+ const detached = React14.useMemo(() => {
3809
3956
  if (resolvedTarget === null) {
3810
3957
  ownDetached.current ??= createShortcutManager(optionsRef.current);
3811
3958
  return ownDetached.current;
@@ -3861,7 +4008,7 @@ function ShortcutProvider({
3861
4008
  };
3862
4009
  }, [resolvedTarget, manager]);
3863
4010
  const depth = nestedOnSameTarget && parent ? parent.depth : 0;
3864
- const value = React13.useMemo(
4011
+ const value = React14.useMemo(
3865
4012
  () => ({ manager, depth, target: resolvedTarget, options: fingerprint }),
3866
4013
  [manager, depth, resolvedTarget, fingerprint]
3867
4014
  );
@@ -3870,11 +4017,11 @@ function ShortcutProvider({
3870
4017
  function ShortcutScope({
3871
4018
  children
3872
4019
  }) {
3873
- const parent = React13.useContext(ShortcutContext);
4020
+ const parent = React14.useContext(ShortcutContext);
3874
4021
  if (!parent) {
3875
4022
  throw new Error("ShortcutScope must be rendered inside a ShortcutProvider");
3876
4023
  }
3877
- const value = React13.useMemo(
4024
+ const value = React14.useMemo(
3878
4025
  () => ({
3879
4026
  manager: parent.manager,
3880
4027
  depth: parent.depth + 1,
@@ -3888,13 +4035,13 @@ function ShortcutScope({
3888
4035
  return /* @__PURE__ */ jsx39(ShortcutContext.Provider, { value, children });
3889
4036
  }
3890
4037
  function useShortcuts(bindings, options) {
3891
- const context = React13.useContext(ShortcutContext);
4038
+ const context = React14.useContext(ShortcutContext);
3892
4039
  if (!context) {
3893
4040
  throw new Error("useShortcuts must be called inside a ShortcutProvider");
3894
4041
  }
3895
4042
  const { manager, depth } = context;
3896
- const registration = React13.useRef(null);
3897
- const latest = React13.useRef({ bindings, options });
4043
+ const registration = React14.useRef(null);
4044
+ const latest = React14.useRef({ bindings, options });
3898
4045
  useIsomorphicLayoutEffect(() => {
3899
4046
  registration.current = manager.register([], {
3900
4047
  name: latest.current.options.name,
@@ -3917,7 +4064,7 @@ function useShortcuts(bindings, options) {
3917
4064
  });
3918
4065
  }
3919
4066
  function useShortcutManager() {
3920
- const context = React13.useContext(ShortcutContext);
4067
+ const context = React14.useContext(ShortcutContext);
3921
4068
  if (!context) {
3922
4069
  throw new Error(
3923
4070
  "useShortcutManager must be called inside a ShortcutProvider"
@@ -3927,7 +4074,7 @@ function useShortcutManager() {
3927
4074
  }
3928
4075
  function useActiveShortcuts() {
3929
4076
  const manager = useShortcutManager();
3930
- return React13.useSyncExternalStore(
4077
+ return React14.useSyncExternalStore(
3931
4078
  manager.subscribe,
3932
4079
  manager.activeBindings,
3933
4080
  manager.activeBindings
@@ -4054,29 +4201,12 @@ function FormSection({
4054
4201
  ),
4055
4202
  description ? /* @__PURE__ */ jsx41("p", { className: "text-sm text-muted-foreground", children: description }) : null
4056
4203
  ] }),
4057
- /* @__PURE__ */ jsx41(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx41("div", { className: "divide-y divide-foreground/10 px-6", children }) })
4204
+ /* @__PURE__ */ jsx41(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx41("div", { className: "nx-form-section-rows divide-y divide-foreground/10 px-6", children }) })
4058
4205
  ] });
4059
4206
  }
4060
4207
 
4061
- // src/components/form-layout.tsx
4062
- import { cva as cva14 } from "class-variance-authority";
4208
+ // src/components/form-actions.tsx
4063
4209
  import { jsx as jsx42, jsxs as jsxs20 } from "react/jsx-runtime";
4064
- var measure = cva14("mx-auto w-full px-6 py-8", {
4065
- variants: {
4066
- width: {
4067
- form: "max-w-[56rem]",
4068
- wide: "max-w-[72rem]"
4069
- }
4070
- },
4071
- defaultVariants: { width: "form" }
4072
- });
4073
- function FormLayout({
4074
- width = "form",
4075
- className,
4076
- children
4077
- }) {
4078
- return /* @__PURE__ */ jsx42("div", { className: cn(measure({ width }), className), children });
4079
- }
4080
4210
  function FormActions({ dirty, className, children }) {
4081
4211
  return /* @__PURE__ */ jsxs20(
4082
4212
  "div",
@@ -4099,6 +4229,127 @@ function FormActions({ dirty, className, children }) {
4099
4229
  }
4100
4230
  );
4101
4231
  }
4232
+
4233
+ // src/components/page-shell.tsx
4234
+ import {
4235
+ createContext as createContext5,
4236
+ forwardRef as forwardRef28,
4237
+ useCallback as useCallback2,
4238
+ useContext as useContext5,
4239
+ useEffect as useEffect4,
4240
+ useRef as useRef5
4241
+ } from "react";
4242
+ import { jsx as jsx43 } from "react/jsx-runtime";
4243
+ function hasBareTextChild(shell) {
4244
+ return Array.from(shell.childNodes).some(
4245
+ (node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() !== ""
4246
+ );
4247
+ }
4248
+ var InsideShell = createContext5(false);
4249
+ function hasDisplayContentsChild(shell) {
4250
+ return Array.from(shell.children).some(
4251
+ (child) => getComputedStyle(child).display === "contents"
4252
+ );
4253
+ }
4254
+ var SHELL_MEASURE = {
4255
+ form: "var(--nx-measure-form)",
4256
+ wide: "var(--nx-measure-wide)",
4257
+ full: "100%"
4258
+ };
4259
+ var PageShell = forwardRef28(
4260
+ ({ width = "form", className, children, style, ...props }, ref) => {
4261
+ const shellRef = useRef5(null);
4262
+ const attachRef = useCallback2(
4263
+ (node) => {
4264
+ shellRef.current = node;
4265
+ if (typeof ref === "function") return ref(node);
4266
+ if (ref) ref.current = node;
4267
+ },
4268
+ [ref]
4269
+ );
4270
+ const nested = useContext5(InsideShell);
4271
+ useEffect4(() => {
4272
+ if (!isDevelopmentRuntime()) return;
4273
+ const shell = shellRef.current;
4274
+ if (!shell) return;
4275
+ devWarnOnce(
4276
+ !nested,
4277
+ "PageShell: an ancestor already renders a PageShell. The two grids each add a pair of gutter tracks, so this page's content is inset twice. Remove this shell, or move the outer one."
4278
+ );
4279
+ const check = () => {
4280
+ devWarnOnce(
4281
+ !hasBareTextChild(shell),
4282
+ "PageShell: a direct child renders as bare text. CSS Grid wraps it in an anonymous grid item, which no selector can reach, so it is placed in the gutter rather than the content column and sits outside the page's measure. Wrap it in an element \u2014 a `<p>`, or the section it belongs to."
4283
+ );
4284
+ devWarnOnce(
4285
+ !hasDisplayContentsChild(shell),
4286
+ "PageShell: a direct child has `display: contents`, so it generates no grid item and the content-column rule has nothing to apply to. Its own children are promoted into this grid, match no selector, and are placed from the gutter onward. Give the child a box, or move `display: contents` inside it."
4287
+ );
4288
+ };
4289
+ check();
4290
+ const observer = new MutationObserver(check);
4291
+ observer.observe(shell, { childList: true });
4292
+ return () => {
4293
+ observer.disconnect();
4294
+ };
4295
+ });
4296
+ const shellStyle = {
4297
+ ...style,
4298
+ "--nx-shell-measure": SHELL_MEASURE[width]
4299
+ };
4300
+ return /* @__PURE__ */ jsx43(InsideShell.Provider, { value: true, children: /* @__PURE__ */ jsx43(
4301
+ "div",
4302
+ {
4303
+ ref: attachRef,
4304
+ "data-slot": "page-shell",
4305
+ style: shellStyle,
4306
+ className: cn("nx-page-shell py-8", className),
4307
+ ...props,
4308
+ children
4309
+ }
4310
+ ) });
4311
+ }
4312
+ );
4313
+ PageShell.displayName = "PageShell";
4314
+ var Bleed = forwardRef28(
4315
+ ({ className, children, ...props }, ref) => /* @__PURE__ */ jsx43(
4316
+ "div",
4317
+ {
4318
+ ref,
4319
+ "data-slot": "bleed",
4320
+ className: cn("nx-bleed", className),
4321
+ ...props,
4322
+ children
4323
+ }
4324
+ )
4325
+ );
4326
+ Bleed.displayName = "Bleed";
4327
+
4328
+ // src/components/page-header.tsx
4329
+ import { forwardRef as forwardRef29 } from "react";
4330
+ import { jsx as jsx44, jsxs as jsxs21 } from "react/jsx-runtime";
4331
+ var PageHeader = forwardRef29(
4332
+ ({ title, description, breadcrumbs, actions, className, ...props }, ref) => /* @__PURE__ */ jsxs21(
4333
+ "header",
4334
+ {
4335
+ ref,
4336
+ "data-slot": "page-header",
4337
+ className: cn("mb-8", className),
4338
+ ...props,
4339
+ children: [
4340
+ breadcrumbs ? /* @__PURE__ */ jsx44("div", { className: "mb-6", children: breadcrumbs }) : null,
4341
+ /* @__PURE__ */ jsxs21("div", { className: "flex flex-col justify-between gap-4 sm:flex-row sm:items-center", children: [
4342
+ /* @__PURE__ */ jsxs21("div", { className: "space-y-1", children: [
4343
+ /* @__PURE__ */ jsx44("h1", { className: "text-xl font-semibold tracking-tight", children: title }),
4344
+ description ? /* @__PURE__ */ jsx44("p", { className: "mt-1 text-sm font-normal text-muted-foreground", children: description }) : null
4345
+ ] }),
4346
+ actions ? /* @__PURE__ */ jsx44("div", { className: "flex items-center gap-3", children: actions }) : null
4347
+ ] })
4348
+ ]
4349
+ }
4350
+ )
4351
+ );
4352
+ PageHeader.displayName = "PageHeader";
4102
4353
  export {
4103
4354
  Accordion,
4104
4355
  AccordionContent,
@@ -4122,6 +4373,7 @@ export {
4122
4373
  AvatarFallback,
4123
4374
  AvatarImage,
4124
4375
  Badge,
4376
+ Bleed,
4125
4377
  Button,
4126
4378
  Card,
4127
4379
  CardAction,
@@ -4186,11 +4438,12 @@ export {
4186
4438
  FieldShell,
4187
4439
  FormActions,
4188
4440
  FormLabelWithTooltip,
4189
- FormLayout,
4190
4441
  FormSection,
4191
4442
  Grid,
4192
4443
  Input,
4193
4444
  Label,
4445
+ PageHeader,
4446
+ PageShell,
4194
4447
  Popover,
4195
4448
  PopoverAnchor,
4196
4449
  PopoverContent,
@@ -4202,6 +4455,7 @@ export {
4202
4455
  ResizableHandle,
4203
4456
  ResizablePanel,
4204
4457
  ResizablePanelGroup,
4458
+ SHELL_MEASURE,
4205
4459
  Select,
4206
4460
  SelectContent,
4207
4461
  SelectGroup,
@@ -4260,8 +4514,10 @@ export {
4260
4514
  badgeVariants,
4261
4515
  buttonVariants,
4262
4516
  cardVariants,
4517
+ chordMatches,
4263
4518
  commandDefaultFilter,
4264
4519
  createShortcutManager,
4520
+ detectApplePlatform,
4265
4521
  dialogContentVariants,
4266
4522
  inputVariants,
4267
4523
  parseKeys,
@@ -4271,6 +4527,8 @@ export {
4271
4527
  spinnerVariants,
4272
4528
  toast,
4273
4529
  useActiveShortcuts,
4530
+ useCommandHighlight,
4531
+ useIsomorphicLayoutEffect,
4274
4532
  usePortalContainer,
4275
4533
  useShortcutManager,
4276
4534
  useShortcuts