@estiva-app/ui 0.66.0 → 0.68.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 (54) hide show
  1. package/dist/BlockNodes.d.ts +12 -1
  2. package/dist/BlockNodes.d.ts.map +1 -1
  3. package/dist/EditorTableControls.d.ts +38 -0
  4. package/dist/EditorTableControls.d.ts.map +1 -0
  5. package/dist/LinkChips.d.ts +82 -0
  6. package/dist/LinkChips.d.ts.map +1 -0
  7. package/dist/NavItem.d.ts +11 -1
  8. package/dist/NavItem.d.ts.map +1 -1
  9. package/dist/ReferenceCards.d.ts +77 -0
  10. package/dist/ReferenceCards.d.ts.map +1 -0
  11. package/dist/ReferenceMenu.d.ts +18 -0
  12. package/dist/ReferenceMenu.d.ts.map +1 -0
  13. package/dist/RichText.d.ts +9 -2
  14. package/dist/RichText.d.ts.map +1 -1
  15. package/dist/SelectionToolbar.d.ts +9 -1
  16. package/dist/SelectionToolbar.d.ts.map +1 -1
  17. package/dist/editor.d.ts +4 -0
  18. package/dist/editor.d.ts.map +1 -1
  19. package/dist/editor.js +750 -27
  20. package/dist/editor.js.map +4 -4
  21. package/dist/index.js +62 -33
  22. package/dist/index.js.map +4 -4
  23. package/package.json +2 -1
  24. package/registry.json +414 -4
  25. package/src/BlockNodes.test.tsx +2 -1
  26. package/src/BlockNodes.tsx +51 -9
  27. package/src/EditorTableControls.mdx +78 -0
  28. package/src/EditorTableControls.stories.tsx +63 -0
  29. package/src/EditorTableControls.test.tsx +288 -0
  30. package/src/EditorTableControls.tsx +255 -0
  31. package/src/LinkChips.mdx +57 -0
  32. package/src/LinkChips.stories.tsx +67 -0
  33. package/src/LinkChips.test.tsx +178 -0
  34. package/src/LinkChips.ts +243 -0
  35. package/src/NavItem.mdx +10 -0
  36. package/src/NavItem.stories.tsx +23 -1
  37. package/src/NavItem.test.tsx +36 -0
  38. package/src/NavItem.tsx +40 -16
  39. package/src/ReferenceCards.mdx +79 -0
  40. package/src/ReferenceCards.stories.tsx +64 -0
  41. package/src/ReferenceCards.test.tsx +143 -0
  42. package/src/ReferenceCards.ts +225 -0
  43. package/src/ReferenceMenu.mdx +75 -0
  44. package/src/ReferenceMenu.stories.tsx +59 -0
  45. package/src/ReferenceMenu.test.tsx +289 -0
  46. package/src/ReferenceMenu.tsx +346 -0
  47. package/src/RichText.test.tsx +17 -0
  48. package/src/RichText.tsx +36 -4
  49. package/src/SelectionToolbar.tsx +10 -2
  50. package/src/editor.ts +14 -0
  51. package/src/pages.test.ts +3 -0
  52. package/src/registry/registry.test.ts +7 -7
  53. package/src/registry/siblings.test.ts +2 -2
  54. package/stories/Choosing.mdx +4 -0
package/dist/index.js CHANGED
@@ -2421,6 +2421,9 @@ function InlineChip({ tone = "neutral", icon, href, className, children, ...prop
2421
2421
  return /* @__PURE__ */ jsx33("a", { href, className: classes, ...props, onClick: inApp, children: body });
2422
2422
  }
2423
2423
 
2424
+ // src/RichText.tsx
2425
+ import { Fragment as Fragment8 } from "react";
2426
+
2424
2427
  // src/codeColours.ts
2425
2428
  import { createElement } from "react";
2426
2429
  import { common, createLowlight } from "lowlight";
@@ -2438,7 +2441,7 @@ function codeColours(text, language) {
2438
2441
  }
2439
2442
 
2440
2443
  // src/RichText.tsx
2441
- import { Fragment as Fragment8, jsx as jsx34, jsxs as jsxs25 } from "react/jsx-runtime";
2444
+ import { Fragment as Fragment9, jsx as jsx34, jsxs as jsxs25 } from "react/jsx-runtime";
2442
2445
  var RICH_TEXT_CLASSES = {
2443
2446
  /* Blocks 8px apart (Katerina, 9 October; 4 before). A list's own items stay together. */
2444
2447
  base: "flex flex-col gap-2 break-words text-text-secondary",
@@ -2466,7 +2469,8 @@ var RICH_TEXT_CLASSES = {
2466
2469
  syntax: "[&_:is(.hljs-keyword,.hljs-doctag,.hljs-type,.hljs-template-tag,.hljs-template-variable)]:text-syntax-keyword [&_.hljs-title]:text-syntax-function [&_:is(.hljs-number,.hljs-literal,.hljs-attr,.hljs-attribute,.hljs-meta,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id)]:text-syntax-constant [&_:is(.hljs-string,.hljs-regexp)]:text-syntax-string [&_:is(.hljs-built_in,.hljs-symbol)]:text-syntax-variable [&_.hljs-comment]:text-syntax-comment [&_:is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo)]:text-syntax-tag",
2467
2470
  /* A divider splits the text in two: 16px either side (Katerina, 9 October; 4 before). */
2468
2471
  divider: "[&_hr]:my-2 [&_hr]:border-border-default",
2469
- table: "[&_:is(th,td)>p]:m-0 [&_:is(th,td)]:border [&_:is(th,td)]:border-border-default [&_:is(th,td)]:px-2 [&_:is(th,td)]:py-1 [&_:is(th,td)]:text-left [&_:is(th,td)]:align-top [&_table]:w-full [&_table]:border-collapse [&_th]:bg-bg-code [&_th]:font-medium [&_th]:text-text-primary",
2472
+ /* A table 16px above and below, as a divider (Katerina, 14 October; 8 before): the editor's row ➕ sits in that room, not over the next line. */
2473
+ table: "[&_:is(th,td)>p]:m-0 [&_:is(th,td)]:border [&_:is(th,td)]:border-border-default [&_:is(th,td)]:px-2 [&_:is(th,td)]:py-1 [&_:is(th,td)]:text-left [&_:is(th,td)]:align-top [&_table]:my-2 [&_table]:w-full [&_table]:border-collapse [&_th]:bg-bg-code [&_th]:font-medium [&_th]:text-text-primary",
2470
2474
  /* Bold in the heading colour, not only heavier. An underlined word draws a faint line, so it does not read as a link (Katerina, 9 October). */
2471
2475
  marks: "[&_em]:italic [&_strong]:font-semibold [&_strong]:text-text-primary [&_u]:underline [&_u]:decoration-text-muted [&_u]:underline-offset-2",
2472
2476
  /* Lines 1.5 apart (Katerina, 9 October; 1.4 before). */
@@ -2509,7 +2513,7 @@ function calloutLook(tone) {
2509
2513
  return CALLOUT_TONES.has(named2) ? named2 : "note";
2510
2514
  }
2511
2515
  function Runs({ runs, draw: draw2 }) {
2512
- return /* @__PURE__ */ jsx34(Fragment8, { children: (runs ?? []).map((run, i) => {
2516
+ return /* @__PURE__ */ jsx34(Fragment9, { children: (runs ?? []).map((run, i) => {
2513
2517
  if (run.reference) return /* @__PURE__ */ jsx34("span", { children: draw2.renderReference(run.reference, run) }, i);
2514
2518
  if (run.marks.includes("code")) {
2515
2519
  return /* @__PURE__ */ jsx34("code", { children: run.text }, i);
@@ -2539,8 +2543,12 @@ function Table({ block, draw: draw2 }) {
2539
2543
  colSpan: colspan && colspan > 1 ? colspan : void 0,
2540
2544
  rowSpan: rowspan && rowspan > 1 ? rowspan : void 0,
2541
2545
  children: [
2542
- (cell.children ?? []).map((child, j) => /* @__PURE__ */ jsx34(Runs, { runs: child.inline, draw: draw2 }, j)),
2543
- /* @__PURE__ */ jsx34(Runs, { runs: cell.inline, draw: draw2 })
2546
+ (cell.children ?? []).map((child, j) => /* @__PURE__ */ jsxs25(Fragment8, { children: [
2547
+ /* @__PURE__ */ jsx34(Runs, { runs: child.inline, draw: draw2 }),
2548
+ draw2.renderAfter?.(child)
2549
+ ] }, j)),
2550
+ /* @__PURE__ */ jsx34(Runs, { runs: cell.inline, draw: draw2 }),
2551
+ draw2.renderAfter?.(cell)
2544
2552
  ]
2545
2553
  },
2546
2554
  cell.id ?? i
@@ -2558,6 +2566,7 @@ function ListItem({ item, draw: draw2, below }) {
2558
2566
  if (typeof item.checked !== "boolean") {
2559
2567
  return /* @__PURE__ */ jsxs25("li", { "data-block-id": item.id, children: [
2560
2568
  /* @__PURE__ */ jsx34(Runs, { runs: item.inline, draw: draw2 }),
2569
+ draw2.renderAfter?.(item),
2561
2570
  below
2562
2571
  ] });
2563
2572
  }
@@ -2569,6 +2578,7 @@ function ListItem({ item, draw: draw2, below }) {
2569
2578
  /* @__PURE__ */ jsx34("span", { "data-checklist-box": "", ...control ? {} : { role: "checkbox", "aria-checked": item.checked, "aria-readonly": true, "aria-label": name }, children: /* @__PURE__ */ jsx34(Checkbox, { checked: item.checked, onChange: control, "aria-label": name }) }),
2570
2579
  /* @__PURE__ */ jsxs25("div", { children: [
2571
2580
  /* @__PURE__ */ jsx34("p", { children: /* @__PURE__ */ jsx34(Runs, { runs: item.inline, draw: draw2 }) }),
2581
+ draw2.renderAfter?.(item),
2572
2582
  below
2573
2583
  ] })
2574
2584
  ] });
@@ -2590,6 +2600,16 @@ function nestItems(items) {
2590
2600
  function Block({ block, draw: draw2 }) {
2591
2601
  const own = draw2.renderBlock?.(block);
2592
2602
  if (own !== void 0) return own;
2603
+ const drawn = Drawn({ block, draw: draw2 });
2604
+ const lineOfWords = block.type === "paragraph" || block.type === "heading" || block.type === "blockquote";
2605
+ const after = lineOfWords ? draw2.renderAfter?.(block) : void 0;
2606
+ if (after === void 0 || after === null || after === false) return drawn;
2607
+ return /* @__PURE__ */ jsxs25(Fragment9, { children: [
2608
+ drawn,
2609
+ after
2610
+ ] });
2611
+ }
2612
+ function Drawn({ block, draw: draw2 }) {
2593
2613
  const runs = /* @__PURE__ */ jsx34(Runs, { runs: block.inline, draw: draw2 });
2594
2614
  switch (block.type) {
2595
2615
  case "heading": {
@@ -2637,10 +2657,11 @@ function RichText({
2637
2657
  renderText = (text) => text,
2638
2658
  renderReference = (_reference, run) => run.text,
2639
2659
  renderBlock,
2660
+ renderAfter,
2640
2661
  onTick
2641
2662
  }) {
2642
2663
  if (blocks.length === 0) return null;
2643
- const draw2 = { renderText, renderReference, renderBlock, onTick };
2664
+ const draw2 = { renderText, renderReference, renderBlock, renderAfter, onTick };
2644
2665
  return /* @__PURE__ */ jsx34("div", { "data-rich-text": true, className: richTextClassName(size), children: blocks.map((block, i) => /* @__PURE__ */ jsx34(Block, { block, draw: draw2 }, block.id ?? i)) });
2645
2666
  }
2646
2667
 
@@ -2737,7 +2758,7 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
2737
2758
  }
2738
2759
 
2739
2760
  // src/IdentityMenu.tsx
2740
- import { Fragment as Fragment9, jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
2761
+ import { Fragment as Fragment10, jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
2741
2762
  function IdentityPanel({ className, onClose = () => {
2742
2763
  }, ...rest }) {
2743
2764
  return /* @__PURE__ */ jsx38(MenuPanel, { className: cn("w-72", className), children: /* @__PURE__ */ jsx38(IdentityRows, { ...rest, onClose }) });
@@ -2748,7 +2769,7 @@ function IdentityRows({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, on
2748
2769
  action();
2749
2770
  };
2750
2771
  const appRows = typeof children === "function" ? children(onClose) : children;
2751
- return /* @__PURE__ */ jsxs28(Fragment9, { children: [
2772
+ return /* @__PURE__ */ jsxs28(Fragment10, { children: [
2752
2773
  /* @__PURE__ */ jsxs28(MenuSection, { label: signedIn ? "Signed in as" : "Acting as", children: [
2753
2774
  /* @__PURE__ */ jsx38(MenuRow, { children: /* @__PURE__ */ jsx38("div", { className: "flex min-w-0 text-body-2-strong", children: /* @__PURE__ */ jsx38(Person, { name: me.name, picture: me.picture, fallback: "Anonymous", size: 28 }) }) }),
2754
2775
  /* @__PURE__ */ jsx38(Note, { children: signedIn ? "Your Estiva ID. The same person you are in every Estiva app." : "A key held by this browser only. Nobody else knows who you are." }),
@@ -2757,14 +2778,14 @@ function IdentityRows({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, on
2757
2778
  " \xB7 known to Estiva, never published to the relay"
2758
2779
  ] })
2759
2780
  ] }),
2760
- relayUrl && /* @__PURE__ */ jsxs28(Fragment9, { children: [
2781
+ relayUrl && /* @__PURE__ */ jsxs28(Fragment10, { children: [
2761
2782
  /* @__PURE__ */ jsx38(Divider, { className: "mx-0 my-2" }),
2762
2783
  /* @__PURE__ */ jsxs28(MenuSection, { label: "Workspace", children: [
2763
2784
  /* @__PURE__ */ jsx38(MenuRow, { children: /* @__PURE__ */ jsx38("span", { className: "break-all font-mono text-caption text-text-primary", children: relayUrl }) }),
2764
2785
  /* @__PURE__ */ jsx38(Note, { children: "Everyone on this relay shares this workspace, in every app." })
2765
2786
  ] })
2766
2787
  ] }),
2767
- appRows && /* @__PURE__ */ jsxs28(Fragment9, { children: [
2788
+ appRows && /* @__PURE__ */ jsxs28(Fragment10, { children: [
2768
2789
  /* @__PURE__ */ jsx38(Divider, { className: "mx-0 my-2" }),
2769
2790
  appRows
2770
2791
  ] }),
@@ -3019,7 +3040,7 @@ var Textarea = forwardRef3(function Textarea2({ className, ...props }, ref) {
3019
3040
 
3020
3041
  // src/ConfirmDialog.tsx
3021
3042
  import { useState as useState6 } from "react";
3022
- import { Fragment as Fragment10, jsx as jsx43, jsxs as jsxs30 } from "react/jsx-runtime";
3043
+ import { Fragment as Fragment11, jsx as jsx43, jsxs as jsxs30 } from "react/jsx-runtime";
3023
3044
  function ConfirmDialog({ title, children, confirmLabel, destructive = false, onConfirm, onClose }) {
3024
3045
  const [busy, setBusy] = useState6(false);
3025
3046
  const confirm = async () => {
@@ -3037,7 +3058,7 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
3037
3058
  alert: true,
3038
3059
  title,
3039
3060
  onClose,
3040
- footer: /* @__PURE__ */ jsxs30(Fragment10, { children: [
3061
+ footer: /* @__PURE__ */ jsxs30(Fragment11, { children: [
3041
3062
  /* @__PURE__ */ jsx43(Button, { variant: "muted", onClick: onClose, disabled: busy, children: "Cancel" }),
3042
3063
  /* @__PURE__ */ jsx43(Button, { variant: destructive ? "destructive" : "primary", onClick: () => void confirm(), disabled: busy, children: confirmLabel })
3043
3064
  ] }),
@@ -3209,7 +3230,7 @@ function ProgressBar({ value, max, label, variant = "default", className }) {
3209
3230
  }
3210
3231
 
3211
3232
  // src/Breadcrumb.tsx
3212
- import { Fragment as Fragment11, useCallback as useCallback3, useLayoutEffect as useLayoutEffect4, useRef as useRef10, useState as useState8 } from "react";
3233
+ import { Fragment as Fragment12, useCallback as useCallback3, useLayoutEffect as useLayoutEffect4, useRef as useRef10, useState as useState8 } from "react";
3213
3234
  import { jsx as jsx47, jsxs as jsxs31 } from "react/jsx-runtime";
3214
3235
  function Breadcrumb({ items, className }) {
3215
3236
  const navRef = useRef10(null);
@@ -3253,7 +3274,7 @@ function Breadcrumb({ items, className }) {
3253
3274
  // truncates and is measured.
3254
3275
  /* @__PURE__ */ jsx47("span", { className: cn("flex min-w-0", item.mono && "shrink-0 font-mono text-caption", tone, "hover:text-text-primary"), children: /* @__PURE__ */ jsx47(Link, { ref: setLabelRef, variant: "plain", truncate: true, href: item.href, onClick: item.onClick, children: item.label }) })
3255
3276
  ) : /* @__PURE__ */ jsx47("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
3256
- return /* @__PURE__ */ jsxs31(Fragment11, { children: [
3277
+ return /* @__PURE__ */ jsxs31(Fragment12, { children: [
3257
3278
  index > 0 && /* @__PURE__ */ jsx47("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: "/" }),
3258
3279
  item.icon && /* @__PURE__ */ jsx47("span", { "aria-hidden": "true", className: cn("flex shrink-0", tone), children: item.icon }),
3259
3280
  truncated.has(index) ? (
@@ -3288,6 +3309,7 @@ function NavItem({
3288
3309
  icon,
3289
3310
  menu,
3290
3311
  menuLabel,
3312
+ action,
3291
3313
  hint,
3292
3314
  unread = false,
3293
3315
  urgent = false,
@@ -3308,9 +3330,10 @@ function NavItem({
3308
3330
  setOpenState(next);
3309
3331
  onOpenChange?.(next);
3310
3332
  };
3311
- const besides = menu != null || hasRows;
3333
+ const buttons = (menu != null ? 1 : 0) + (action ? 1 : 0);
3334
+ const besides = buttons > 0 || hasRows;
3312
3335
  const mark = unread ? urgent ? /* @__PURE__ */ jsx49(UrgentMark, {}) : /* @__PURE__ */ jsx49(UnreadDot, {}) : null;
3313
- const stepsAside = menu != null && "transition-opacity group-hover/nav:opacity-0 group-has-[:focus-visible]/nav:opacity-0 group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:opacity-0 [@media(hover:none)]:opacity-0";
3336
+ const stepsAside = buttons > 0 && "transition-opacity group-hover/nav:opacity-0 group-has-[:focus-visible]/nav:opacity-0 group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:opacity-0 [@media(hover:none)]:opacity-0";
3314
3337
  const row = /* @__PURE__ */ jsxs32(
3315
3338
  "a",
3316
3339
  {
@@ -3327,7 +3350,7 @@ function NavItem({
3327
3350
  // look while the ⋮ is pointed at and while its menu is open. The same
3328
3351
  // for the arrow.
3329
3352
  besides && !active && "group-hover/nav:bg-bg-hover group-hover/nav:text-text-primary",
3330
- menu != null && !active && "group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:bg-bg-hover group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:text-text-primary",
3353
+ buttons > 0 && !active && "group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:bg-bg-hover group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:text-text-primary",
3331
3354
  className
3332
3355
  ),
3333
3356
  ...props,
@@ -3347,6 +3370,9 @@ function NavItem({
3347
3370
  /* @__PURE__ */ jsx49("span", { className: cn("flex-1 truncate", unread && "font-medium"), children: label }),
3348
3371
  hint != null && // Takes room only while it shows, so the label gives way to it.
3349
3372
  /* @__PURE__ */ jsx49("span", { className: "hidden shrink-0 items-center group-hover/nav:flex group-has-[:focus-visible]/nav:flex", children: hint }),
3373
+ buttons > 1 && // Two buttons: the second one's room (24px and the 4px between them, the
3374
+ // row's 8px gap making up the rest), kept only while they show.
3375
+ /* @__PURE__ */ jsx49("span", { "aria-hidden": "true", className: "hidden w-5 shrink-0 group-hover/nav:block group-has-[:focus-visible]/nav:block group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:block [@media(hover:none)]:block" }),
3350
3376
  count ? /* @__PURE__ */ jsx49(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx49(
3351
3377
  "span",
3352
3378
  {
@@ -3364,7 +3390,7 @@ function NavItem({
3364
3390
  // of the ⋮ (8px from the edge to the 16px box), one column with a
3365
3391
  // folder heading's dot and ⋮ (Katerina, 5 October).
3366
3392
  /* @__PURE__ */ jsx49("span", { className: cn("-mr-1 flex shrink-0", stepsAside), children: mark })
3367
- ) : menu != null && !count ? (
3393
+ ) : buttons > 0 && !count ? (
3368
3394
  // Nothing at the right, but a menu: no room is kept at rest, so the
3369
3395
  // label has the row's whole width; while the ⋮ shows, its 24px slot
3370
3396
  // is kept, so a long label ends before it instead of running under it.
@@ -3393,7 +3419,7 @@ function NavItem({
3393
3419
  children: /* @__PURE__ */ jsx49(IconChevronRight2, { size: 16, stroke: 1.5, className: cn("transition-transform duration-150", open && "rotate-90") })
3394
3420
  }
3395
3421
  ) }),
3396
- menu != null && // `right-1`: the 24px button's 16px icon ends 8px from the row's
3422
+ buttons > 0 && // `right-1`: the 24px button's 16px icon ends 8px from the row's
3397
3423
  // edge, where a count ends (px-2) — and where a SectionHeader's
3398
3424
  // actions and ⋮ end, so a folder's ⋮ and its rows' ⋮ are one column
3399
3425
  // (Katerina, 5 October: the rows move to the heading, not the
@@ -3401,14 +3427,17 @@ function NavItem({
3401
3427
  // right of the slot's centre.
3402
3428
  // On a screen with no hover it always shows: nothing there can point
3403
3429
  // at the row, and the menu has no other way in.
3404
- /* @__PURE__ */ jsx49("div", { "data-nav-menu": true, className: cn("absolute inset-y-0 right-1 flex items-center", "opacity-0 transition-opacity group-hover/nav:opacity-100 group-has-[:focus-visible]/nav:opacity-100 has-[[aria-expanded=true]]:opacity-100 [@media(hover:none)]:opacity-100"), children: /* @__PURE__ */ jsx49(
3405
- Menu,
3406
- {
3407
- align: "right",
3408
- trigger: /* @__PURE__ */ jsx49(IconButton, { tooltip: "More options", "aria-label": name, children: /* @__PURE__ */ jsx49(IconDotsVertical, { size: 16, stroke: 1.5 }) }),
3409
- children: menu
3410
- }
3411
- ) })
3430
+ /* @__PURE__ */ jsxs32("div", { "data-nav-menu": true, className: cn("absolute inset-y-0 right-1 flex items-center gap-1", "opacity-0 transition-opacity group-hover/nav:opacity-100 group-has-[:focus-visible]/nav:opacity-100 has-[[aria-expanded=true]]:opacity-100 [@media(hover:none)]:opacity-100"), children: [
3431
+ action && /* @__PURE__ */ jsx49(IconButton, { tooltip: action.tooltip, "aria-label": `${action.tooltip}: ${label}`, onClick: action.onClick, children: action.icon }),
3432
+ menu != null && /* @__PURE__ */ jsx49(
3433
+ Menu,
3434
+ {
3435
+ align: "right",
3436
+ trigger: /* @__PURE__ */ jsx49(IconButton, { tooltip: "More options", "aria-label": name, children: /* @__PURE__ */ jsx49(IconDotsVertical, { size: 16, stroke: 1.5 }) }),
3437
+ children: menu
3438
+ }
3439
+ )
3440
+ ] })
3412
3441
  ] });
3413
3442
  if (!hasRows) return line;
3414
3443
  return /* @__PURE__ */ jsxs32(Collapsible.Root, { open, onOpenChange: setOpen, className: "flex shrink-0 flex-col", children: [
@@ -3438,7 +3467,7 @@ import { Collapsible as Collapsible2 } from "@base-ui/react/collapsible";
3438
3467
  // src/SectionHeader.tsx
3439
3468
  import { IconChevronRight as IconChevronRight3, IconDotsVertical as IconDotsVertical2 } from "@tabler/icons-react";
3440
3469
  import { useRender } from "@base-ui/react/use-render";
3441
- import { Fragment as Fragment12, jsx as jsx50, jsxs as jsxs33 } from "react/jsx-runtime";
3470
+ import { Fragment as Fragment13, jsx as jsx50, jsxs as jsxs33 } from "react/jsx-runtime";
3442
3471
  function SectionHeader({ title, chevron = false, icon, isExpanded = true, onToggle, trailing, count, actions, menu, menuLabel, showActions = "hover", hover = "fill", look = "heading", render, className }) {
3443
3472
  const row = look === "row";
3444
3473
  const acts = actions && actions.length > 0 || menu != null;
@@ -3452,7 +3481,7 @@ function SectionHeader({ title, chevron = false, icon, isExpanded = true, onTogg
3452
3481
  // row itself carried the click.
3453
3482
  // An icon is a row's: NavItem's 8px after it.
3454
3483
  className: cn("flex h-full min-w-0 flex-1 items-center text-left", row || chevron || icon != null ? "gap-2" : "gap-1"),
3455
- children: /* @__PURE__ */ jsxs33(Fragment12, { children: [
3484
+ children: /* @__PURE__ */ jsxs33(Fragment13, { children: [
3456
3485
  icon != null ? /* @__PURE__ */ jsxs33("span", { className: "relative flex size-4 shrink-0 items-center justify-center text-text-secondary", children: [
3457
3486
  /* @__PURE__ */ jsx50("span", { className: cn("flex", chevron && "transition-opacity group-hover:opacity-0 group-has-[:focus-visible]:opacity-0"), children: icon }),
3458
3487
  chevron && /* @__PURE__ */ jsx50(
@@ -3566,7 +3595,7 @@ function CollapsibleSection({ title, defaultOpen = true, open: openProp, onOpenC
3566
3595
  }
3567
3596
 
3568
3597
  // src/NavTree.tsx
3569
- import { Fragment as Fragment13, jsx as jsx52, jsxs as jsxs35 } from "react/jsx-runtime";
3598
+ import { Fragment as Fragment14, jsx as jsx52, jsxs as jsxs35 } from "react/jsx-runtime";
3570
3599
  var NAV_TREE_INTENT_MS = 150;
3571
3600
  function holds(nodes, id) {
3572
3601
  return !!nodes && id !== void 0 && nodes.some((node) => node.id === id || holds(node.children ?? null, id));
@@ -3656,7 +3685,7 @@ function NavTreeSection({ id, title, message, loading = false, pending, trailing
3656
3685
  );
3657
3686
  }
3658
3687
  function NavTree({ title, titleActions, groups, children, selected, onSelect, emptyMessage = "", storageKey }) {
3659
- return /* @__PURE__ */ jsxs35(Fragment13, { children: [
3688
+ return /* @__PURE__ */ jsxs35(Fragment14, { children: [
3660
3689
  title && /* @__PURE__ */ jsx52(SectionHeader, { title, look: "quiet", showActions: "always", actions: titleActions, className: "mt-4 shrink-0" }),
3661
3690
  groups === void 0 ? children : groups === null ? /* @__PURE__ */ jsx52(SkeletonList, { rows: 5 }) : groups.length === 0 ? (
3662
3691
  // No words, no line: an app that says nothing when it has no groups.
@@ -4176,7 +4205,7 @@ function ListColumn(props) {
4176
4205
 
4177
4206
  // src/Tabs.tsx
4178
4207
  import { Tabs as BaseTabs } from "@base-ui/react/tabs";
4179
- import { Fragment as Fragment14, jsx as jsx66, jsxs as jsxs46 } from "react/jsx-runtime";
4208
+ import { Fragment as Fragment15, jsx as jsx66, jsxs as jsxs46 } from "react/jsx-runtime";
4180
4209
  function Tabs({ tabs, active, onChange, size = "default", className, ...aria }) {
4181
4210
  return /* @__PURE__ */ jsx66(
4182
4211
  BaseTabs.Root,
@@ -4210,7 +4239,7 @@ function Tabs({ tabs, active, onChange, size = "default", className, ...aria })
4210
4239
  tab.label,
4211
4240
  tab.count !== void 0 ? /* @__PURE__ */ jsx66("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
4212
4241
  ] }),
4213
- tab.unread ? /* @__PURE__ */ jsxs46(Fragment14, { children: [
4242
+ tab.unread ? /* @__PURE__ */ jsxs46(Fragment15, { children: [
4214
4243
  /* @__PURE__ */ jsx66(UnreadDot, { className: "h-auto w-auto" }),
4215
4244
  /* @__PURE__ */ jsx66("span", { className: "sr-only", children: "new" })
4216
4245
  ] }) : null