@estiva-app/ui 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/NavItem.d.ts +14 -1
  2. package/dist/NavItem.d.ts.map +1 -1
  3. package/dist/NavTree.d.ts +93 -13
  4. package/dist/NavTree.d.ts.map +1 -1
  5. package/dist/UnreadDot.d.ts +12 -0
  6. package/dist/UnreadDot.d.ts.map +1 -0
  7. package/dist/eslint/index.js +1 -1
  8. package/dist/eslint/index.js.map +2 -2
  9. package/dist/eslint/no-native-title.d.ts +1 -1
  10. package/dist/eslint/no-native-title.d.ts.map +1 -1
  11. package/dist/gates/{chunk-FIBZLYQI.js → chunk-SNYYAFZS.js} +8 -1
  12. package/dist/gates/chunk-SNYYAFZS.js.map +7 -0
  13. package/dist/gates/create-app.d.ts.map +1 -1
  14. package/dist/gates/create-app.js +1 -1
  15. package/dist/gates/index.js +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +215 -141
  19. package/dist/index.js.map +4 -4
  20. package/dist/looks.d.ts +1 -1
  21. package/dist/looks.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/registry.json +154 -11
  24. package/src/NavItem.mdx +14 -1
  25. package/src/NavItem.stories.tsx +15 -0
  26. package/src/NavItem.test.tsx +31 -0
  27. package/src/NavItem.tsx +49 -12
  28. package/src/NavTree.mdx +25 -0
  29. package/src/NavTree.stories.tsx +57 -1
  30. package/src/NavTree.test.tsx +78 -1
  31. package/src/NavTree.tsx +175 -40
  32. package/src/SectionLabel.stories.tsx +2 -3
  33. package/src/UnreadDot.mdx +57 -0
  34. package/src/UnreadDot.stories.tsx +22 -0
  35. package/src/UnreadDot.test.tsx +17 -0
  36. package/src/UnreadDot.tsx +17 -0
  37. package/src/eslint/no-native-title.ts +1 -1
  38. package/src/gates/create-app.test.ts +8 -0
  39. package/src/gates/create-app.ts +7 -0
  40. package/src/index.ts +2 -1
  41. package/src/looks.ts +2 -1
  42. package/src/registry/registry.test.ts +5 -5
  43. package/stories/Choosing.mdx +1 -0
  44. package/tailwind-preset.js +1 -0
  45. package/tokens.css +5 -0
  46. package/dist/gates/chunk-FIBZLYQI.js.map +0 -7
package/dist/index.js CHANGED
@@ -319,7 +319,7 @@ var FIELD_SHELL_CLASSES = "bg-bg-field border border-border-field shadow-field h
319
319
  var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
320
320
  var CHIP_TEXT_CLASSES = "text-chip signal:font-mono signal:text-small signal:font-semibold signal:tabular-nums";
321
321
  var BARE_INPUT_CLASSES = "bg-transparent text-text-primary placeholder:text-text-muted outline-none";
322
- var SIDEBAR_ROW_CLASSES = "rounded-md transition-colors";
322
+ var SIDEBAR_ROW_CLASSES = "rounded-lg transition-colors";
323
323
  var SIDEBAR_ROW_TEXT_CLASSES = "text-text-secondary hover:text-text-primary";
324
324
  var COLLAPSIBLE_PANEL_CLASSES = "h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none data-[starting-style]:h-0 data-[ending-style]:h-0";
325
325
 
@@ -2799,8 +2799,16 @@ function Breadcrumb({ items, className }) {
2799
2799
  // src/NavItem.tsx
2800
2800
  import { useId, useState as useState7 } from "react";
2801
2801
  import { Collapsible } from "@base-ui/react/collapsible";
2802
- import { IconChevronRight as IconChevronRight2, IconDotsVertical } from "@tabler/icons-react";
2803
- import { jsx as jsx43, jsxs as jsxs29 } from "react/jsx-runtime";
2802
+ import { IconAlertSquareRounded, IconChevronRight as IconChevronRight2, IconDotsVertical } from "@tabler/icons-react";
2803
+
2804
+ // src/UnreadDot.tsx
2805
+ import { jsx as jsx43 } from "react/jsx-runtime";
2806
+ function UnreadDot({ className }) {
2807
+ return /* @__PURE__ */ jsx43("div", { className: cn("flex h-6 w-6 shrink-0 items-center justify-center", className), "data-unread": true, children: /* @__PURE__ */ jsx43("div", { className: "h-1.5 w-1.5 rounded-full bg-accent-primary" }) });
2808
+ }
2809
+
2810
+ // src/NavItem.tsx
2811
+ import { jsx as jsx44, jsxs as jsxs29 } from "react/jsx-runtime";
2804
2812
  function NavItem({
2805
2813
  label,
2806
2814
  href,
@@ -2811,6 +2819,8 @@ function NavItem({
2811
2819
  menu,
2812
2820
  menuLabel,
2813
2821
  hint,
2822
+ unread = false,
2823
+ urgent = false,
2814
2824
  children,
2815
2825
  defaultOpen = true,
2816
2826
  open: openProp,
@@ -2827,6 +2837,8 @@ function NavItem({
2827
2837
  onOpenChange?.(next);
2828
2838
  };
2829
2839
  const besides = menu != null || hasRows;
2840
+ const mark = unread ? urgent ? /* @__PURE__ */ jsx44(UrgentMark, {}) : /* @__PURE__ */ jsx44(UnreadDot, {}) : null;
2841
+ const stepsAside = menu != null && "transition-opacity group-hover/nav:opacity-0 group-focus-within/nav:opacity-0 group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:opacity-0";
2830
2842
  const row = /* @__PURE__ */ jsxs29(
2831
2843
  "a",
2832
2844
  {
@@ -2838,7 +2850,7 @@ function NavItem({
2838
2850
  // shrink-0 family; Katerina, 2026-09-02).
2839
2851
  "flex h-8 min-w-0 shrink-0 items-center gap-2 px-2 text-body-2",
2840
2852
  SIDEBAR_ROW_CLASSES,
2841
- active ? "bg-bg-active text-text-primary" : cn(SIDEBAR_ROW_TEXT_CLASSES, "hover:bg-bg-hover"),
2853
+ active ? "bg-bg-nav-active text-text-primary" : cn(unread ? "text-text-primary" : SIDEBAR_ROW_TEXT_CLASSES, "hover:bg-bg-hover"),
2842
2854
  // With a menu the ⋮ sits beside the link, so the row keeps its hover
2843
2855
  // look while the ⋮ is pointed at and while its menu is open. The same
2844
2856
  // for the arrow.
@@ -2848,7 +2860,7 @@ function NavItem({
2848
2860
  ),
2849
2861
  ...props,
2850
2862
  children: [
2851
- (icon || hasRows) && /* @__PURE__ */ jsx43(
2863
+ (icon || hasRows) && /* @__PURE__ */ jsx44(
2852
2864
  "span",
2853
2865
  {
2854
2866
  className: cn(
@@ -2859,23 +2871,25 @@ function NavItem({
2859
2871
  children: icon
2860
2872
  }
2861
2873
  ),
2862
- /* @__PURE__ */ jsx43("span", { className: "flex-1 truncate", children: label }),
2874
+ /* @__PURE__ */ jsx44("span", { className: cn("flex-1 truncate", unread && "font-medium"), children: label }),
2863
2875
  hint != null && // Takes room only while it shows, so the label gives way to it.
2864
- /* @__PURE__ */ jsx43("span", { className: "hidden shrink-0 items-center group-hover/nav:flex group-focus-within/nav:flex", children: hint }),
2865
- count ? /* @__PURE__ */ jsx43(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx43(
2876
+ /* @__PURE__ */ jsx44("span", { className: "hidden shrink-0 items-center group-hover/nav:flex group-focus-within/nav:flex", children: hint }),
2877
+ count ? /* @__PURE__ */ jsx44(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx44(
2866
2878
  "span",
2867
2879
  {
2868
2880
  className: cn(
2869
2881
  "min-w-4 shrink-0 text-center font-mono text-caption tabular-nums text-text-muted",
2870
- // With a menu, the ⋮ takes this place while it shows.
2871
- menu != null && "transition-opacity group-hover/nav:opacity-0 group-focus-within/nav:opacity-0 group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:opacity-0"
2882
+ // With a menu and no mark after it, the ⋮ takes this place while it shows.
2883
+ !mark && stepsAside
2872
2884
  ),
2873
2885
  children: count
2874
2886
  }
2875
- ) }) : menu != null ? (
2876
- // No count, but a menu: the count's place is kept free, so a long
2877
- // label truncates before the ⋮ instead of running under it.
2878
- /* @__PURE__ */ jsx43("span", { "aria-hidden": "true", className: "min-w-4 shrink-0" })
2887
+ ) }) : null,
2888
+ mark ? /* @__PURE__ */ jsx44("span", { className: cn("flex shrink-0", stepsAside), children: mark }) : menu != null && !count ? (
2889
+ // Nothing at the right, but a menu: no room is kept at rest, so the
2890
+ // label has the row's whole width; while the ⋮ shows, its 24px slot
2891
+ // is kept, so a long label ends before it instead of running under it.
2892
+ /* @__PURE__ */ jsx44("span", { "aria-hidden": "true", className: "hidden w-6 shrink-0 group-hover/nav:block group-focus-within/nav:block group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:block" })
2879
2893
  ) : null
2880
2894
  ]
2881
2895
  }
@@ -2886,7 +2900,7 @@ function NavItem({
2886
2900
  row,
2887
2901
  hasRows && // `left-1`: the 24px button's 16px arrow then sits where the icon
2888
2902
  // sits (the row's px-2), so the icon turns into the arrow in place.
2889
- /* @__PURE__ */ jsx43("div", { className: "absolute inset-y-0 left-1 flex items-center opacity-0 transition-opacity focus-within:opacity-100 group-hover/nav:opacity-100", children: /* @__PURE__ */ jsx43(
2903
+ /* @__PURE__ */ jsx44("div", { className: "absolute inset-y-0 left-1 flex items-center opacity-0 transition-opacity focus-within:opacity-100 group-hover/nav:opacity-100", children: /* @__PURE__ */ jsx44(
2890
2904
  IconButton,
2891
2905
  {
2892
2906
  tooltip: open ? "Collapse" : "Expand",
@@ -2894,16 +2908,18 @@ function NavItem({
2894
2908
  "aria-expanded": open,
2895
2909
  "aria-controls": panelId,
2896
2910
  onClick: () => setOpen(!open),
2897
- children: /* @__PURE__ */ jsx43(IconChevronRight2, { size: 16, stroke: 1.5, className: cn("transition-transform duration-150", open && "rotate-90") })
2911
+ children: /* @__PURE__ */ jsx44(IconChevronRight2, { size: 16, stroke: 1.5, className: cn("transition-transform duration-150", open && "rotate-90") })
2898
2912
  }
2899
2913
  ) }),
2900
- menu != null && // `right-1`: the 24px button's 16px icon then ends 8px from the row's
2901
- // edge, where the count ends (px-2), so the ⋮ lands on the number.
2902
- /* @__PURE__ */ jsx43("div", { "data-nav-menu": true, className: "absolute inset-y-0 right-1 flex items-center opacity-0 transition-opacity focus-within:opacity-100 group-hover/nav:opacity-100 has-[[aria-expanded=true]]:opacity-100", children: /* @__PURE__ */ jsx43(
2914
+ menu != null && // On a count, `right-1`: the 24px button's 16px icon then ends 8px
2915
+ // from the row's edge, where the count ends (px-2), so the ⋮ lands on
2916
+ // the number. On the mark or the empty slot, `right-2`: the button
2917
+ // sits in the 24px slot itself.
2918
+ /* @__PURE__ */ jsx44("div", { "data-nav-menu": true, className: cn("absolute inset-y-0 flex items-center", count && !mark ? "right-1" : "right-2", "opacity-0 transition-opacity focus-within:opacity-100 group-hover/nav:opacity-100 has-[[aria-expanded=true]]:opacity-100"), children: /* @__PURE__ */ jsx44(
2903
2919
  Menu,
2904
2920
  {
2905
2921
  align: "right",
2906
- trigger: /* @__PURE__ */ jsx43(IconButton, { tooltip: "More options", "aria-label": name, children: /* @__PURE__ */ jsx43(IconDotsVertical, { size: 16, stroke: 1.5 }) }),
2922
+ trigger: /* @__PURE__ */ jsx44(IconButton, { tooltip: "More options", "aria-label": name, children: /* @__PURE__ */ jsx44(IconDotsVertical, { size: 16, stroke: 1.5 }) }),
2907
2923
  children: menu
2908
2924
  }
2909
2925
  ) })
@@ -2911,17 +2927,23 @@ function NavItem({
2911
2927
  if (!hasRows) return line;
2912
2928
  return /* @__PURE__ */ jsxs29(Collapsible.Root, { open, onOpenChange: setOpen, className: "flex shrink-0 flex-col", children: [
2913
2929
  line,
2914
- /* @__PURE__ */ jsx43(
2930
+ /* @__PURE__ */ jsx44(
2915
2931
  Collapsible.Panel,
2916
2932
  {
2917
2933
  id: panelId,
2918
2934
  hiddenUntilFound: true,
2919
2935
  className: COLLAPSIBLE_PANEL_CLASSES,
2920
- children: /* @__PURE__ */ jsx43("div", { className: "flex flex-col gap-px pl-6", children })
2936
+ children: /* @__PURE__ */ jsx44("div", { className: "flex flex-col gap-px pl-6", children })
2921
2937
  }
2922
2938
  )
2923
2939
  ] });
2924
2940
  }
2941
+ function UrgentMark() {
2942
+ return /* @__PURE__ */ jsx44("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center", "data-urgent": true, children: /* @__PURE__ */ jsx44("div", { className: "flex items-center rounded-full bg-warning-muted p-0.5 signal:shadow-glow-warning", children: /* @__PURE__ */ jsx44(IconAlertSquareRounded, { size: 12, stroke: 2.5, className: "text-warning-default" }) }) });
2943
+ }
2944
+
2945
+ // src/NavTree.tsx
2946
+ import { useEffect as useEffect4, useRef as useRef9 } from "react";
2925
2947
 
2926
2948
  // src/CollapsibleSection.tsx
2927
2949
  import { useState as useState8 } from "react";
@@ -2930,18 +2952,18 @@ import { Collapsible as Collapsible2 } from "@base-ui/react/collapsible";
2930
2952
  // src/SectionHeader.tsx
2931
2953
  import { IconChevronRight as IconChevronRight3 } from "@tabler/icons-react";
2932
2954
  import { useRender } from "@base-ui/react/use-render";
2933
- import { Fragment as Fragment11, jsx as jsx44, jsxs as jsxs30 } from "react/jsx-runtime";
2955
+ import { Fragment as Fragment11, jsx as jsx45, jsxs as jsxs30 } from "react/jsx-runtime";
2934
2956
  function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, trailing, actions, showActions = "hover", hover = "fill", look = "heading", render, className }) {
2935
2957
  const row = look === "row";
2936
2958
  const titleElement = useRender({
2937
- render: render ?? (chevron ? /* @__PURE__ */ jsx44("button", { type: "button", onClick: onToggle, "aria-expanded": isExpanded }) : /* @__PURE__ */ jsx44("span", {})),
2959
+ render: render ?? (chevron ? /* @__PURE__ */ jsx45("button", { type: "button", onClick: onToggle, "aria-expanded": isExpanded }) : /* @__PURE__ */ jsx45("span", {})),
2938
2960
  props: {
2939
2961
  // `text-left`: a button centres its text. `h-full` and `flex-1`: the
2940
2962
  // whole row up to the actions is the hit target, as it was when the
2941
2963
  // row itself carried the click.
2942
2964
  className: cn("flex h-full min-w-0 flex-1 items-center text-left", row ? "gap-2" : "gap-1"),
2943
2965
  children: /* @__PURE__ */ jsxs30(Fragment11, { children: [
2944
- chevron && /* @__PURE__ */ jsx44(
2966
+ chevron && /* @__PURE__ */ jsx45(
2945
2967
  IconChevronRight3,
2946
2968
  {
2947
2969
  size: row ? 16 : 12,
@@ -2949,10 +2971,10 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
2949
2971
  className: cn("shrink-0 transition-transform duration-150", !row && "text-text-secondary", isExpanded && "rotate-90")
2950
2972
  }
2951
2973
  ),
2952
- look === "quiet" ? /* @__PURE__ */ jsx44("span", { className: "min-w-0 truncate text-caption text-text-muted", children: title }) : row ? (
2974
+ look === "quiet" ? /* @__PURE__ */ jsx45("span", { className: "min-w-0 truncate text-caption text-text-muted", children: title }) : row ? (
2953
2975
  // A row's words: NavItem's size and colour, brightening under the pointer with the row.
2954
- /* @__PURE__ */ jsx44("span", { className: "min-w-0 flex-1 truncate text-body-2", children: title })
2955
- ) : /* @__PURE__ */ jsx44(SectionLabel, { folds: chevron, children: title })
2976
+ /* @__PURE__ */ jsx45("span", { className: "min-w-0 flex-1 truncate text-body-2", children: title })
2977
+ ) : /* @__PURE__ */ jsx45(SectionLabel, { folds: chevron, children: title })
2956
2978
  ] })
2957
2979
  }
2958
2980
  });
@@ -2973,15 +2995,15 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
2973
2995
  ),
2974
2996
  children: [
2975
2997
  titleElement,
2976
- trailing != null && /* @__PURE__ */ jsx44("div", { className: "flex shrink-0 items-center", children: trailing }),
2977
- actions && actions.length > 0 && /* @__PURE__ */ jsx44(
2998
+ trailing != null && /* @__PURE__ */ jsx45("div", { className: "flex shrink-0 items-center", children: trailing }),
2999
+ actions && actions.length > 0 && /* @__PURE__ */ jsx45(
2978
3000
  "div",
2979
3001
  {
2980
3002
  className: cn(
2981
3003
  "-mr-1 flex shrink-0 items-center gap-1",
2982
3004
  showActions === "hover" && "opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100"
2983
3005
  ),
2984
- children: actions.map((action) => /* @__PURE__ */ jsx44(IconButton, { tooltip: action.tooltip, "aria-label": action.tooltip, onClick: action.onClick, children: action.icon }, action.tooltip))
3006
+ children: actions.map((action) => /* @__PURE__ */ jsx45(IconButton, { tooltip: action.tooltip, "aria-label": action.tooltip, onClick: action.onClick, children: action.icon }, action.tooltip))
2985
3007
  }
2986
3008
  )
2987
3009
  ]
@@ -2990,7 +3012,7 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
2990
3012
  }
2991
3013
 
2992
3014
  // src/CollapsibleSection.tsx
2993
- import { jsx as jsx45, jsxs as jsxs31 } from "react/jsx-runtime";
3015
+ import { jsx as jsx46, jsxs as jsxs31 } from "react/jsx-runtime";
2994
3016
  var OPEN = "open";
2995
3017
  var CLOSED = "closed";
2996
3018
  function readStored(key) {
@@ -3018,25 +3040,30 @@ function CollapsibleSection({ title, defaultOpen = true, open: openProp, onOpenC
3018
3040
  onOpenChange?.(next);
3019
3041
  };
3020
3042
  return /* @__PURE__ */ jsxs31(Collapsible2.Root, { open, onOpenChange: setOpen, className: cn("flex flex-col", className), children: [
3021
- /* @__PURE__ */ jsx45(SectionHeader, { title, chevron: true, isExpanded: open, trailing, actions, showActions, look, className: "shrink-0", render: /* @__PURE__ */ jsx45(Collapsible2.Trigger, {}) }),
3022
- /* @__PURE__ */ jsx45(
3043
+ /* @__PURE__ */ jsx46(SectionHeader, { title, chevron: true, isExpanded: open, trailing, actions, showActions, look, className: "shrink-0", render: /* @__PURE__ */ jsx46(Collapsible2.Trigger, {}) }),
3044
+ /* @__PURE__ */ jsx46(
3023
3045
  Collapsible2.Panel,
3024
3046
  {
3025
3047
  hiddenUntilFound: true,
3026
3048
  className: COLLAPSIBLE_PANEL_CLASSES,
3027
- children: /* @__PURE__ */ jsx45("div", { className: cn("flex flex-col", look === "row" ? "pl-6" : indent && "pl-4", contentClassName), children })
3049
+ children: /* @__PURE__ */ jsx46("div", { className: cn("flex flex-col", look === "row" ? "pl-6" : indent && "pl-4", contentClassName), children })
3028
3050
  }
3029
3051
  )
3030
3052
  ] });
3031
3053
  }
3032
3054
 
3033
3055
  // src/NavTree.tsx
3034
- import { Fragment as Fragment12, jsx as jsx46, jsxs as jsxs32 } from "react/jsx-runtime";
3056
+ import { Fragment as Fragment12, jsx as jsx47, jsxs as jsxs32 } from "react/jsx-runtime";
3057
+ var NAV_TREE_INTENT_MS = 150;
3035
3058
  function holds(nodes, id) {
3036
- return id !== void 0 && nodes.some((node) => node.id === id || holds(node.children ?? [], id));
3059
+ return !!nodes && id !== void 0 && nodes.some((node) => node.id === id || holds(node.children ?? null, id));
3060
+ }
3061
+ function Line({ message }) {
3062
+ return /* @__PURE__ */ jsx47("div", { className: "px-2 py-1", children: /* @__PURE__ */ jsx47(EmptyState, { scope: "section", message }) });
3037
3063
  }
3038
3064
  function Row({ node, selected, onSelect }) {
3039
- return /* @__PURE__ */ jsx46(
3065
+ const under = node.childrenMessage !== void 0 ? /* @__PURE__ */ jsx47(Line, { message: node.childrenMessage }) : node.children?.length ? node.children.map((child) => /* @__PURE__ */ jsx47(Row, { node: child, selected, onSelect }, child.id)) : node.children === null || node.hasChildren && node.children === void 0 ? /* @__PURE__ */ jsx47(SkeletonList, { rows: 2 }) : void 0;
3066
+ return /* @__PURE__ */ jsx47(
3040
3067
  NavItem,
3041
3068
  {
3042
3069
  href: node.href,
@@ -3045,40 +3072,85 @@ function Row({ node, selected, onSelect }) {
3045
3072
  count: node.count,
3046
3073
  countLabel: node.countLabel,
3047
3074
  hint: node.hint,
3075
+ unread: node.unread,
3076
+ urgent: node.urgent,
3048
3077
  menu: node.menu,
3049
3078
  active: node.id === selected,
3050
3079
  defaultOpen: node.defaultOpen ?? true,
3080
+ open: node.open,
3081
+ onOpenChange: node.onOpenChange,
3051
3082
  onClick: onSelect ? (event) => onSelect(node, event) : void 0,
3052
- children: node.children?.length ? node.children.map((child) => /* @__PURE__ */ jsx46(Row, { node: child, selected, onSelect }, child.id)) : void 0
3083
+ children: under
3084
+ }
3085
+ );
3086
+ }
3087
+ function Group({ group, selected, onSelect, storageKey }) {
3088
+ return /* @__PURE__ */ jsx47(
3089
+ NavTreeSection,
3090
+ {
3091
+ id: group.id,
3092
+ title: group.title,
3093
+ message: group.message,
3094
+ loading: group.nodes === null,
3095
+ trailing: group.trailing,
3096
+ actions: group.actions,
3097
+ defaultOpen: group.defaultOpen ?? holds(group.nodes, selected),
3098
+ open: group.open,
3099
+ onOpenChange: group.onOpenChange,
3100
+ storageKey: storageKey ? `${storageKey}.${group.id}` : void 0,
3101
+ onIntent: group.onIntent,
3102
+ children: group.nodes?.map((node) => /* @__PURE__ */ jsx47(Row, { node, selected, onSelect }, node.id))
3103
+ }
3104
+ );
3105
+ }
3106
+ function NavTreeSection({ id, title, message, loading = false, trailing, actions, defaultOpen = true, open, onOpenChange, storageKey, onIntent, children }) {
3107
+ const timer = useRef9(void 0);
3108
+ useEffect4(() => () => clearTimeout(timer.current), []);
3109
+ const intent = onIntent;
3110
+ return /* @__PURE__ */ jsx47(
3111
+ "div",
3112
+ {
3113
+ className: "shrink-0",
3114
+ "data-nav-group": id,
3115
+ onPointerEnter: intent ? () => {
3116
+ clearTimeout(timer.current);
3117
+ timer.current = setTimeout(intent, NAV_TREE_INTENT_MS);
3118
+ } : void 0,
3119
+ onPointerLeave: intent ? () => clearTimeout(timer.current) : void 0,
3120
+ onFocus: intent,
3121
+ children: /* @__PURE__ */ jsx47(
3122
+ CollapsibleSection,
3123
+ {
3124
+ title,
3125
+ indent: true,
3126
+ defaultOpen,
3127
+ open,
3128
+ onOpenChange,
3129
+ storageKey,
3130
+ trailing,
3131
+ actions,
3132
+ className: "mt-2 shrink-0",
3133
+ contentClassName: "gap-px",
3134
+ children: message !== void 0 ? /* @__PURE__ */ jsx47(Line, { message }) : loading ? /* @__PURE__ */ jsx47(SkeletonList, { rows: 3 }) : children
3135
+ }
3136
+ )
3053
3137
  }
3054
3138
  );
3055
3139
  }
3056
- function NavTree({ title, groups, selected, onSelect, emptyMessage, storageKey }) {
3140
+ function NavTree({ title, titleActions, groups, children, selected, onSelect, emptyMessage = "", storageKey }) {
3057
3141
  return /* @__PURE__ */ jsxs32(Fragment12, { children: [
3058
- title && /* @__PURE__ */ jsx46(SectionHeader, { title, look: "quiet", className: "mt-4 shrink-0" }),
3059
- groups === null ? /* @__PURE__ */ jsx46(SkeletonList, { rows: 5 }) : groups.length === 0 ? (
3060
- // Level with the rows: a row pads itself, so the line takes a row's inset (Katerina, 14 September).
3061
- /* @__PURE__ */ jsx46("div", { className: "px-2 py-1", children: /* @__PURE__ */ jsx46(EmptyState, { scope: "section", message: emptyMessage }) })
3062
- ) : groups.map((group) => /* @__PURE__ */ jsx46(
3063
- CollapsibleSection,
3064
- {
3065
- title: group.title,
3066
- indent: true,
3067
- defaultOpen: holds(group.nodes, selected),
3068
- storageKey: storageKey ? `${storageKey}.${group.id}` : void 0,
3069
- className: "mt-2 shrink-0",
3070
- contentClassName: "gap-px",
3071
- children: group.nodes.map((node) => /* @__PURE__ */ jsx46(Row, { node, selected, onSelect }, node.id))
3072
- },
3073
- group.id
3074
- ))
3142
+ title && /* @__PURE__ */ jsx47(SectionHeader, { title, look: "quiet", showActions: "always", actions: titleActions, className: "mt-4 shrink-0" }),
3143
+ groups === void 0 ? children : groups === null ? /* @__PURE__ */ jsx47(SkeletonList, { rows: 5 }) : groups.length === 0 ? (
3144
+ // No words, no line: an app that says nothing when it has no groups.
3145
+ emptyMessage ? /* @__PURE__ */ jsx47(Line, { message: emptyMessage }) : null
3146
+ ) : groups.map((group) => /* @__PURE__ */ jsx47(Group, { group, selected, onSelect, storageKey }, group.id))
3075
3147
  ] });
3076
3148
  }
3077
3149
 
3078
3150
  // src/Popover.tsx
3079
3151
  import { useMemo as useMemo3 } from "react";
3080
3152
  import { Popover as BasePopover } from "@base-ui/react/popover";
3081
- import { jsx as jsx47, jsxs as jsxs33 } from "react/jsx-runtime";
3153
+ import { jsx as jsx48, jsxs as jsxs33 } from "react/jsx-runtime";
3082
3154
  var GAP5 = 4;
3083
3155
  var VIEWPORT_PAD5 = 8;
3084
3156
  function Popover({ trigger, anchor, align = "left", side = "bottom", open, onOpenChange, finalFocus, actionsRef, ariaLabel, children, className, contentClassName, maxHeight }) {
@@ -3095,8 +3167,8 @@ function Popover({ trigger, anchor, align = "left", side = "bottom", open, onOpe
3095
3167
  actionsRef,
3096
3168
  modal: false,
3097
3169
  children: [
3098
- trigger && /* @__PURE__ */ jsx47(BasePopover.Trigger, { render: trigger, disabled: triggerDisabled(trigger) }),
3099
- /* @__PURE__ */ jsx47(BasePopover.Portal, { children: /* @__PURE__ */ jsx47(
3170
+ trigger && /* @__PURE__ */ jsx48(BasePopover.Trigger, { render: trigger, disabled: triggerDisabled(trigger) }),
3171
+ /* @__PURE__ */ jsx48(BasePopover.Portal, { children: /* @__PURE__ */ jsx48(
3100
3172
  BasePopover.Positioner,
3101
3173
  {
3102
3174
  anchor: anchorTarget,
@@ -3105,20 +3177,20 @@ function Popover({ trigger, anchor, align = "left", side = "bottom", open, onOpe
3105
3177
  sideOffset: GAP5,
3106
3178
  collisionPadding: VIEWPORT_PAD5,
3107
3179
  className: "z-50 data-[anchor-hidden]:hidden",
3108
- children: /* @__PURE__ */ jsx47(
3180
+ children: /* @__PURE__ */ jsx48(
3109
3181
  BasePopover.Popup,
3110
3182
  {
3111
3183
  "aria-label": ariaLabel,
3112
3184
  initialFocus: trigger ? void 0 : false,
3113
3185
  finalFocus,
3114
3186
  className: cn("min-w-[180px] outline-none p-0", className),
3115
- render: /* @__PURE__ */ jsx47(MenuPanel, {}),
3116
- children: /* @__PURE__ */ jsx47(
3187
+ render: /* @__PURE__ */ jsx48(MenuPanel, {}),
3188
+ children: /* @__PURE__ */ jsx48(
3117
3189
  ScrollArea,
3118
3190
  {
3119
3191
  viewportClassName: maxHeight ?? "max-h-[var(--available-height)]",
3120
3192
  contentClassName: cn("flex flex-col p-2", contentClassName),
3121
- children: /* @__PURE__ */ jsx47(DividerInPanel.Provider, { value: true, children })
3193
+ children: /* @__PURE__ */ jsx48(DividerInPanel.Provider, { value: true, children })
3122
3194
  }
3123
3195
  )
3124
3196
  }
@@ -3131,8 +3203,8 @@ function Popover({ trigger, anchor, align = "left", side = "bottom", open, onOpe
3131
3203
  }
3132
3204
 
3133
3205
  // src/SuggestionMenu.tsx
3134
- import { useCallback as useCallback3, useEffect as useEffect4, useId as useId2, useImperativeHandle, useMemo as useMemo4, useRef as useRef9, useState as useState9 } from "react";
3135
- import { jsx as jsx48, jsxs as jsxs34 } from "react/jsx-runtime";
3206
+ import { useCallback as useCallback3, useEffect as useEffect5, useId as useId2, useImperativeHandle, useMemo as useMemo4, useRef as useRef10, useState as useState9 } from "react";
3207
+ import { jsx as jsx49, jsxs as jsxs34 } from "react/jsx-runtime";
3136
3208
  function SuggestionMenu({ rect, editorElement, ariaLabel, width, maxHeight, sections, itemKey, row, onSelect, handle }) {
3137
3209
  const shown = useMemo4(() => sections.filter((section) => section.items.length > 0), [sections]);
3138
3210
  const items = useMemo4(() => shown.flatMap((section) => section.items), [shown]);
@@ -3140,9 +3212,9 @@ function SuggestionMenu({ rect, editorElement, ariaLabel, width, maxHeight, sect
3140
3212
  const [highlight, setHighlight] = useState9(0);
3141
3213
  const base = useId2();
3142
3214
  const listboxId = `${base}-list`;
3143
- const chosen = useRef9(null);
3144
- useEffect4(() => void (chosen.current = highlight > 0 ? keys.split("\n")[highlight] ?? null : null), [highlight]);
3145
- useEffect4(() => {
3215
+ const chosen = useRef10(null);
3216
+ useEffect5(() => void (chosen.current = highlight > 0 ? keys.split("\n")[highlight] ?? null : null), [highlight]);
3217
+ useEffect5(() => {
3146
3218
  const at = chosen.current ? keys.split("\n").indexOf(chosen.current) : -1;
3147
3219
  setHighlight(at > 0 ? at : 0);
3148
3220
  }, [keys]);
@@ -3167,7 +3239,7 @@ function SuggestionMenu({ rect, editorElement, ariaLabel, width, maxHeight, sect
3167
3239
  }
3168
3240
  }), [items, highlight, onSelect]);
3169
3241
  const open = rect !== null && items.length > 0;
3170
- useEffect4(() => {
3242
+ useEffect5(() => {
3171
3243
  if (!editorElement || !open) return;
3172
3244
  editorElement.setAttribute("aria-autocomplete", "list");
3173
3245
  editorElement.setAttribute("aria-haspopup", "listbox");
@@ -3176,7 +3248,7 @@ function SuggestionMenu({ rect, editorElement, ariaLabel, width, maxHeight, sect
3176
3248
  for (const name of ["aria-autocomplete", "aria-haspopup", "aria-controls", "aria-activedescendant"]) editorElement.removeAttribute(name);
3177
3249
  };
3178
3250
  }, [editorElement, open, listboxId]);
3179
- useEffect4(() => {
3251
+ useEffect5(() => {
3180
3252
  if (!editorElement || !open) return;
3181
3253
  editorElement.setAttribute("aria-activedescendant", `${base}-${highlight}`);
3182
3254
  }, [editorElement, open, base, highlight]);
@@ -3190,7 +3262,7 @@ function SuggestionMenu({ rect, editorElement, ariaLabel, width, maxHeight, sect
3190
3262
  const { label, description, leading, shortcut, hint, size } = row(item);
3191
3263
  return (
3192
3264
  // A row of a list the editor drives from its caret: focus stays in the text, so the row is no Tab stop.
3193
- /* @__PURE__ */ jsx48(
3265
+ /* @__PURE__ */ jsx49(
3194
3266
  MenuItem,
3195
3267
  {
3196
3268
  role: "option",
@@ -3215,17 +3287,17 @@ function SuggestionMenu({ rect, editorElement, ariaLabel, width, maxHeight, sect
3215
3287
  )
3216
3288
  );
3217
3289
  };
3218
- return /* @__PURE__ */ jsx48(Popover, { anchor: rect, open: rect !== null, side: "top", align: "left", className: width, maxHeight, ariaLabel, children: /* @__PURE__ */ jsx48("div", { id: listboxId, role: "listbox", "aria-label": ariaLabel, className: "flex flex-col", children: shown.map((section, i) => /* @__PURE__ */ jsxs34("div", { className: "contents", children: [
3219
- i > 0 && /* @__PURE__ */ jsx48(MenuSeparator, {}),
3220
- section.label ? /* @__PURE__ */ jsx48("div", { role: "group", "aria-label": section.label, children: /* @__PURE__ */ jsx48(MenuSection, { label: section.label, className: section.className, children: section.items.map(drawRow) }) }) : section.items.map(drawRow)
3290
+ return /* @__PURE__ */ jsx49(Popover, { anchor: rect, open: rect !== null, side: "top", align: "left", className: width, maxHeight, ariaLabel, children: /* @__PURE__ */ jsx49("div", { id: listboxId, role: "listbox", "aria-label": ariaLabel, className: "flex flex-col", children: shown.map((section, i) => /* @__PURE__ */ jsxs34("div", { className: "contents", children: [
3291
+ i > 0 && /* @__PURE__ */ jsx49(MenuSeparator, {}),
3292
+ section.label ? /* @__PURE__ */ jsx49("div", { role: "group", "aria-label": section.label, children: /* @__PURE__ */ jsx49(MenuSection, { label: section.label, className: section.className, children: section.items.map(drawRow) }) }) : section.items.map(drawRow)
3221
3293
  ] }, section.label ?? i)) }) });
3222
3294
  }
3223
3295
 
3224
3296
  // src/Toolbar.tsx
3225
3297
  import { Toolbar as BaseToolbar } from "@base-ui/react/toolbar";
3226
- import { jsx as jsx49 } from "react/jsx-runtime";
3298
+ import { jsx as jsx50 } from "react/jsx-runtime";
3227
3299
  function Toolbar({ "aria-label": ariaLabel, orientation = "horizontal", loopFocus = true, surface = true, children, className }) {
3228
- const strip = /* @__PURE__ */ jsx49(
3300
+ const strip = /* @__PURE__ */ jsx50(
3229
3301
  BaseToolbar.Root,
3230
3302
  {
3231
3303
  "aria-label": ariaLabel,
@@ -3236,27 +3308,27 @@ function Toolbar({ "aria-label": ariaLabel, orientation = "horizontal", loopFocu
3236
3308
  }
3237
3309
  );
3238
3310
  if (!surface) return strip;
3239
- return /* @__PURE__ */ jsx49(MenuPanel, { className: "w-fit p-1", children: strip });
3311
+ return /* @__PURE__ */ jsx50(MenuPanel, { className: "w-fit p-1", children: strip });
3240
3312
  }
3241
3313
  function ToolbarButton({ ref, disabled, disabledReason, ...props }) {
3242
- return /* @__PURE__ */ jsx49(
3314
+ return /* @__PURE__ */ jsx50(
3243
3315
  BaseToolbar.Button,
3244
3316
  {
3245
3317
  ref,
3246
3318
  disabled: disabled || !!disabledReason,
3247
3319
  focusableWhenDisabled: true,
3248
- render: /* @__PURE__ */ jsx49(IconButton, { disabled, disabledReason, ...props })
3320
+ render: /* @__PURE__ */ jsx50(IconButton, { disabled, disabledReason, ...props })
3249
3321
  }
3250
3322
  );
3251
3323
  }
3252
3324
  function ToolbarLink({ ref, href, ...props }) {
3253
- return /* @__PURE__ */ jsx49(BaseToolbar.Link, { ref, render: /* @__PURE__ */ jsx49(IconButton, { href, ...props }) });
3325
+ return /* @__PURE__ */ jsx50(BaseToolbar.Link, { ref, render: /* @__PURE__ */ jsx50(IconButton, { href, ...props }) });
3254
3326
  }
3255
3327
  function ToolbarInput({ size, ...props }) {
3256
- return /* @__PURE__ */ jsx49(BaseToolbar.Input, { render: /* @__PURE__ */ jsx49(TextInput, { size }), ...props });
3328
+ return /* @__PURE__ */ jsx50(BaseToolbar.Input, { render: /* @__PURE__ */ jsx50(TextInput, { size }), ...props });
3257
3329
  }
3258
3330
  function ToolbarSeparator({ className }) {
3259
- return /* @__PURE__ */ jsx49(
3331
+ return /* @__PURE__ */ jsx50(
3260
3332
  BaseToolbar.Separator,
3261
3333
  {
3262
3334
  orientation: "vertical",
@@ -3266,9 +3338,9 @@ function ToolbarSeparator({ className }) {
3266
3338
  }
3267
3339
 
3268
3340
  // src/ReactionPicker.tsx
3269
- import { jsx as jsx50 } from "react/jsx-runtime";
3341
+ import { jsx as jsx51 } from "react/jsx-runtime";
3270
3342
  function ReactionPicker({ options, onSelect, "aria-label": ariaLabel = "Reactions", surface = true, className }) {
3271
- return /* @__PURE__ */ jsx50(Toolbar, { "aria-label": ariaLabel, surface, className: cn("gap-0.5", className), children: options.map((option) => (
3343
+ return /* @__PURE__ */ jsx51(Toolbar, { "aria-label": ariaLabel, surface, className: cn("gap-0.5", className), children: options.map((option) => (
3272
3344
  /*
3273
3345
  A `ToolbarButton`, not a button in a tooltip wrapper. The wrapper
3274
3346
  would become the toolbar's item and the button inside it would never
@@ -3278,14 +3350,14 @@ function ReactionPicker({ options, onSelect, "aria-label": ariaLabel = "Reaction
3278
3350
  The tooltip names it for a pointer, `aria-label` for everything else,
3279
3351
  and both say the meaning rather than the glyph.
3280
3352
  */
3281
- /* @__PURE__ */ jsx50(
3353
+ /* @__PURE__ */ jsx51(
3282
3354
  ToolbarButton,
3283
3355
  {
3284
3356
  "aria-label": option.label,
3285
3357
  tooltip: option.label,
3286
3358
  onClick: () => onSelect(option.emoji),
3287
3359
  className: "size-7",
3288
- children: /* @__PURE__ */ jsx50("span", { "aria-hidden": "true", className: "text-h3 leading-none", children: option.emoji })
3360
+ children: /* @__PURE__ */ jsx51("span", { "aria-hidden": "true", className: "text-h3 leading-none", children: option.emoji })
3289
3361
  },
3290
3362
  option.emoji
3291
3363
  )
@@ -3294,23 +3366,23 @@ function ReactionPicker({ options, onSelect, "aria-label": ariaLabel = "Reaction
3294
3366
 
3295
3367
  // src/PreviewCard.tsx
3296
3368
  import { PreviewCard as BasePreviewCard } from "@base-ui/react/preview-card";
3297
- import { jsx as jsx51, jsxs as jsxs35 } from "react/jsx-runtime";
3369
+ import { jsx as jsx52, jsxs as jsxs35 } from "react/jsx-runtime";
3298
3370
  var GAP6 = 12;
3299
3371
  var VIEWPORT_PAD6 = 8;
3300
3372
  var OPEN_DELAY2 = 350;
3301
3373
  var CLOSE_DELAY = 200;
3302
3374
  function PreviewCard({ content, children, side = "right", delay = OPEN_DELAY2, closeDelay = CLOSE_DELAY, className, contentClassName, wrapperClassName }) {
3303
3375
  return /* @__PURE__ */ jsxs35(BasePreviewCard.Root, { children: [
3304
- /* @__PURE__ */ jsx51(
3376
+ /* @__PURE__ */ jsx52(
3305
3377
  BasePreviewCard.Trigger,
3306
3378
  {
3307
3379
  delay,
3308
3380
  closeDelay,
3309
- render: /* @__PURE__ */ jsx51("span", { className: cn("inline-flex", wrapperClassName) }),
3381
+ render: /* @__PURE__ */ jsx52("span", { className: cn("inline-flex", wrapperClassName) }),
3310
3382
  children
3311
3383
  }
3312
3384
  ),
3313
- /* @__PURE__ */ jsx51(BasePreviewCard.Portal, { children: /* @__PURE__ */ jsx51(
3385
+ /* @__PURE__ */ jsx52(BasePreviewCard.Portal, { children: /* @__PURE__ */ jsx52(
3314
3386
  BasePreviewCard.Positioner,
3315
3387
  {
3316
3388
  side,
@@ -3318,12 +3390,12 @@ function PreviewCard({ content, children, side = "right", delay = OPEN_DELAY2, c
3318
3390
  sideOffset: GAP6,
3319
3391
  collisionPadding: VIEWPORT_PAD6,
3320
3392
  className: "z-50 data-[anchor-hidden]:hidden",
3321
- children: /* @__PURE__ */ jsx51(
3393
+ children: /* @__PURE__ */ jsx52(
3322
3394
  BasePreviewCard.Popup,
3323
3395
  {
3324
3396
  className: cn("w-[360px] p-0 outline-none", className),
3325
- render: /* @__PURE__ */ jsx51(MenuPanel, {}),
3326
- children: /* @__PURE__ */ jsx51(ScrollArea, { viewportClassName: "max-h-[min(300px,var(--available-height))]", contentClassName: cn("flex flex-col gap-3 p-3", contentClassName), children: content })
3397
+ render: /* @__PURE__ */ jsx52(MenuPanel, {}),
3398
+ children: /* @__PURE__ */ jsx52(ScrollArea, { viewportClassName: "max-h-[min(300px,var(--available-height))]", contentClassName: cn("flex flex-col gap-3 p-3", contentClassName), children: content })
3327
3399
  }
3328
3400
  )
3329
3401
  }
@@ -3332,13 +3404,13 @@ function PreviewCard({ content, children, side = "right", delay = OPEN_DELAY2, c
3332
3404
  }
3333
3405
 
3334
3406
  // src/Rail.tsx
3335
- import { jsx as jsx52 } from "react/jsx-runtime";
3407
+ import { jsx as jsx53 } from "react/jsx-runtime";
3336
3408
  function Rail({ "aria-label": ariaLabel = "Navigation", children, className }) {
3337
- return /* @__PURE__ */ jsx52("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
3409
+ return /* @__PURE__ */ jsx53("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
3338
3410
  }
3339
3411
 
3340
3412
  // src/RailItem.tsx
3341
- import { jsx as jsx53, jsxs as jsxs36 } from "react/jsx-runtime";
3413
+ import { jsx as jsx54, jsxs as jsxs36 } from "react/jsx-runtime";
3342
3414
  function RailItem({ href, icon, label, active = false, className, ...props }) {
3343
3415
  return /* @__PURE__ */ jsxs36(
3344
3416
  "a",
@@ -3348,14 +3420,14 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
3348
3420
  className: cn("group flex h-12 w-full shrink-0 flex-col items-center gap-0.5 px-2 py-0.5", className),
3349
3421
  ...props,
3350
3422
  children: [
3351
- /* @__PURE__ */ jsx53(
3423
+ /* @__PURE__ */ jsx54(
3352
3424
  "div",
3353
3425
  {
3354
3426
  className: cn(
3355
3427
  "flex items-center justify-center rounded-lg p-2 transition-colors",
3356
3428
  active ? "bg-bg-selected" : "group-hover:bg-bg-hover"
3357
3429
  ),
3358
- children: /* @__PURE__ */ jsx53(
3430
+ children: /* @__PURE__ */ jsx54(
3359
3431
  "span",
3360
3432
  {
3361
3433
  className: cn(
@@ -3367,14 +3439,14 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
3367
3439
  )
3368
3440
  }
3369
3441
  ),
3370
- /* @__PURE__ */ jsx53("span", { className: cn("text-center text-menu", active ? "text-text-primary" : "text-text-secondary"), children: label })
3442
+ /* @__PURE__ */ jsx54("span", { className: cn("text-center text-menu", active ? "text-text-primary" : "text-text-secondary"), children: label })
3371
3443
  ]
3372
3444
  }
3373
3445
  );
3374
3446
  }
3375
3447
 
3376
3448
  // src/Property.tsx
3377
- import { jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
3449
+ import { jsx as jsx55, jsxs as jsxs37 } from "react/jsx-runtime";
3378
3450
  var VALUE_CLASSES = "contents";
3379
3451
  function Property({ label, layout = "row", children, className }) {
3380
3452
  if (layout === "stacked") {
@@ -3383,20 +3455,20 @@ function Property({ label, layout = "row", children, className }) {
3383
3455
  // Peek's topic-details sections already sit at 12px, Ship's rail was
3384
3456
  // 6px, and unifying means the bigger, calmer one.
3385
3457
  /* @__PURE__ */ jsxs37("dl", { className: cn("flex flex-col gap-3", className), children: [
3386
- /* @__PURE__ */ jsx54("dt", { className: "text-menu uppercase tracking-widest text-text-secondary", children: label }),
3387
- /* @__PURE__ */ jsx54("dd", { className: VALUE_CLASSES, children })
3458
+ /* @__PURE__ */ jsx55("dt", { className: "text-menu uppercase tracking-widest text-text-secondary", children: label }),
3459
+ /* @__PURE__ */ jsx55("dd", { className: VALUE_CLASSES, children })
3388
3460
  ] })
3389
3461
  );
3390
3462
  }
3391
3463
  return /* @__PURE__ */ jsxs37("dl", { className: cn("flex items-center gap-2", className), children: [
3392
- /* @__PURE__ */ jsx54("dt", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
3393
- /* @__PURE__ */ jsx54("dd", { className: VALUE_CLASSES, children })
3464
+ /* @__PURE__ */ jsx55("dt", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
3465
+ /* @__PURE__ */ jsx55("dd", { className: VALUE_CLASSES, children })
3394
3466
  ] });
3395
3467
  }
3396
3468
 
3397
3469
  // src/Reaction.tsx
3398
3470
  import { Toggle } from "@base-ui/react/toggle";
3399
- import { jsx as jsx55, jsxs as jsxs38 } from "react/jsx-runtime";
3471
+ import { jsx as jsx56, jsxs as jsxs38 } from "react/jsx-runtime";
3400
3472
  function Reaction({ emoji, count, pressed = false, className, ...props }) {
3401
3473
  return (
3402
3474
  // @estiva-escape(no-copied-look): W1 (Katerina, 24 September: leave alone): a reaction pill shares four words with a field's box by coincidence
@@ -3422,8 +3494,8 @@ function Reaction({ emoji, count, pressed = false, className, ...props }) {
3422
3494
  ),
3423
3495
  ...props,
3424
3496
  children: [
3425
- /* @__PURE__ */ jsx55("span", { "aria-hidden": "true", className: "shrink-0 text-body-1 leading-none", children: emoji }),
3426
- /* @__PURE__ */ jsx55("span", { className: CHIP_TEXT_CLASSES, children: count })
3497
+ /* @__PURE__ */ jsx56("span", { "aria-hidden": "true", className: "shrink-0 text-body-1 leading-none", children: emoji }),
3498
+ /* @__PURE__ */ jsx56("span", { className: CHIP_TEXT_CLASSES, children: count })
3427
3499
  ]
3428
3500
  }
3429
3501
  )
@@ -3433,7 +3505,7 @@ function Reaction({ emoji, count, pressed = false, className, ...props }) {
3433
3505
  // src/SearchInput.tsx
3434
3506
  import "react";
3435
3507
  import { Input as Input3 } from "@base-ui/react/input";
3436
- import { jsx as jsx56, jsxs as jsxs39 } from "react/jsx-runtime";
3508
+ import { jsx as jsx57, jsxs as jsxs39 } from "react/jsx-runtime";
3437
3509
  function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...props }) {
3438
3510
  return /* @__PURE__ */ jsxs39(
3439
3511
  "div",
@@ -3447,7 +3519,7 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
3447
3519
  className
3448
3520
  ),
3449
3521
  children: [
3450
- /* @__PURE__ */ jsx56(
3522
+ /* @__PURE__ */ jsx57(
3451
3523
  Input3,
3452
3524
  {
3453
3525
  className: cn("flex-1 min-w-0 text-input-value", BARE_INPUT_CLASSES),
@@ -3455,40 +3527,40 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
3455
3527
  ...props
3456
3528
  }
3457
3529
  ),
3458
- shortcut && /* @__PURE__ */ jsx56(Kbd, { children: shortcut })
3530
+ shortcut && /* @__PURE__ */ jsx57(Kbd, { children: shortcut })
3459
3531
  ]
3460
3532
  }
3461
3533
  );
3462
3534
  }
3463
3535
 
3464
3536
  // src/Panel.tsx
3465
- import { jsx as jsx57, jsxs as jsxs40 } from "react/jsx-runtime";
3537
+ import { jsx as jsx58, jsxs as jsxs40 } from "react/jsx-runtime";
3466
3538
  function Frame({ title, chevron = false, actions, children, footer, bodyClassName, bodyRef, onBodyScroll, className, broken = false }) {
3467
3539
  return /* @__PURE__ */ jsxs40("div", { className: cn("flex h-full min-h-0 flex-1 flex-col", className), children: [
3468
- /* @__PURE__ */ jsx57(ContainerHeader, { title, chevron, actions }),
3540
+ /* @__PURE__ */ jsx58(ContainerHeader, { title, chevron, actions }),
3469
3541
  broken ? (
3470
3542
  // The message takes the room the body had and centres itself in it.
3471
- /* @__PURE__ */ jsx57("div", { className: "flex min-h-0 flex-1 flex-col px-4", children })
3472
- ) : /* @__PURE__ */ jsx57(ScrollArea, { className: "min-h-0 flex-1", contentClassName: bodyClassName, viewportRef: bodyRef, onScroll: onBodyScroll, children }),
3473
- !broken && footer && /* @__PURE__ */ jsx57("div", { className: "shrink-0", children: footer })
3543
+ /* @__PURE__ */ jsx58("div", { className: "flex min-h-0 flex-1 flex-col px-4", children })
3544
+ ) : /* @__PURE__ */ jsx58(ScrollArea, { className: "min-h-0 flex-1", contentClassName: bodyClassName, viewportRef: bodyRef, onScroll: onBodyScroll, children }),
3545
+ !broken && footer && /* @__PURE__ */ jsx58("div", { className: "shrink-0", children: footer })
3474
3546
  ] });
3475
3547
  }
3476
3548
  function Panel(props) {
3477
- return /* @__PURE__ */ jsx57(
3549
+ return /* @__PURE__ */ jsx58(
3478
3550
  ErrorBoundary,
3479
3551
  {
3480
3552
  label: "panel",
3481
3553
  frame: (message) => (
3482
3554
  // Only the name comes back with the message: the actions and the footer may be what threw.
3483
- /* @__PURE__ */ jsx57(Frame, { title: props.title, chevron: props.chevron, className: props.className, broken: true, children: message })
3555
+ /* @__PURE__ */ jsx58(Frame, { title: props.title, chevron: props.chevron, className: props.className, broken: true, children: message })
3484
3556
  ),
3485
- children: /* @__PURE__ */ jsx57(Frame, { ...props })
3557
+ children: /* @__PURE__ */ jsx58(Frame, { ...props })
3486
3558
  }
3487
3559
  );
3488
3560
  }
3489
3561
 
3490
3562
  // src/ListColumn.tsx
3491
- import { jsx as jsx58, jsxs as jsxs41 } from "react/jsx-runtime";
3563
+ import { jsx as jsx59, jsxs as jsxs41 } from "react/jsx-runtime";
3492
3564
  function Column({ title, chevron = false, actions, above, collapsed = false, children, className, broken = false }) {
3493
3565
  return /* @__PURE__ */ jsxs41(
3494
3566
  "div",
@@ -3501,35 +3573,35 @@ function Column({ title, chevron = false, actions, above, collapsed = false, chi
3501
3573
  ),
3502
3574
  "data-collapsed": collapsed || void 0,
3503
3575
  children: [
3504
- /* @__PURE__ */ jsx58(ContainerHeader, { title, chevron, actions }),
3576
+ /* @__PURE__ */ jsx59(ContainerHeader, { title, chevron, actions }),
3505
3577
  above,
3506
3578
  broken ? (
3507
3579
  // The message takes the room the list had and centres itself in it (Katerina, 19 September).
3508
- /* @__PURE__ */ jsx58("div", { className: "flex min-h-0 flex-1 flex-col px-3", children })
3509
- ) : /* @__PURE__ */ jsx58(ScrollArea, { className: "flex-1", contentClassName: "flex flex-col gap-0.5 px-3 pt-4 pb-3", children })
3580
+ /* @__PURE__ */ jsx59("div", { className: "flex min-h-0 flex-1 flex-col px-3", children })
3581
+ ) : /* @__PURE__ */ jsx59(ScrollArea, { className: "flex-1", contentClassName: "flex flex-col gap-0.5 px-3 pt-4 pb-3", children })
3510
3582
  ]
3511
3583
  }
3512
3584
  );
3513
3585
  }
3514
3586
  function ListColumn(props) {
3515
- return /* @__PURE__ */ jsx58(
3587
+ return /* @__PURE__ */ jsx59(
3516
3588
  ErrorBoundary,
3517
3589
  {
3518
3590
  label: "list panel",
3519
3591
  frame: (message) => (
3520
3592
  // Only the name comes back with the message: the actions and the row above may be what threw.
3521
- /* @__PURE__ */ jsx58(Column, { title: props.title, chevron: props.chevron, collapsed: props.collapsed, className: props.className, broken: true, children: message })
3593
+ /* @__PURE__ */ jsx59(Column, { title: props.title, chevron: props.chevron, collapsed: props.collapsed, className: props.className, broken: true, children: message })
3522
3594
  ),
3523
- children: /* @__PURE__ */ jsx58(Column, { ...props })
3595
+ children: /* @__PURE__ */ jsx59(Column, { ...props })
3524
3596
  }
3525
3597
  );
3526
3598
  }
3527
3599
 
3528
3600
  // src/Tabs.tsx
3529
3601
  import { Tabs as BaseTabs } from "@base-ui/react/tabs";
3530
- import { jsx as jsx59, jsxs as jsxs42 } from "react/jsx-runtime";
3602
+ import { jsx as jsx60, jsxs as jsxs42 } from "react/jsx-runtime";
3531
3603
  function Tabs({ tabs, active, onChange, size = "default", className, ...aria }) {
3532
- return /* @__PURE__ */ jsx59(
3604
+ return /* @__PURE__ */ jsx60(
3533
3605
  BaseTabs.Root,
3534
3606
  {
3535
3607
  value: active,
@@ -3537,7 +3609,7 @@ function Tabs({ tabs, active, onChange, size = "default", className, ...aria })
3537
3609
  if (details.reason === "none") onChange(value);
3538
3610
  },
3539
3611
  className,
3540
- children: /* @__PURE__ */ jsx59(
3612
+ children: /* @__PURE__ */ jsx60(
3541
3613
  BaseTabs.List,
3542
3614
  {
3543
3615
  activateOnFocus: true,
@@ -3559,7 +3631,7 @@ function Tabs({ tabs, active, onChange, size = "default", className, ...aria })
3559
3631
  tab.icon,
3560
3632
  /* @__PURE__ */ jsxs42("span", { className: "flex items-baseline", children: [
3561
3633
  tab.label,
3562
- tab.count !== void 0 ? /* @__PURE__ */ jsx59("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
3634
+ tab.count !== void 0 ? /* @__PURE__ */ jsx60("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
3563
3635
  ] })
3564
3636
  ]
3565
3637
  },
@@ -3575,7 +3647,7 @@ function Tabs({ tabs, active, onChange, size = "default", className, ...aria })
3575
3647
  import { createContext as createContext6, useContext as useContext6, useMemo as useMemo5 } from "react";
3576
3648
  import { Toast as BaseToast } from "@base-ui/react/toast";
3577
3649
  import { IconAlertCircle as IconAlertCircle2, IconCircleCheck, IconCircleX } from "@tabler/icons-react";
3578
- import { jsx as jsx60, jsxs as jsxs43 } from "react/jsx-runtime";
3650
+ import { jsx as jsx61, jsxs as jsxs43 } from "react/jsx-runtime";
3579
3651
  var SURFACE_STYLES = {
3580
3652
  success: "bg-success-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-md leaf:bg-bg-inset leaf:border leaf:border-border-default leaf:shadow-md",
3581
3653
  brand: "bg-accent-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-md leaf:bg-bg-inset leaf:border leaf:border-border-default leaf:shadow-md",
@@ -3611,16 +3683,16 @@ var pillClassName = (type, hasAction, className) => cn(
3611
3683
  );
3612
3684
  function LeadingIcon({ type }) {
3613
3685
  const Icon = ICONS[type];
3614
- return /* @__PURE__ */ jsx60(Icon, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_STYLES[type]) });
3686
+ return /* @__PURE__ */ jsx61(Icon, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_STYLES[type]) });
3615
3687
  }
3616
3688
  function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAction, className }) {
3617
3689
  const hasAction = !!(actionLabel && onAction);
3618
3690
  return /* @__PURE__ */ jsxs43("div", { className: pillClassName(type, hasAction, className), children: [
3619
3691
  /* @__PURE__ */ jsxs43("div", { className: "flex items-center gap-2 shrink-0", children: [
3620
- leadingIcon && /* @__PURE__ */ jsx60(LeadingIcon, { type }),
3621
- /* @__PURE__ */ jsx60("span", { className: LABEL_CLASSES, children: label })
3692
+ leadingIcon && /* @__PURE__ */ jsx61(LeadingIcon, { type }),
3693
+ /* @__PURE__ */ jsx61("span", { className: LABEL_CLASSES, children: label })
3622
3694
  ] }),
3623
- hasAction && /* @__PURE__ */ jsx60(Button, { variant: "outlined", size: "small", onClick: onAction, children: actionLabel })
3695
+ hasAction && /* @__PURE__ */ jsx61(Button, { variant: "outlined", size: "small", onClick: onAction, children: actionLabel })
3624
3696
  ] });
3625
3697
  }
3626
3698
  var ToastContext = createContext6(null);
@@ -3641,9 +3713,9 @@ function ToastProvider({ children }) {
3641
3713
  }),
3642
3714
  [manager]
3643
3715
  );
3644
- return /* @__PURE__ */ jsx60(ToastContext.Provider, { value, children: /* @__PURE__ */ jsxs43(BaseToast.Provider, { toastManager: manager, limit: VISIBLE_TOASTS, children: [
3716
+ return /* @__PURE__ */ jsx61(ToastContext.Provider, { value, children: /* @__PURE__ */ jsxs43(BaseToast.Provider, { toastManager: manager, limit: VISIBLE_TOASTS, children: [
3645
3717
  children,
3646
- /* @__PURE__ */ jsx60(BaseToast.Portal, { children: /* @__PURE__ */ jsx60(BaseToast.Viewport, { className: "fixed bottom-4 left-4 z-[100] flex flex-col-reverse items-start gap-2 pointer-events-none", children: /* @__PURE__ */ jsx60(ToastList, {}) }) })
3718
+ /* @__PURE__ */ jsx61(BaseToast.Portal, { children: /* @__PURE__ */ jsx61(BaseToast.Viewport, { className: "fixed bottom-4 left-4 z-[100] flex flex-col-reverse items-start gap-2 pointer-events-none", children: /* @__PURE__ */ jsx61(ToastList, {}) }) })
3647
3719
  ] }) });
3648
3720
  }
3649
3721
  function ToastList() {
@@ -3659,17 +3731,17 @@ function ToastList() {
3659
3731
  className: pillClassName(type, !!actionLabel, "pointer-events-auto data-[limited]:hidden"),
3660
3732
  children: [
3661
3733
  /* @__PURE__ */ jsxs43("div", { className: "flex items-center gap-2 shrink-0", children: [
3662
- leadingIcon && /* @__PURE__ */ jsx60(LeadingIcon, { type }),
3663
- /* @__PURE__ */ jsx60(BaseToast.Title, { render: /* @__PURE__ */ jsx60("span", {}), className: LABEL_CLASSES })
3734
+ leadingIcon && /* @__PURE__ */ jsx61(LeadingIcon, { type }),
3735
+ /* @__PURE__ */ jsx61(BaseToast.Title, { render: /* @__PURE__ */ jsx61("span", {}), className: LABEL_CLASSES })
3664
3736
  ] }),
3665
- actionLabel && /* @__PURE__ */ jsx60(
3737
+ actionLabel && /* @__PURE__ */ jsx61(
3666
3738
  BaseToast.Action,
3667
3739
  {
3668
3740
  onClick: () => {
3669
3741
  onAction?.();
3670
3742
  close(toast.id);
3671
3743
  },
3672
- render: /* @__PURE__ */ jsx60(Button, { variant: "outlined", size: "small", children: actionLabel })
3744
+ render: /* @__PURE__ */ jsx61(Button, { variant: "outlined", size: "small", children: actionLabel })
3673
3745
  }
3674
3746
  )
3675
3747
  ]
@@ -3748,6 +3820,7 @@ export {
3748
3820
  MenuSub,
3749
3821
  NavItem,
3750
3822
  NavTree,
3823
+ NavTreeSection,
3751
3824
  Panel,
3752
3825
  Person,
3753
3826
  PersonTrigger,
@@ -3783,6 +3856,7 @@ export {
3783
3856
  Tooltip,
3784
3857
  TooltipProvider,
3785
3858
  TopBar,
3859
+ UnreadDot,
3786
3860
  WithTooltip,
3787
3861
  cn,
3788
3862
  hueFor,