@estiva-app/ui 0.4.0 → 0.6.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 (58) hide show
  1. package/dist/AppShell.d.ts.map +1 -1
  2. package/dist/Avatar.d.ts.map +1 -1
  3. package/dist/AvatarGroup.d.ts +14 -5
  4. package/dist/AvatarGroup.d.ts.map +1 -1
  5. package/dist/ChipInput.d.ts.map +1 -1
  6. package/dist/Divider.d.ts.map +1 -1
  7. package/dist/IconButton.d.ts +4 -1
  8. package/dist/IconButton.d.ts.map +1 -1
  9. package/dist/IdentityMenu.d.ts +6 -1
  10. package/dist/IdentityMenu.d.ts.map +1 -1
  11. package/dist/Kbd.d.ts +27 -0
  12. package/dist/Kbd.d.ts.map +1 -0
  13. package/dist/Menu.d.ts +115 -11
  14. package/dist/Menu.d.ts.map +1 -1
  15. package/dist/SearchInput.d.ts +1 -1
  16. package/dist/SearchInput.d.ts.map +1 -1
  17. package/dist/Select.d.ts +2 -31
  18. package/dist/Select.d.ts.map +1 -1
  19. package/dist/Tooltip.d.ts +8 -2
  20. package/dist/Tooltip.d.ts.map +1 -1
  21. package/dist/fit.d.ts +98 -0
  22. package/dist/fit.d.ts.map +1 -0
  23. package/dist/index.d.ts +3 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +441 -232
  26. package/dist/index.js.map +4 -4
  27. package/package.json +1 -1
  28. package/src/AppShell.stories.tsx +2 -2
  29. package/src/AppShell.tsx +8 -1
  30. package/src/Avatar.tsx +14 -1
  31. package/src/AvatarGroup.stories.tsx +5 -0
  32. package/src/AvatarGroup.tsx +36 -13
  33. package/src/ChipInput.tsx +15 -3
  34. package/src/Divider.tsx +5 -1
  35. package/src/IconButton.tsx +5 -1
  36. package/src/IdentityMenu.tsx +14 -4
  37. package/src/Kbd.mdx +77 -0
  38. package/src/Kbd.stories.tsx +58 -0
  39. package/src/Kbd.tsx +42 -0
  40. package/src/Menu.fit.test.ts +90 -0
  41. package/src/Menu.mdx +8 -1
  42. package/src/Menu.stories.tsx +26 -3
  43. package/src/Menu.tsx +310 -34
  44. package/src/MenuItem.mdx +1 -1
  45. package/src/MenuItem.stories.tsx +5 -5
  46. package/src/SearchInput.mdx +2 -2
  47. package/src/SearchInput.stories.tsx +2 -2
  48. package/src/SearchInput.tsx +3 -6
  49. package/src/Select.fit.test.ts +12 -8
  50. package/src/Select.tsx +8 -32
  51. package/src/Tooltip.mdx +9 -0
  52. package/src/Tooltip.stories.tsx +15 -0
  53. package/src/Tooltip.tsx +19 -5
  54. package/src/TopBar.mdx +1 -1
  55. package/src/TopBar.stories.tsx +2 -2
  56. package/src/fit.ts +94 -0
  57. package/src/index.ts +3 -1
  58. package/tailwind-preset.js +9 -1
package/dist/index.js CHANGED
@@ -75,16 +75,25 @@ function AppShell({ variant = "solid", menu, logo, search, identity, banner, nav
75
75
  ] })
76
76
  ] });
77
77
  }
78
- return /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-0 flex-col bg-bg-base text-text-primary", children: [
79
- bar,
80
- /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1", children: [
81
- nav,
82
- /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [
83
- banner,
84
- /* @__PURE__ */ jsx2("main", { className: "min-w-0 flex-1 overflow-y-auto", children })
78
+ return (
79
+ /* `relative overflow-hidden` is the seal the floating manner already has,
80
+ and it takes both halves: an absolutely positioned descendant with no
81
+ positioned ancestor belongs to the *viewport*, so a scroll container
82
+ never clips it and the document itself gains its position as scroll
83
+ range — the whole page scrolls, navigation and top bar included.
84
+ `relative` claims such strays for the shell; `overflow-hidden` clips
85
+ them at its edge. Either alone seals nothing. */
86
+ /* @__PURE__ */ jsxs2("div", { className: "relative flex h-full min-h-0 flex-col overflow-hidden bg-bg-base text-text-primary", children: [
87
+ bar,
88
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1", children: [
89
+ nav,
90
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [
91
+ banner,
92
+ /* @__PURE__ */ jsx2("main", { className: "min-w-0 flex-1 overflow-y-auto", children })
93
+ ] })
85
94
  ] })
86
95
  ] })
87
- ] });
96
+ );
88
97
  }
89
98
 
90
99
  // src/Avatar.tsx
@@ -109,7 +118,7 @@ function Avatar({ src, name, alt = "", size = 36, className }) {
109
118
  return /* @__PURE__ */ jsx3("div", { className: cn("rounded-sm overflow-hidden shrink-0 bg-bg-inset", className), style: { width: size, height: size }, children: picture ? /* @__PURE__ */ jsx3("img", { src: picture, alt: alt || name || "", className: "w-full h-full object-cover", onError: () => setBroken(true) }) : label ? /* @__PURE__ */ jsx3(
110
119
  "div",
111
120
  {
112
- className: "w-full h-full flex items-center justify-center font-semibold",
121
+ className: "w-full h-full flex items-center justify-center font-semibold leading-none",
113
122
  style: {
114
123
  color: "#08121c",
115
124
  fontSize: Math.round(size * 0.36),
@@ -122,18 +131,25 @@ function Avatar({ src, name, alt = "", size = 36, className }) {
122
131
 
123
132
  // src/AvatarGroup.tsx
124
133
  import { jsx as jsx4 } from "react/jsx-runtime";
125
- function AvatarGroup({ members }) {
134
+ function AvatarGroup({ members, size = 24 }) {
126
135
  const visible = members.slice(0, 3);
127
- return /* @__PURE__ */ jsx4("div", { className: "flex items-center pr-2", children: visible.map((member, i) => /* @__PURE__ */ jsx4(
128
- Avatar,
129
- {
130
- size: 24,
131
- name: member.name,
132
- src: member.picture,
133
- alt: member.name,
134
- className: "-mr-2 relative border-2 border-bg-surface"
135
- },
136
- i
136
+ const overlap = Math.round(size / 3);
137
+ const ring = size / 12;
138
+ return /* @__PURE__ */ jsx4("div", { className: "flex items-center", style: { paddingRight: overlap }, children: visible.map((member, i) => (
139
+ /*
140
+ The wrapper carries the overlap and the ring; the face carries
141
+ neither. It must never clip — see the note at the top of the file —
142
+ so it has the avatar's own radius and no overflow of its own.
143
+ */
144
+ /* @__PURE__ */ jsx4(
145
+ "span",
146
+ {
147
+ className: "relative flex rounded-sm",
148
+ style: { marginRight: -overlap, boxShadow: `0 0 0 ${ring}px var(--bg-surface)` },
149
+ children: /* @__PURE__ */ jsx4(Avatar, { size, name: member.name, src: member.picture, alt: member.name })
150
+ },
151
+ i
152
+ )
137
153
  )) });
138
154
  }
139
155
 
@@ -238,19 +254,72 @@ function Chip({ type = "neutral", label, leadingIcon, trailingIcon, className })
238
254
  }
239
255
 
240
256
  // src/ChipInput.tsx
241
- import { useState as useState2, useRef as useRef2, useMemo, useEffect as useEffect2, useLayoutEffect } from "react";
257
+ import { useState as useState3, useRef as useRef2, useMemo, useEffect as useEffect2, useLayoutEffect as useLayoutEffect2 } from "react";
242
258
  import { createPortal as createPortal2 } from "react-dom";
243
259
  import { IconX } from "@tabler/icons-react";
244
260
 
261
+ // src/fit.ts
262
+ var MARGIN = 8;
263
+ var GAP = 4;
264
+ function fitMenu({
265
+ anchor,
266
+ menu,
267
+ viewport,
268
+ cap = Number.POSITIVE_INFINITY
269
+ }) {
270
+ const left = Math.max(MARGIN, Math.min(anchor.left, viewport.width - menu.width - MARGIN));
271
+ const below = viewport.height - anchor.bottom - GAP - MARGIN;
272
+ const above = anchor.top - GAP - MARGIN;
273
+ const openUp = below < Math.min(menu.contentHeight, cap) && above > below;
274
+ const maxHeight = Math.max(Math.min(cap, openUp ? above : below), 120);
275
+ return openUp ? { left, bottom: viewport.height - anchor.top + GAP, maxHeight } : { left, top: anchor.bottom + GAP, maxHeight };
276
+ }
277
+ function clampBox({
278
+ box,
279
+ viewport
280
+ }) {
281
+ const left = Math.max(MARGIN, Math.min(box.left, viewport.width - box.width - MARGIN));
282
+ const maxHeight = Math.max(Math.min(box.height, viewport.height - 2 * MARGIN), 120);
283
+ const top = Math.max(MARGIN, Math.min(box.top, viewport.height - maxHeight - MARGIN));
284
+ return { left, top, maxHeight };
285
+ }
286
+ function fitSubmenu({
287
+ row,
288
+ panel,
289
+ viewport
290
+ }) {
291
+ const fitsRight = row.right + GAP + panel.width <= viewport.width - MARGIN;
292
+ const left = Math.max(MARGIN, fitsRight ? row.right + GAP : row.left - GAP - panel.width);
293
+ const top = Math.max(MARGIN, Math.min(row.top, viewport.height - panel.height - MARGIN));
294
+ return { left, top };
295
+ }
296
+
245
297
  // src/Menu.tsx
246
- import { useEffect, useRef } from "react";
298
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState as useState2 } from "react";
247
299
  import { IconChevronRight } from "@tabler/icons-react";
248
300
  import { createPortal } from "react-dom";
249
301
 
250
- // src/SectionLabel.tsx
302
+ // src/Kbd.tsx
251
303
  import { jsx as jsx8 } from "react/jsx-runtime";
252
- function SectionLabel({ children, className }) {
304
+ function Kbd({ children, className }) {
253
305
  return /* @__PURE__ */ jsx8(
306
+ "kbd",
307
+ {
308
+ className: cn(
309
+ "inline-flex shrink-0 items-center justify-center whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset px-1 py-px font-sans text-[12px] leading-[120%] font-normal text-text-secondary",
310
+ "signal:border-b-2 signal:pt-[2px] signal:pb-px signal:bg-[rgba(255,255,255,.05)] signal:font-mono signal:text-[10px]",
311
+ "ship:border-b-2 ship:pt-[2px] ship:pb-px ship:bg-[rgba(255,255,255,.05)] ship:font-mono ship:text-[10px]",
312
+ className
313
+ ),
314
+ children
315
+ }
316
+ );
317
+ }
318
+
319
+ // src/SectionLabel.tsx
320
+ import { jsx as jsx9 } from "react/jsx-runtime";
321
+ function SectionLabel({ children, className }) {
322
+ return /* @__PURE__ */ jsx9(
254
323
  "span",
255
324
  {
256
325
  className: cn(
@@ -263,9 +332,30 @@ function SectionLabel({ children, className }) {
263
332
  }
264
333
 
265
334
  // src/Menu.tsx
266
- import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
267
- function Menu({ onClose, position, children, className }) {
335
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
336
+ var MenuHoverContext = createContext(null);
337
+ function MenuPanel({ children, className, ...props }) {
338
+ return /* @__PURE__ */ jsx10(
339
+ "div",
340
+ {
341
+ className: cn("flex flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg", className),
342
+ ...props,
343
+ children
344
+ }
345
+ );
346
+ }
347
+ function Menu({ onClose, anchor, align = "left", position, closeOnLeave = false, children, className }) {
268
348
  const ref = useRef(null);
349
+ const leaveTimer = useRef(void 0);
350
+ const hold = useCallback(() => clearTimeout(leaveTimer.current), []);
351
+ const release = useCallback(() => {
352
+ if (!closeOnLeave) return;
353
+ clearTimeout(leaveTimer.current);
354
+ leaveTimer.current = setTimeout(onClose, 150);
355
+ }, [closeOnLeave, onClose]);
356
+ useEffect(() => () => clearTimeout(leaveTimer.current), []);
357
+ const portalled = Boolean(anchor || position);
358
+ const [placed, setPlaced] = useState2(null);
269
359
  useEffect(() => {
270
360
  const onDown = (event) => {
271
361
  if (!ref.current?.contains(event.target)) onClose();
@@ -280,34 +370,139 @@ function Menu({ onClose, position, children, className }) {
280
370
  document.removeEventListener("keydown", onKey);
281
371
  };
282
372
  }, [onClose]);
283
- const style = position ? { top: position.top, ..."left" in position ? { left: position.left } : { right: position.right } } : void 0;
284
- const node = /* @__PURE__ */ jsx9(
285
- "div",
373
+ const posLeft = position && "left" in position ? position.left : void 0;
374
+ const posRight = position && "right" in position ? position.right : void 0;
375
+ const posTop = position?.top;
376
+ useLayoutEffect(() => {
377
+ if (!portalled || !ref.current) return;
378
+ const viewport = { width: window.innerWidth, height: window.innerHeight };
379
+ const menu = ref.current;
380
+ if (anchor) {
381
+ const rect = anchor instanceof Element ? anchor.getBoundingClientRect() : anchor;
382
+ const left = align === "right" ? rect.right - menu.offsetWidth : rect.left;
383
+ setPlaced(
384
+ fitMenu({
385
+ anchor: { left, top: rect.top, bottom: rect.bottom },
386
+ menu: { width: menu.offsetWidth, contentHeight: menu.scrollHeight },
387
+ viewport
388
+ })
389
+ );
390
+ } else if (posTop !== void 0) {
391
+ const left = posLeft ?? viewport.width - (posRight ?? 0) - menu.offsetWidth;
392
+ setPlaced(clampBox({ box: { left, top: posTop, width: menu.offsetWidth, height: menu.offsetHeight }, viewport }));
393
+ }
394
+ }, [portalled, anchor, align, posLeft, posRight, posTop]);
395
+ useEffect(() => {
396
+ if (!anchor) return;
397
+ const onScroll = (event) => {
398
+ if (event.target instanceof Node && ref.current?.contains(event.target)) return;
399
+ onClose();
400
+ };
401
+ window.addEventListener("resize", onClose);
402
+ window.addEventListener("scroll", onScroll, true);
403
+ return () => {
404
+ window.removeEventListener("resize", onClose);
405
+ window.removeEventListener("scroll", onScroll, true);
406
+ };
407
+ }, [anchor, onClose]);
408
+ const style = portalled ? placed ? { left: placed.left, top: placed.top, bottom: placed.bottom, maxHeight: placed.maxHeight } : (
409
+ // The provisional render: measured by the layout effect, never seen.
410
+ { left: 0, top: 0, visibility: "hidden" }
411
+ ) : void 0;
412
+ const node = /* @__PURE__ */ jsx10(MenuHoverContext.Provider, { value: { hold, release }, children: /* @__PURE__ */ jsx10(
413
+ MenuPanel,
286
414
  {
287
415
  ref,
288
416
  role: "menu",
289
417
  "data-interactive": true,
290
418
  className: cn(
291
- "z-50 flex min-w-[180px] flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg",
292
- position ? "fixed" : "absolute right-0 top-full mt-1",
419
+ "z-50 min-w-[180px]",
420
+ portalled ? "fixed overflow-y-auto" : "absolute right-0 top-full mt-1",
293
421
  className
294
422
  ),
295
423
  style,
296
424
  onClick: (event) => event.stopPropagation(),
425
+ onMouseEnter: closeOnLeave ? hold : void 0,
426
+ onMouseLeave: closeOnLeave ? release : void 0,
297
427
  children
298
428
  }
299
- );
300
- return position ? createPortal(node, document.body) : node;
429
+ ) });
430
+ return portalled ? createPortal(node, document.body) : node;
301
431
  }
302
- function MenuItem({ label, children, size = "default", description, leading, trailing, shortcut, submenu, destructive, selected, className, ...props }) {
303
- const edge = trailing ?? (shortcut ? /* @__PURE__ */ jsx9("kbd", { className: "inline-flex shrink-0 items-center justify-center rounded-sm border border-border-strong bg-bg-inset px-1 py-px text-caption text-text-secondary", children: shortcut }) : submenu ? /* @__PURE__ */ jsx9(IconChevronRight, { size: 16, stroke: 1.5, className: "shrink-0 text-text-muted" }) : null);
432
+ function MenuSub({ label, leading, selected, children, className }) {
433
+ const [open, setOpen] = useState2(false);
434
+ const rowRef = useRef(null);
435
+ const panelRef = useRef(null);
436
+ const closeTimer = useRef(void 0);
437
+ const [placed, setPlaced] = useState2(null);
438
+ const menuHover = useContext(MenuHoverContext);
439
+ const enter = () => {
440
+ clearTimeout(closeTimer.current);
441
+ menuHover?.hold();
442
+ setOpen(true);
443
+ };
444
+ const leave = () => {
445
+ closeTimer.current = setTimeout(() => setOpen(false), 150);
446
+ menuHover?.release();
447
+ };
448
+ useEffect(() => () => clearTimeout(closeTimer.current), []);
449
+ useLayoutEffect(() => {
450
+ if (!open || !rowRef.current || !panelRef.current) {
451
+ setPlaced(null);
452
+ return;
453
+ }
454
+ const row = rowRef.current.getBoundingClientRect();
455
+ setPlaced(
456
+ fitSubmenu({
457
+ row: { left: row.left, right: row.right, top: row.top },
458
+ panel: { width: panelRef.current.offsetWidth, height: panelRef.current.offsetHeight },
459
+ viewport: { width: window.innerWidth, height: window.innerHeight }
460
+ })
461
+ );
462
+ }, [open]);
463
+ return /* @__PURE__ */ jsxs5("div", { ref: rowRef, onMouseEnter: enter, onMouseLeave: leave, children: [
464
+ /* @__PURE__ */ jsx10(MenuItem, { label, leading, selected, submenu: true }),
465
+ open && createPortal(
466
+ /* @__PURE__ */ jsx10(
467
+ MenuPanel,
468
+ {
469
+ ref: panelRef,
470
+ role: "menu",
471
+ "data-interactive": true,
472
+ className: cn("fixed z-50 w-[160px]", className),
473
+ style: placed ?? { left: 0, top: 0, visibility: "hidden" },
474
+ onMouseEnter: enter,
475
+ onMouseLeave: leave,
476
+ children
477
+ }
478
+ ),
479
+ document.body
480
+ )
481
+ ] });
482
+ }
483
+ function MenuItem({ label, children, size = "default", description, leading, trailing, hint, shortcut, submenu, destructive, selected, className, ...props }) {
484
+ const edge = trailing ?? (shortcut ? /* @__PURE__ */ jsx10(Kbd, { children: shortcut }) : submenu ? /* @__PURE__ */ jsx10(IconChevronRight, { size: 16, stroke: 1.5, className: "shrink-0 text-text-muted" }) : null);
304
485
  return /* @__PURE__ */ jsxs5(
305
486
  "button",
306
487
  {
307
488
  type: "button",
308
489
  role: "menuitem",
309
490
  className: cn(
310
- "flex w-full cursor-pointer items-center rounded-lg text-left hover:bg-bg-hover transition-colors",
491
+ // shrink-0: a menu is a flex column that scrolls at its max height,
492
+ // and a flex child shrinks before its container does — so every row
493
+ // in an overflowing menu was squashed to its `min-h`, and a row given
494
+ // an explicit height silently lost it (Peek's `[` menu: h-12 rows
495
+ // measured 40px). The same fix NavItem took on 2026-09-02.
496
+ // `group`: the `hint` slot reveals itself from this row's own :hover,
497
+ // so the hint and the fill are one CSS state change, not two engines.
498
+ //
499
+ // No transition on the fill (Katerina, 2026-09-05). It faded over
500
+ // 150ms, and anything appearing with it had to fade too or arrive
501
+ // ahead of it — which, sweeping a pointer down a list, read as the
502
+ // hint flickering in and out. Both are instant now: they still change
503
+ // on exactly the same :hover, so they cannot come apart, and a row
504
+ // lights and unlights crisply as the pointer crosses it.
505
+ "group flex w-full shrink-0 cursor-pointer items-center rounded-lg text-left hover:bg-bg-hover",
311
506
  // tall: as tall as its content, never shorter than 40px (Katerina,
312
507
  // 2026-09-01) — a single-line picker row sits at 40, a row with a
313
508
  // 32px face and a role line comes out at its natural 48. One rule,
@@ -318,34 +513,41 @@ function MenuItem({ label, children, size = "default", description, leading, tra
318
513
  ),
319
514
  ...props,
320
515
  children: [
321
- leading && /* @__PURE__ */ jsx9("span", { className: "flex shrink-0 items-center", children: leading }),
516
+ leading && /* @__PURE__ */ jsx10("span", { className: "flex shrink-0 items-center", children: leading }),
322
517
  children ?? /* @__PURE__ */ jsxs5("span", { className: cn("flex min-w-0 flex-1 flex-col", size === "tall" && "gap-[2px]"), children: [
323
- /* @__PURE__ */ jsx9("span", { className: cn("truncate text-[14px] leading-[140%]", destructive ? "text-error-default" : "text-text-primary"), children: label }),
324
- description && /* @__PURE__ */ jsx9("span", { className: "truncate text-[12px] leading-[120%] text-text-secondary", children: description })
518
+ /* @__PURE__ */ jsx10("span", { className: cn("truncate text-[14px] leading-[140%]", destructive ? "text-error-default" : "text-text-primary"), children: label }),
519
+ description && /* @__PURE__ */ jsx10("span", { className: "truncate text-[12px] leading-[120%] text-text-secondary", children: description })
325
520
  ] }),
326
- edge && /* @__PURE__ */ jsx9("span", { className: "flex shrink-0 items-center", children: edge })
521
+ (edge || hint) && // One grid cell holding both, right-aligned: the slot is as wide as
522
+ // the wider of the two and never changes, so revealing the hint moves
523
+ // nothing. No transition here either — the hint switches on the same
524
+ // :hover / `selected` as the fill, in the same frame.
525
+ /* @__PURE__ */ jsxs5("span", { className: "grid shrink-0 items-center justify-items-end [&>*]:col-start-1 [&>*]:row-start-1", children: [
526
+ edge && /* @__PURE__ */ jsx10("span", { className: cn("flex items-center", hint && "group-hover:opacity-0", hint && selected && "opacity-0"), children: edge }),
527
+ hint && /* @__PURE__ */ jsx10("span", { className: cn("flex items-center opacity-0 group-hover:opacity-100", selected && "opacity-100"), children: hint })
528
+ ] })
327
529
  ]
328
530
  }
329
531
  );
330
532
  }
331
- function EnterHint({ label = "Enter" }) {
332
- return /* @__PURE__ */ jsxs5("span", { className: "flex shrink-0 items-center gap-2 text-text-muted", children: [
333
- /* @__PURE__ */ jsx9("span", { className: "text-[12px] leading-[120%]", children: "\u21A9" }),
334
- /* @__PURE__ */ jsx9("span", { className: "text-[9px] font-medium leading-[115%] signal:font-mono signal:text-[9.5px] signal:tracking-[0.04em]", children: label })
533
+ function EnterHint({ target }) {
534
+ return /* @__PURE__ */ jsxs5("span", { className: "flex shrink-0 items-center gap-1.5 text-text-muted", children: [
535
+ /* @__PURE__ */ jsx10(Kbd, { children: "\u21A9 Enter" }),
536
+ target && /* @__PURE__ */ jsx10("span", { className: "text-[9px] font-medium leading-[115%] signal:font-mono signal:text-[9.5px] signal:tracking-[0.04em]", children: target })
335
537
  ] });
336
538
  }
337
539
  function MenuSection({ label, children, className }) {
338
540
  return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col", children: [
339
- /* @__PURE__ */ jsx9("div", { className: cn("flex h-8 items-center px-2", className), children: /* @__PURE__ */ jsx9(SectionLabel, { className: "text-text-secondary", children: label }) }),
541
+ /* @__PURE__ */ jsx10("div", { className: cn("flex h-8 items-center px-2", className), children: /* @__PURE__ */ jsx10(SectionLabel, { className: "text-text-secondary", children: label }) }),
340
542
  children
341
543
  ] });
342
544
  }
343
545
  function MenuRow({ children, className }) {
344
- return /* @__PURE__ */ jsx9("div", { className: cn("flex items-center gap-2 px-2 py-1.5", className), children });
546
+ return /* @__PURE__ */ jsx10("div", { className: cn("flex items-center gap-2 px-2 py-1.5", className), children });
345
547
  }
346
548
 
347
549
  // src/ChipInput.tsx
348
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
550
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
349
551
  function InputChip({ label, leading, onRemove, className }) {
350
552
  return /* @__PURE__ */ jsxs6(
351
553
  "div",
@@ -363,9 +565,9 @@ function InputChip({ label, leading, onRemove, className }) {
363
565
  className
364
566
  ),
365
567
  children: [
366
- leading && /* @__PURE__ */ jsx10("span", { className: "flex shrink-0 items-center", children: leading }),
367
- /* @__PURE__ */ jsx10("span", { className: "text-caption font-medium text-text-primary", children: label }),
368
- onRemove && /* @__PURE__ */ jsx10(
568
+ leading && /* @__PURE__ */ jsx11("span", { className: "flex shrink-0 items-center", children: leading }),
569
+ /* @__PURE__ */ jsx11("span", { className: "text-caption font-medium text-text-primary", children: label }),
570
+ onRemove && /* @__PURE__ */ jsx11(
369
571
  "button",
370
572
  {
371
573
  type: "button",
@@ -375,7 +577,7 @@ function InputChip({ label, leading, onRemove, className }) {
375
577
  },
376
578
  className: "size-4 flex items-center justify-center rounded-full hover:bg-bg-hover text-text-secondary",
377
579
  "aria-label": `Remove ${label}`,
378
- children: /* @__PURE__ */ jsx10(IconX, { size: 10, stroke: 1.5 })
580
+ children: /* @__PURE__ */ jsx11(IconX, { size: 10, stroke: 1.5 })
379
581
  }
380
582
  )
381
583
  ]
@@ -392,10 +594,10 @@ function ChipInput({
392
594
  chipLeading,
393
595
  rowLeading
394
596
  }) {
395
- const [query, setQuery] = useState2("");
396
- const [highlight, setHighlight] = useState2(0);
397
- const [isFocused, setIsFocused] = useState2(false);
398
- const [anchorRect, setAnchorRect] = useState2(null);
597
+ const [query, setQuery] = useState3("");
598
+ const [highlight, setHighlight] = useState3(0);
599
+ const [isFocused, setIsFocused] = useState3(false);
600
+ const [anchorRect, setAnchorRect] = useState3(null);
399
601
  const wrapperRef = useRef2(null);
400
602
  const inputRef = useRef2(null);
401
603
  const matches = useMemo(() => {
@@ -413,7 +615,7 @@ function ChipInput({
413
615
  setHighlight(0);
414
616
  }, [query, matches.length]);
415
617
  const showDropdown = isFocused && query.trim().length > 0 && matches.length > 0;
416
- useLayoutEffect(() => {
618
+ useLayoutEffect2(() => {
417
619
  if (!showDropdown) return;
418
620
  const update = () => {
419
621
  if (wrapperRef.current) setAnchorRect(wrapperRef.current.getBoundingClientRect());
@@ -471,8 +673,8 @@ function ChipInput({
471
673
  className: "bg-bg-inset border border-border-default hover:border-border-strong focus-within:border-border-focus focus-within:hover:border-border-focus rounded-lg px-3 py-1.5 flex flex-wrap items-center gap-1.5 transition-colors min-h-[38px] cursor-text signal:transition-shadow signal:focus-within:shadow-focus-ring",
472
674
  onClick: () => inputRef.current?.focus(),
473
675
  children: [
474
- value.map((o) => /* @__PURE__ */ jsx10(InputChip, { label: o.label, leading: chipLeading?.(o), onRemove: () => removeOption(o.id) }, o.id)),
475
- /* @__PURE__ */ jsx10(
676
+ value.map((o) => /* @__PURE__ */ jsx11(InputChip, { label: o.label, leading: chipLeading?.(o), onRemove: () => removeOption(o.id) }, o.id)),
677
+ /* @__PURE__ */ jsx11(
476
678
  "input",
477
679
  {
478
680
  ref: inputRef,
@@ -493,16 +695,20 @@ function ChipInput({
493
695
  }
494
696
  ),
495
697
  showDropdown && anchorRect && createPortal2(
496
- /* @__PURE__ */ jsx10(
698
+ /* @__PURE__ */ jsx11(
497
699
  "div",
498
700
  {
499
- className: "fixed z-[60] max-h-[240px] overflow-y-auto bg-bg-elevated border border-border-default rounded-lg shadow-lg",
701
+ className: "fixed z-[60] overflow-y-auto bg-bg-elevated border border-border-default rounded-lg shadow-lg",
500
702
  style: {
501
- top: anchorRect.bottom + 4,
502
- left: anchorRect.left,
703
+ ...fitMenu({
704
+ anchor: { left: anchorRect.left, top: anchorRect.top, bottom: anchorRect.bottom },
705
+ menu: { width: anchorRect.width, contentHeight: matches.length * 48 },
706
+ viewport: { width: window.innerWidth, height: window.innerHeight },
707
+ cap: 240
708
+ }),
503
709
  width: anchorRect.width
504
710
  },
505
- children: matches.map((o, i) => /* @__PURE__ */ jsx10(
711
+ children: matches.map((o, i) => /* @__PURE__ */ jsx11(
506
712
  MenuItem,
507
713
  {
508
714
  size: "tall",
@@ -527,55 +733,62 @@ function ChipInput({
527
733
  }
528
734
 
529
735
  // src/Tooltip.tsx
530
- import { useCallback, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState3 } from "react";
736
+ import { useCallback as useCallback2, useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState as useState4 } from "react";
531
737
  import { createPortal as createPortal3 } from "react-dom";
532
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
533
- function Tooltip({ label, className }) {
534
- return /* @__PURE__ */ jsx11("div", { role: "tooltip", className: cn("bg-bg-elevated border border-border-default rounded-lg h-[30px] flex items-center justify-center px-2 shadow-lg", className), children: /* @__PURE__ */ jsx11("span", { className: "text-caption text-text-primary whitespace-nowrap", children: label }) });
738
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
739
+ function Tooltip({ label, shortcut, className }) {
740
+ return /* @__PURE__ */ jsxs7("div", { role: "tooltip", className: cn("bg-bg-elevated border border-border-default rounded-lg h-[30px] flex items-center justify-center gap-1.5 px-2 shadow-lg", className), children: [
741
+ /* @__PURE__ */ jsx12("span", { className: "text-caption text-text-primary whitespace-nowrap", children: label }),
742
+ shortcut && /* @__PURE__ */ jsx12(Kbd, { children: shortcut })
743
+ ] });
535
744
  }
536
- var GAP = 6;
745
+ var GAP2 = 6;
537
746
  var VIEWPORT_PAD = 8;
538
- function WithTooltip({ label, placement = "top", wrapperClassName, children }) {
539
- const [show, setShow] = useState3(false);
747
+ function WithTooltip({ label, shortcut, placement = "top", wrapperClassName, children }) {
748
+ const [show, setShow] = useState4(false);
540
749
  const ref = useRef3(null);
541
750
  const tooltipRef = useRef3(null);
542
- const [style, setStyle] = useState3({ position: "fixed", zIndex: 9999, pointerEvents: "none", visibility: "hidden" });
543
- const reposition = useCallback(() => {
751
+ const [style, setStyle] = useState4({ position: "fixed", zIndex: 9999, pointerEvents: "none", visibility: "hidden" });
752
+ const reposition = useCallback2(() => {
544
753
  const trigger = ref.current;
545
754
  const tip = tooltipRef.current;
546
755
  if (!trigger || !tip) return;
547
756
  const triggerRect = trigger.getBoundingClientRect();
548
757
  const tipRect = tip.getBoundingClientRect();
549
- const top = placement === "bottom" ? triggerRect.bottom + GAP : triggerRect.top - tipRect.height - GAP;
758
+ let top = placement === "bottom" ? triggerRect.bottom + GAP2 : triggerRect.top - tipRect.height - GAP2;
759
+ if (placement === "top" && top < VIEWPORT_PAD) top = triggerRect.bottom + GAP2;
760
+ else if (placement === "bottom" && top + tipRect.height > window.innerHeight - VIEWPORT_PAD) top = triggerRect.top - tipRect.height - GAP2;
761
+ top = Math.max(VIEWPORT_PAD, Math.min(top, window.innerHeight - tipRect.height - VIEWPORT_PAD));
550
762
  let left = triggerRect.left + triggerRect.width / 2 - tipRect.width / 2;
551
763
  left = Math.max(VIEWPORT_PAD, Math.min(left, window.innerWidth - tipRect.width - VIEWPORT_PAD));
552
764
  setStyle({ position: "fixed", top, left, zIndex: 9999, pointerEvents: "none", visibility: "visible" });
553
765
  }, [placement]);
554
- useLayoutEffect2(() => {
766
+ useLayoutEffect3(() => {
555
767
  if (show) reposition();
556
768
  }, [show, reposition]);
557
769
  return /* @__PURE__ */ jsxs7("div", { ref, className: cn("inline-flex shrink-0", wrapperClassName), onMouseEnter: () => setShow(true), onMouseLeave: () => setShow(false), children: [
558
770
  children,
559
771
  show && createPortal3(
560
- /* @__PURE__ */ jsx11("div", { ref: tooltipRef, style, children: /* @__PURE__ */ jsx11(Tooltip, { label }) }),
772
+ /* @__PURE__ */ jsx12("div", { ref: tooltipRef, style, children: /* @__PURE__ */ jsx12(Tooltip, { label, shortcut }) }),
561
773
  document.body
562
774
  )
563
775
  ] });
564
776
  }
565
777
 
566
778
  // src/IconButton.tsx
567
- import { jsx as jsx12 } from "react/jsx-runtime";
779
+ import { jsx as jsx13 } from "react/jsx-runtime";
568
780
  function IconButton({
569
781
  variant = "muted",
570
782
  className,
571
783
  children,
572
784
  disabled,
573
785
  tooltip,
786
+ tooltipShortcut,
574
787
  tooltipPlacement,
575
788
  type = "button",
576
789
  ...props
577
790
  }) {
578
- const button = /* @__PURE__ */ jsx12(
791
+ const button = /* @__PURE__ */ jsx13(
579
792
  "button",
580
793
  {
581
794
  type,
@@ -596,47 +809,47 @@ function IconButton({
596
809
  }
597
810
  );
598
811
  if (tooltip) {
599
- return /* @__PURE__ */ jsx12(WithTooltip, { label: tooltip, placement: tooltipPlacement, children: button });
812
+ return /* @__PURE__ */ jsx13(WithTooltip, { label: tooltip, shortcut: tooltipShortcut, placement: tooltipPlacement, children: button });
600
813
  }
601
814
  return button;
602
815
  }
603
816
 
604
817
  // src/IdentityMenu.tsx
605
- import { useState as useState4 } from "react";
818
+ import { useRef as useRef4, useState as useState5 } from "react";
606
819
 
607
820
  // src/Divider.tsx
608
- import { jsx as jsx13 } from "react/jsx-runtime";
821
+ import { jsx as jsx14 } from "react/jsx-runtime";
609
822
  function Divider({ orientation = "horizontal", className }) {
610
- return /* @__PURE__ */ jsx13(
823
+ return /* @__PURE__ */ jsx14(
611
824
  "div",
612
825
  {
613
826
  role: "separator",
614
827
  "aria-orientation": orientation,
615
- className: cn("bg-border-subtle", orientation === "horizontal" ? "h-px mx-3" : "w-px self-stretch shrink-0", className)
828
+ className: cn("shrink-0 bg-border-subtle", orientation === "horizontal" ? "h-px mx-3" : "w-px self-stretch", className)
616
829
  }
617
830
  );
618
831
  }
619
832
 
620
833
  // src/Person.tsx
621
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
834
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
622
835
  function Person({ name, picture, fallback = "\u2014", size = 20, className }) {
623
836
  return (
624
837
  // gap-2: the face-to-name distance is one rule (8px) wherever a person
625
838
  // appears — Katerina, 2026-09-01, set on Person so PersonTrigger and every
626
839
  // row agree by construction.
627
840
  /* @__PURE__ */ jsxs8("span", { className: cn("flex min-w-0 items-center gap-2", className), children: [
628
- /* @__PURE__ */ jsx14(Avatar, { name, src: picture, size }),
629
- /* @__PURE__ */ jsx14("span", { className: cn("truncate", !name && "text-text-muted"), children: name ?? fallback })
841
+ /* @__PURE__ */ jsx15(Avatar, { name, src: picture, size }),
842
+ /* @__PURE__ */ jsx15("span", { className: cn("truncate", !name && "text-text-muted"), children: name ?? fallback })
630
843
  ] })
631
844
  );
632
845
  }
633
846
 
634
847
  // src/PersonTrigger.tsx
635
848
  import { IconChevronDown } from "@tabler/icons-react";
636
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
849
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
637
850
  function PersonTrigger({ name, picture, fallback, size, open = false, compact = false, className, ...props }) {
638
851
  if (compact) {
639
- return /* @__PURE__ */ jsx15(
852
+ return /* @__PURE__ */ jsx16(
640
853
  "button",
641
854
  {
642
855
  type: "button",
@@ -644,7 +857,7 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
644
857
  "aria-expanded": open,
645
858
  className: cn("cursor-pointer rounded-full focus:outline-none", className),
646
859
  ...props,
647
- children: /* @__PURE__ */ jsx15(Avatar, { name, src: picture, size: size ?? 36 })
860
+ children: /* @__PURE__ */ jsx16(Avatar, { name, src: picture, size: size ?? 36 })
648
861
  }
649
862
  );
650
863
  }
@@ -664,57 +877,58 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
664
877
  ),
665
878
  ...props,
666
879
  children: [
667
- /* @__PURE__ */ jsx15(Person, { name, picture, fallback, size: size ?? 22 }),
668
- /* @__PURE__ */ jsx15(IconChevronDown, { size: 14, stroke: 1.5, className: "shrink-0 text-text-muted" })
880
+ /* @__PURE__ */ jsx16(Person, { name, picture, fallback, size: size ?? 22 }),
881
+ /* @__PURE__ */ jsx16(IconChevronDown, { size: 14, stroke: 1.5, className: "shrink-0 text-text-muted" })
669
882
  ]
670
883
  }
671
884
  );
672
885
  }
673
886
 
674
887
  // src/IdentityMenu.tsx
675
- import { Fragment, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
676
- function IdentityPanel({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, onClose, className, children }) {
888
+ import { Fragment, jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
889
+ function IdentityPanel({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, onClose, anchor, className, children }) {
677
890
  const act = (action) => () => {
678
891
  onClose();
679
892
  action();
680
893
  };
681
894
  const appRows = typeof children === "function" ? children(onClose) : children;
682
- return /* @__PURE__ */ jsxs10(Menu, { onClose, className: cn("w-72", className), children: [
895
+ return /* @__PURE__ */ jsxs10(Menu, { onClose, anchor, align: "right", className: cn("w-72", className), children: [
683
896
  /* @__PURE__ */ jsxs10(MenuSection, { label: signedIn ? "Signed in as" : "Acting as", children: [
684
- /* @__PURE__ */ jsx16(MenuRow, { children: /* @__PURE__ */ jsx16(Person, { name: me.name, picture: me.picture, fallback: "Anonymous", size: 28, className: "text-body-2-strong" }) }),
685
- /* @__PURE__ */ jsx16(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." }),
897
+ /* @__PURE__ */ jsx17(MenuRow, { children: /* @__PURE__ */ jsx17(Person, { name: me.name, picture: me.picture, fallback: "Anonymous", size: 28, className: "text-body-2-strong" }) }),
898
+ /* @__PURE__ */ jsx17(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." }),
686
899
  me.email && /* @__PURE__ */ jsxs10(Note, { children: [
687
900
  me.email,
688
901
  " \xB7 known to Estiva, never published to the relay"
689
902
  ] })
690
903
  ] }),
691
904
  relayUrl && /* @__PURE__ */ jsxs10(Fragment, { children: [
692
- /* @__PURE__ */ jsx16(Divider, { className: "mx-0 my-2" }),
905
+ /* @__PURE__ */ jsx17(Divider, { className: "mx-0 my-2" }),
693
906
  /* @__PURE__ */ jsxs10(MenuSection, { label: "Workspace", children: [
694
- /* @__PURE__ */ jsx16(MenuRow, { children: /* @__PURE__ */ jsx16("span", { className: "break-all font-mono text-caption text-text-primary", children: relayUrl }) }),
695
- /* @__PURE__ */ jsx16(Note, { children: "Everyone on this relay shares this workspace, in every app." })
907
+ /* @__PURE__ */ jsx17(MenuRow, { children: /* @__PURE__ */ jsx17("span", { className: "break-all font-mono text-caption text-text-primary", children: relayUrl }) }),
908
+ /* @__PURE__ */ jsx17(Note, { children: "Everyone on this relay shares this workspace, in every app." })
696
909
  ] })
697
910
  ] }),
698
911
  appRows && /* @__PURE__ */ jsxs10(Fragment, { children: [
699
- /* @__PURE__ */ jsx16(Divider, { className: "mx-0 my-2" }),
912
+ /* @__PURE__ */ jsx17(Divider, { className: "mx-0 my-2" }),
700
913
  appRows
701
914
  ] }),
702
- signedIn && idBase || onCopyKey || idBase && onSignOut ? /* @__PURE__ */ jsx16(Divider, { className: "mx-0 my-2" }) : null,
703
- signedIn && idBase && /* @__PURE__ */ jsx16(
915
+ signedIn && idBase || onCopyKey || idBase && onSignOut ? /* @__PURE__ */ jsx17(Divider, { className: "mx-0 my-2" }) : null,
916
+ signedIn && idBase && /* @__PURE__ */ jsx17(
704
917
  MenuItem,
705
918
  {
706
919
  label: "Edit your profile in Estiva ID",
707
920
  onClick: act(() => window.open(idBase, "_blank", "noopener,noreferrer"))
708
921
  }
709
922
  ),
710
- onCopyKey && /* @__PURE__ */ jsx16(MenuItem, { label: "Copy public key", onClick: act(onCopyKey) }),
711
- idBase && onSignOut && /* @__PURE__ */ jsx16(MenuItem, { label: "Sign out", onClick: act(onSignOut) })
923
+ onCopyKey && /* @__PURE__ */ jsx17(MenuItem, { label: "Copy public key", onClick: act(onCopyKey) }),
924
+ idBase && onSignOut && /* @__PURE__ */ jsx17(MenuItem, { label: "Sign out", onClick: act(onSignOut) })
712
925
  ] });
713
926
  }
714
927
  function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, compact = false, className, children }) {
715
- const [open, setOpen] = useState4(false);
716
- return /* @__PURE__ */ jsxs10("div", { className: cn("relative", className), children: [
717
- /* @__PURE__ */ jsx16(
928
+ const [open, setOpen] = useState5(false);
929
+ const anchorRef = useRef4(null);
930
+ return /* @__PURE__ */ jsxs10("div", { ref: anchorRef, className: cn("relative", className), children: [
931
+ /* @__PURE__ */ jsx17(
718
932
  PersonTrigger,
719
933
  {
720
934
  name: me.name,
@@ -728,7 +942,7 @@ function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, co
728
942
  "aria-label": compact ? "Account menu" : void 0
729
943
  }
730
944
  ),
731
- open && /* @__PURE__ */ jsx16(
945
+ open && /* @__PURE__ */ jsx17(
732
946
  IdentityPanel,
733
947
  {
734
948
  me,
@@ -738,21 +952,22 @@ function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, co
738
952
  onCopyKey,
739
953
  onSignOut,
740
954
  onClose: () => setOpen(false),
955
+ anchor: anchorRef.current,
741
956
  children
742
957
  }
743
958
  )
744
959
  ] });
745
960
  }
746
961
  function Note({ children }) {
747
- return /* @__PURE__ */ jsx16("span", { className: "px-2 pb-1.5 text-caption text-text-secondary", children });
962
+ return /* @__PURE__ */ jsx17("span", { className: "px-2 pb-1.5 text-caption text-text-secondary", children });
748
963
  }
749
964
 
750
965
  // src/Field.tsx
751
- import { createContext, useContext, useId } from "react";
752
- import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
753
- var FieldControlIdContext = createContext(void 0);
966
+ import { createContext as createContext2, useContext as useContext2, useId } from "react";
967
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
968
+ var FieldControlIdContext = createContext2(void 0);
754
969
  function useFieldControlId(ownId) {
755
- const fromField = useContext(FieldControlIdContext);
970
+ const fromField = useContext2(FieldControlIdContext);
756
971
  return fromField ?? ownId;
757
972
  }
758
973
  function Field({ label, required = false, htmlFor, children }) {
@@ -766,52 +981,37 @@ function Field({ label, required = false, htmlFor, children }) {
766
981
  className: `text-input-label text-text-primary${required ? " flex items-center" : ""}`,
767
982
  children: [
768
983
  label,
769
- required && /* @__PURE__ */ jsx17("span", { className: "text-error-default ml-0.5", children: "*" })
984
+ required && /* @__PURE__ */ jsx18("span", { className: "text-error-default ml-0.5", children: "*" })
770
985
  ]
771
986
  }
772
987
  ),
773
- /* @__PURE__ */ jsx17(FieldControlIdContext.Provider, { value: id, children })
988
+ /* @__PURE__ */ jsx18(FieldControlIdContext.Provider, { value: id, children })
774
989
  ] });
775
990
  }
776
991
 
777
992
  // src/Select.tsx
778
993
  import { IconCheck as IconCheck2, IconChevronDown as IconChevronDown2 } from "@tabler/icons-react";
779
- import { useCallback as useCallback2, useEffect as useEffect3, useId as useId2, useLayoutEffect as useLayoutEffect3, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
994
+ import { useCallback as useCallback3, useEffect as useEffect3, useId as useId2, useLayoutEffect as useLayoutEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState6 } from "react";
780
995
  import { createPortal as createPortal4 } from "react-dom";
781
- import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
782
- function fitMenu({
783
- anchor,
784
- menu,
785
- viewport
786
- }) {
787
- const MARGIN = 8;
788
- const GAP2 = 4;
789
- const CAP = 288;
790
- const left = Math.max(MARGIN, Math.min(anchor.left, viewport.width - menu.width - MARGIN));
791
- const below = viewport.height - anchor.bottom - GAP2 - MARGIN;
792
- const above = anchor.top - GAP2 - MARGIN;
793
- const openUp = below < Math.min(menu.contentHeight, CAP) && above > below;
794
- const maxHeight = Math.max(Math.min(CAP, openUp ? above : below), 120);
795
- return openUp ? { left, bottom: viewport.height - anchor.top + GAP2, maxHeight } : { left, top: anchor.bottom + GAP2, maxHeight };
796
- }
996
+ import { Fragment as Fragment2, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
797
997
  function Select({ value, onChange, options, size = "default", ariaLabel, placeholder = "Select\u2026", disabled, className }) {
798
998
  const id = useId2();
799
- const triggerRef = useRef4(null);
800
- const menuRef = useRef4(null);
801
- const [rect, setRect] = useState5(null);
802
- const [placement, setPlacement] = useState5(null);
999
+ const triggerRef = useRef5(null);
1000
+ const menuRef = useRef5(null);
1001
+ const [rect, setRect] = useState6(null);
1002
+ const [placement, setPlacement] = useState6(null);
803
1003
  const open = rect !== null;
804
1004
  const selectedIndex = useMemo2(() => Math.max(0, options.findIndex((o) => o.value === value)), [options, value]);
805
- const [activeIndex, setActiveIndex] = useState5(selectedIndex);
806
- const close = useCallback2((refocus) => {
1005
+ const [activeIndex, setActiveIndex] = useState6(selectedIndex);
1006
+ const close = useCallback3((refocus) => {
807
1007
  setRect(null);
808
1008
  if (refocus) triggerRef.current?.focus();
809
1009
  }, []);
810
- const openMenu = useCallback2(() => {
1010
+ const openMenu = useCallback3(() => {
811
1011
  setActiveIndex(selectedIndex);
812
1012
  setRect(triggerRef.current?.getBoundingClientRect() ?? null);
813
1013
  }, [selectedIndex]);
814
- useLayoutEffect3(() => {
1014
+ useLayoutEffect4(() => {
815
1015
  if (!rect || !menuRef.current) {
816
1016
  setPlacement(null);
817
1017
  return;
@@ -820,7 +1020,10 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
820
1020
  fitMenu({
821
1021
  anchor: { left: rect.left, top: rect.top, bottom: rect.bottom },
822
1022
  menu: { width: menuRef.current.offsetWidth, contentHeight: menuRef.current.scrollHeight },
823
- viewport: { width: window.innerWidth, height: window.innerHeight }
1023
+ viewport: { width: window.innerWidth, height: window.innerHeight },
1024
+ // The option list keeps its classic height (the old max-h-72); a menu
1025
+ // panel passes no cap and stands as tall as the room it opens into.
1026
+ cap: 288
824
1027
  })
825
1028
  );
826
1029
  }, [rect, options.length]);
@@ -930,15 +1133,15 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
930
1133
  ),
931
1134
  children: [
932
1135
  /* @__PURE__ */ jsxs12("span", { className: cn("flex min-w-0 items-center gap-2", !selected && "text-text-muted"), children: [
933
- selected?.leading && /* @__PURE__ */ jsx18("span", { className: "flex shrink-0 items-center", children: selected.leading }),
934
- /* @__PURE__ */ jsx18("span", { className: "truncate", children: selected?.label ?? placeholder })
1136
+ selected?.leading && /* @__PURE__ */ jsx19("span", { className: "flex shrink-0 items-center", children: selected.leading }),
1137
+ /* @__PURE__ */ jsx19("span", { className: "truncate", children: selected?.label ?? placeholder })
935
1138
  ] }),
936
- /* @__PURE__ */ jsx18(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1139
+ /* @__PURE__ */ jsx19(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
937
1140
  ]
938
1141
  }
939
1142
  ),
940
1143
  open && rect && createPortal4(
941
- /* @__PURE__ */ jsx18(
1144
+ /* @__PURE__ */ jsx19(
942
1145
  "div",
943
1146
  {
944
1147
  ref: menuRef,
@@ -971,10 +1174,10 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
971
1174
  ),
972
1175
  children: [
973
1176
  /* @__PURE__ */ jsxs12("span", { className: "flex min-w-0 items-center gap-2", children: [
974
- option.leading && /* @__PURE__ */ jsx18("span", { className: "flex shrink-0 items-center", children: option.leading }),
975
- /* @__PURE__ */ jsx18("span", { className: "truncate", children: option.label })
1177
+ option.leading && /* @__PURE__ */ jsx19("span", { className: "flex shrink-0 items-center", children: option.leading }),
1178
+ /* @__PURE__ */ jsx19("span", { className: "truncate", children: option.label })
976
1179
  ] }),
977
- option.value === value && /* @__PURE__ */ jsx18(IconCheck2, { size: 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1180
+ option.value === value && /* @__PURE__ */ jsx19(IconCheck2, { size: 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
978
1181
  ]
979
1182
  },
980
1183
  option.value
@@ -988,10 +1191,10 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
988
1191
 
989
1192
  // src/TextInput.tsx
990
1193
  import { forwardRef } from "react";
991
- import { jsx as jsx19 } from "react/jsx-runtime";
1194
+ import { jsx as jsx20 } from "react/jsx-runtime";
992
1195
  var TextInput = forwardRef(function TextInput2({ className, type = "text", id, ...props }, ref) {
993
1196
  const controlId = useFieldControlId(id);
994
- return /* @__PURE__ */ jsx19(
1197
+ return /* @__PURE__ */ jsx20(
995
1198
  "input",
996
1199
  {
997
1200
  ref,
@@ -1012,10 +1215,10 @@ var TextInput = forwardRef(function TextInput2({ className, type = "text", id, .
1012
1215
 
1013
1216
  // src/Textarea.tsx
1014
1217
  import { forwardRef as forwardRef2 } from "react";
1015
- import { jsx as jsx20 } from "react/jsx-runtime";
1218
+ import { jsx as jsx21 } from "react/jsx-runtime";
1016
1219
  var Textarea = forwardRef2(function Textarea2({ className, id, ...props }, ref) {
1017
1220
  const controlId = useFieldControlId(id);
1018
- return /* @__PURE__ */ jsx20(
1221
+ return /* @__PURE__ */ jsx21(
1019
1222
  "textarea",
1020
1223
  {
1021
1224
  ref,
@@ -1034,13 +1237,13 @@ var Textarea = forwardRef2(function Textarea2({ className, id, ...props }, ref)
1034
1237
  });
1035
1238
 
1036
1239
  // src/ConfirmDialog.tsx
1037
- import { useState as useState6 } from "react";
1240
+ import { useState as useState7 } from "react";
1038
1241
 
1039
1242
  // src/DialogShell.tsx
1040
1243
  import { useEffect as useEffect4 } from "react";
1041
1244
  import { createPortal as createPortal5 } from "react-dom";
1042
1245
  import { IconX as IconX2 } from "@tabler/icons-react";
1043
- import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
1246
+ import { Fragment as Fragment3, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
1044
1247
  function DialogShell({ title, onClose, headerContent, footer, children, bodyClassName, width = 502 }) {
1045
1248
  useEffect4(() => {
1046
1249
  const onKey = (event) => {
@@ -1051,8 +1254,8 @@ function DialogShell({ title, onClose, headerContent, footer, children, bodyClas
1051
1254
  }, [onClose]);
1052
1255
  return createPortal5(
1053
1256
  /* @__PURE__ */ jsxs13(Fragment3, { children: [
1054
- /* @__PURE__ */ jsx21("div", { className: "fixed inset-0 z-40 bg-black/50", onClick: onClose }),
1055
- /* @__PURE__ */ jsx21("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs13(
1257
+ /* @__PURE__ */ jsx22("div", { className: "fixed inset-0 z-40 bg-black/50", onClick: onClose }),
1258
+ /* @__PURE__ */ jsx22("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs13(
1056
1259
  "div",
1057
1260
  {
1058
1261
  role: "dialog",
@@ -1062,11 +1265,11 @@ function DialogShell({ title, onClose, headerContent, footer, children, bodyClas
1062
1265
  style: { width },
1063
1266
  children: [
1064
1267
  /* @__PURE__ */ jsxs13("div", { className: "h-12 flex items-center justify-between pl-5 pr-4 border-b border-border-subtle shrink-0", children: [
1065
- headerContent ?? /* @__PURE__ */ jsx21("span", { className: "text-h4 text-text-primary", children: title }),
1066
- /* @__PURE__ */ jsx21(IconButton, { tooltip: "Close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx21(IconX2, { size: 16, stroke: 1.5 }) })
1268
+ headerContent ?? /* @__PURE__ */ jsx22("span", { className: "text-h4 text-text-primary", children: title }),
1269
+ /* @__PURE__ */ jsx22(IconButton, { tooltip: "Close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx22(IconX2, { size: 16, stroke: 1.5 }) })
1067
1270
  ] }),
1068
- /* @__PURE__ */ jsx21("div", { className: cn("pl-5 pr-4 py-4", footer != null && "border-b border-border-subtle", bodyClassName), children }),
1069
- footer != null && /* @__PURE__ */ jsx21("div", { className: "h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0", children: footer })
1271
+ /* @__PURE__ */ jsx22("div", { className: cn("pl-5 pr-4 py-4", footer != null && "border-b border-border-subtle", bodyClassName), children }),
1272
+ footer != null && /* @__PURE__ */ jsx22("div", { className: "h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0", children: footer })
1070
1273
  ]
1071
1274
  }
1072
1275
  ) })
@@ -1076,9 +1279,9 @@ function DialogShell({ title, onClose, headerContent, footer, children, bodyClas
1076
1279
  }
1077
1280
 
1078
1281
  // src/ConfirmDialog.tsx
1079
- import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
1282
+ import { Fragment as Fragment4, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1080
1283
  function ConfirmDialog({ title, children, confirmLabel, destructive = false, onConfirm, onClose }) {
1081
- const [busy, setBusy] = useState6(false);
1284
+ const [busy, setBusy] = useState7(false);
1082
1285
  const confirm = async () => {
1083
1286
  setBusy(true);
1084
1287
  try {
@@ -1088,15 +1291,15 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
1088
1291
  setBusy(false);
1089
1292
  }
1090
1293
  };
1091
- return /* @__PURE__ */ jsx22(
1294
+ return /* @__PURE__ */ jsx23(
1092
1295
  DialogShell,
1093
1296
  {
1094
1297
  title,
1095
1298
  onClose,
1096
1299
  bodyClassName: "flex flex-col gap-3 text-body-2 text-text-primary",
1097
1300
  footer: /* @__PURE__ */ jsxs14(Fragment4, { children: [
1098
- /* @__PURE__ */ jsx22(Button, { variant: "muted", onClick: onClose, disabled: busy, children: "Cancel" }),
1099
- /* @__PURE__ */ jsx22(Button, { variant: destructive ? "destructive" : "primary", onClick: () => void confirm(), disabled: busy, children: confirmLabel })
1301
+ /* @__PURE__ */ jsx23(Button, { variant: "muted", onClick: onClose, disabled: busy, children: "Cancel" }),
1302
+ /* @__PURE__ */ jsx23(Button, { variant: destructive ? "destructive" : "primary", onClick: () => void confirm(), disabled: busy, children: confirmLabel })
1100
1303
  ] }),
1101
1304
  children
1102
1305
  }
@@ -1104,13 +1307,13 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
1104
1307
  }
1105
1308
 
1106
1309
  // src/EditableText.tsx
1107
- import { useEffect as useEffect5, useRef as useRef5, useState as useState7 } from "react";
1108
- import { jsx as jsx23 } from "react/jsx-runtime";
1310
+ import { useEffect as useEffect5, useRef as useRef6, useState as useState8 } from "react";
1311
+ import { jsx as jsx24 } from "react/jsx-runtime";
1109
1312
  function EditableText({ value, display, displayNode, placeholder, onCommit, multiline = false, className, label, readOnly = false }) {
1110
- const [editing, setEditing] = useState7(false);
1111
- const [draft, setDraft] = useState7(value);
1112
- const [busy, setBusy] = useState7(false);
1113
- const fieldRef = useRef5(null);
1313
+ const [editing, setEditing] = useState8(false);
1314
+ const [draft, setDraft] = useState8(value);
1315
+ const [busy, setBusy] = useState8(false);
1316
+ const fieldRef = useRef6(null);
1114
1317
  useEffect5(() => {
1115
1318
  if (editing) {
1116
1319
  fieldRef.current?.focus();
@@ -1156,7 +1359,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1156
1359
  className
1157
1360
  );
1158
1361
  if (readOnly) {
1159
- return /* @__PURE__ */ jsx23(
1362
+ return /* @__PURE__ */ jsx24(
1160
1363
  "div",
1161
1364
  {
1162
1365
  "aria-label": label,
@@ -1166,7 +1369,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1166
1369
  );
1167
1370
  }
1168
1371
  if (editing) {
1169
- return multiline ? /* @__PURE__ */ jsx23(
1372
+ return multiline ? /* @__PURE__ */ jsx24(
1170
1373
  "textarea",
1171
1374
  {
1172
1375
  ref: fieldRef,
@@ -1179,7 +1382,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1179
1382
  onBlur: () => void commit(),
1180
1383
  className: fieldClass
1181
1384
  }
1182
- ) : /* @__PURE__ */ jsx23(
1385
+ ) : /* @__PURE__ */ jsx24(
1183
1386
  "input",
1184
1387
  {
1185
1388
  ref: fieldRef,
@@ -1193,7 +1396,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1193
1396
  }
1194
1397
  );
1195
1398
  }
1196
- return /* @__PURE__ */ jsx23(
1399
+ return /* @__PURE__ */ jsx24(
1197
1400
  "button",
1198
1401
  {
1199
1402
  type: "button",
@@ -1213,45 +1416,45 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1213
1416
 
1214
1417
  // src/EmptyState.tsx
1215
1418
  import { IconMessage2 } from "@tabler/icons-react";
1216
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1419
+ import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
1217
1420
  function EmptyState({ icon, message, className }) {
1218
1421
  return /* @__PURE__ */ jsxs15("div", { className: cn("flex flex-col gap-2 items-center", className), children: [
1219
- /* @__PURE__ */ jsx24("span", { className: "text-text-secondary", children: icon ?? /* @__PURE__ */ jsx24(IconMessage2, { size: 16, stroke: 1.5 }) }),
1220
- /* @__PURE__ */ jsx24("p", { className: "text-body-2 text-text-secondary text-center", children: message })
1422
+ /* @__PURE__ */ jsx25("span", { className: "text-text-secondary", children: icon ?? /* @__PURE__ */ jsx25(IconMessage2, { size: 16, stroke: 1.5 }) }),
1423
+ /* @__PURE__ */ jsx25("p", { className: "text-body-2 text-text-secondary text-center", children: message })
1221
1424
  ] });
1222
1425
  }
1223
1426
 
1224
1427
  // src/Skeleton.tsx
1225
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1428
+ import { jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
1226
1429
  function SkeletonBar({ className, style }) {
1227
- return /* @__PURE__ */ jsx25("div", { className: cn("bg-bg-inset rounded-sm animate-pulse", className), style });
1430
+ return /* @__PURE__ */ jsx26("div", { className: cn("bg-bg-inset rounded-sm animate-pulse", className), style });
1228
1431
  }
1229
1432
  var ROW_BAR_WIDTHS = [150, 100, 170, 120, 90, 140, 110, 160];
1230
1433
  function SkeletonRow({ barWidth = 130 }) {
1231
1434
  return /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 px-2 h-[32px] rounded-lg", children: [
1232
- /* @__PURE__ */ jsx25(SkeletonBar, { className: "w-4 h-4 shrink-0" }),
1233
- /* @__PURE__ */ jsx25(SkeletonBar, { className: "h-3.5", style: { width: barWidth } })
1435
+ /* @__PURE__ */ jsx26(SkeletonBar, { className: "w-4 h-4 shrink-0" }),
1436
+ /* @__PURE__ */ jsx26(SkeletonBar, { className: "h-3.5", style: { width: barWidth } })
1234
1437
  ] });
1235
1438
  }
1236
1439
  function SkeletonList({ rows = 8, className }) {
1237
- return /* @__PURE__ */ jsx25("div", { className: cn("flex flex-col gap-0.5 animate-skeleton-in", className), "aria-hidden": true, children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx25(SkeletonRow, { barWidth: ROW_BAR_WIDTHS[i % ROW_BAR_WIDTHS.length] }, i)) });
1440
+ return /* @__PURE__ */ jsx26("div", { className: cn("flex flex-col gap-0.5 animate-skeleton-in", className), "aria-hidden": true, children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx26(SkeletonRow, { barWidth: ROW_BAR_WIDTHS[i % ROW_BAR_WIDTHS.length] }, i)) });
1238
1441
  }
1239
1442
 
1240
1443
  // src/Breadcrumb.tsx
1241
- import { Fragment as Fragment5, useCallback as useCallback3, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState8 } from "react";
1242
- import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
1444
+ import { Fragment as Fragment5, useCallback as useCallback4, useLayoutEffect as useLayoutEffect5, useRef as useRef7, useState as useState9 } from "react";
1445
+ import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
1243
1446
  function Breadcrumb({ items, className }) {
1244
- const navRef = useRef6(null);
1245
- const labelRefs = useRef6([]);
1246
- const [truncated, setTruncated] = useState8(/* @__PURE__ */ new Set());
1247
- const measure = useCallback3(() => {
1447
+ const navRef = useRef7(null);
1448
+ const labelRefs = useRef7([]);
1449
+ const [truncated, setTruncated] = useState9(/* @__PURE__ */ new Set());
1450
+ const measure = useCallback4(() => {
1248
1451
  const next = /* @__PURE__ */ new Set();
1249
1452
  labelRefs.current.forEach((el, index) => {
1250
1453
  if (el && el.scrollWidth > el.clientWidth) next.add(index);
1251
1454
  });
1252
1455
  setTruncated((prev) => prev.size === next.size && [...next].every((i) => prev.has(i)) ? prev : next);
1253
1456
  }, []);
1254
- useLayoutEffect4(() => {
1457
+ useLayoutEffect5(() => {
1255
1458
  measure();
1256
1459
  const nav = navRef.current;
1257
1460
  if (!nav || typeof ResizeObserver === "undefined") return;
@@ -1259,7 +1462,7 @@ function Breadcrumb({ items, className }) {
1259
1462
  observer.observe(nav);
1260
1463
  return () => observer.disconnect();
1261
1464
  }, [measure, items]);
1262
- return /* @__PURE__ */ jsx26("nav", { ref: navRef, "aria-label": "Breadcrumb", className: cn("flex min-w-0 items-center gap-1.5 text-[14px] leading-[140%]", className), children: items.map((item, index) => {
1465
+ return /* @__PURE__ */ jsx27("nav", { ref: navRef, "aria-label": "Breadcrumb", className: cn("flex min-w-0 items-center gap-1.5 text-[14px] leading-[140%]", className), children: items.map((item, index) => {
1263
1466
  const last = index === items.length - 1;
1264
1467
  const text = cn(
1265
1468
  "truncate",
@@ -1272,21 +1475,21 @@ function Breadcrumb({ items, className }) {
1272
1475
  const setLabelRef = (el) => {
1273
1476
  labelRefs.current[index] = el;
1274
1477
  };
1275
- const crumb = item.href ? /* @__PURE__ */ jsx26("a", { ref: setLabelRef, href: item.href, className: cn(text, "hover:text-text-primary"), children: item.label }) : /* @__PURE__ */ jsx26("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
1478
+ const crumb = item.href ? /* @__PURE__ */ jsx27("a", { ref: setLabelRef, href: item.href, className: cn(text, "hover:text-text-primary"), children: item.label }) : /* @__PURE__ */ jsx27("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
1276
1479
  return /* @__PURE__ */ jsxs17(Fragment5, { children: [
1277
- index > 0 && /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: "/" }),
1480
+ index > 0 && /* @__PURE__ */ jsx27("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: "/" }),
1278
1481
  truncated.has(index) ? (
1279
1482
  // `min-w-0 shrink` undoes the wrapper's own shrink-0 — the crumb
1280
1483
  // must keep truncating inside it, or wrapping it would widen the
1281
1484
  // trail and the tooltip would never be needed again.
1282
- /* @__PURE__ */ jsx26(WithTooltip, { label: item.label, wrapperClassName: "min-w-0 shrink", children: crumb })
1485
+ /* @__PURE__ */ jsx27(WithTooltip, { label: item.label, wrapperClassName: "min-w-0 shrink", children: crumb })
1283
1486
  ) : crumb
1284
1487
  ] }, `${item.label}-${index}`);
1285
1488
  }) });
1286
1489
  }
1287
1490
 
1288
1491
  // src/NavItem.tsx
1289
- import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
1492
+ import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1290
1493
  function NavItem({ label, href, count, countLabel, active = false, icon, className, ...props }) {
1291
1494
  return /* @__PURE__ */ jsxs18(
1292
1495
  "a",
@@ -1303,22 +1506,22 @@ function NavItem({ label, href, count, countLabel, active = false, icon, classNa
1303
1506
  ),
1304
1507
  ...props,
1305
1508
  children: [
1306
- icon && /* @__PURE__ */ jsx27("span", { className: "flex shrink-0 items-center", children: icon }),
1307
- /* @__PURE__ */ jsx27("span", { className: "flex-1 truncate", children: label }),
1308
- count ? /* @__PURE__ */ jsx27(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx27("span", { className: "shrink-0 font-mono text-caption tabular-nums text-text-muted", children: count }) }) : null
1509
+ icon && /* @__PURE__ */ jsx28("span", { className: "flex shrink-0 items-center", children: icon }),
1510
+ /* @__PURE__ */ jsx28("span", { className: "flex-1 truncate", children: label }),
1511
+ count ? /* @__PURE__ */ jsx28(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx28("span", { className: "shrink-0 font-mono text-caption tabular-nums text-text-muted", children: count }) }) : null
1309
1512
  ]
1310
1513
  }
1311
1514
  );
1312
1515
  }
1313
1516
 
1314
1517
  // src/Rail.tsx
1315
- import { jsx as jsx28 } from "react/jsx-runtime";
1518
+ import { jsx as jsx29 } from "react/jsx-runtime";
1316
1519
  function Rail({ "aria-label": ariaLabel = "Navigation", children, className }) {
1317
- return /* @__PURE__ */ jsx28("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
1520
+ return /* @__PURE__ */ jsx29("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
1318
1521
  }
1319
1522
 
1320
1523
  // src/RailItem.tsx
1321
- import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
1524
+ import { jsx as jsx30, jsxs as jsxs19 } from "react/jsx-runtime";
1322
1525
  function RailItem({ href, icon, label, active = false, className, ...props }) {
1323
1526
  return /* @__PURE__ */ jsxs19(
1324
1527
  "a",
@@ -1328,14 +1531,14 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
1328
1531
  className: cn("group flex w-full shrink-0 flex-col items-center gap-0.5 px-2 py-0.5", className),
1329
1532
  ...props,
1330
1533
  children: [
1331
- /* @__PURE__ */ jsx29(
1534
+ /* @__PURE__ */ jsx30(
1332
1535
  "div",
1333
1536
  {
1334
1537
  className: cn(
1335
1538
  "flex items-center justify-center rounded-lg p-2 transition-colors",
1336
1539
  active ? "bg-bg-selected" : "group-hover:bg-bg-hover"
1337
1540
  ),
1338
- children: /* @__PURE__ */ jsx29(
1541
+ children: /* @__PURE__ */ jsx30(
1339
1542
  "span",
1340
1543
  {
1341
1544
  className: cn(
@@ -1347,16 +1550,16 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
1347
1550
  )
1348
1551
  }
1349
1552
  ),
1350
- /* @__PURE__ */ jsx29("span", { className: cn("text-center text-[9px] font-medium leading-[115%]", active ? "text-text-primary" : "text-text-secondary"), children: label })
1553
+ /* @__PURE__ */ jsx30("span", { className: cn("text-center text-[9px] font-medium leading-[115%]", active ? "text-text-primary" : "text-text-secondary"), children: label })
1351
1554
  ]
1352
1555
  }
1353
1556
  );
1354
1557
  }
1355
1558
 
1356
1559
  // src/Sidebar.tsx
1357
- import { jsx as jsx30 } from "react/jsx-runtime";
1560
+ import { jsx as jsx31 } from "react/jsx-runtime";
1358
1561
  function Sidebar({ "aria-label": ariaLabel = "Workspace", children, className }) {
1359
- return /* @__PURE__ */ jsx30(
1562
+ return /* @__PURE__ */ jsx31(
1360
1563
  "nav",
1361
1564
  {
1362
1565
  "aria-label": ariaLabel,
@@ -1370,7 +1573,7 @@ function Sidebar({ "aria-label": ariaLabel = "Workspace", children, className })
1370
1573
  }
1371
1574
 
1372
1575
  // src/Property.tsx
1373
- import { jsx as jsx31, jsxs as jsxs20 } from "react/jsx-runtime";
1576
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1374
1577
  function Property({ label, layout = "row", children, className }) {
1375
1578
  if (layout === "stacked") {
1376
1579
  return (
@@ -1378,20 +1581,20 @@ function Property({ label, layout = "row", children, className }) {
1378
1581
  // Peek's topic-details sections already sit at 12px, Ship's rail was
1379
1582
  // 6px, and unifying means the bigger, calmer one.
1380
1583
  /* @__PURE__ */ jsxs20("div", { className: cn("flex flex-col gap-3", className), children: [
1381
- /* @__PURE__ */ jsx31("span", { className: "text-menu uppercase tracking-[0.08em] text-text-secondary", children: label }),
1584
+ /* @__PURE__ */ jsx32("span", { className: "text-menu uppercase tracking-[0.08em] text-text-secondary", children: label }),
1382
1585
  children
1383
1586
  ] })
1384
1587
  );
1385
1588
  }
1386
1589
  return /* @__PURE__ */ jsxs20("div", { className: cn("flex items-center gap-2", className), children: [
1387
- /* @__PURE__ */ jsx31("span", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
1590
+ /* @__PURE__ */ jsx32("span", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
1388
1591
  children
1389
1592
  ] });
1390
1593
  }
1391
1594
 
1392
1595
  // src/SearchInput.tsx
1393
1596
  import "react";
1394
- import { jsx as jsx32, jsxs as jsxs21 } from "react/jsx-runtime";
1597
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1395
1598
  function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...props }) {
1396
1599
  return /* @__PURE__ */ jsxs21(
1397
1600
  "div",
@@ -1403,7 +1606,7 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
1403
1606
  className
1404
1607
  ),
1405
1608
  children: [
1406
- /* @__PURE__ */ jsx32(
1609
+ /* @__PURE__ */ jsx33(
1407
1610
  "input",
1408
1611
  {
1409
1612
  className: "flex-1 min-w-0 bg-transparent text-input-value text-text-primary placeholder:text-text-muted outline-none",
@@ -1411,18 +1614,18 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
1411
1614
  ...props
1412
1615
  }
1413
1616
  ),
1414
- shortcut && /* @__PURE__ */ jsx32("div", { className: "flex items-center justify-center px-1 py-px rounded-sm bg-bg-inset border border-border-strong shrink-0 signal:bg-[rgba(255,255,255,.05)] signal:border-b-2", children: /* @__PURE__ */ jsx32("span", { className: "text-caption text-text-secondary whitespace-nowrap signal:font-mono signal:text-[10px]", children: shortcut }) })
1617
+ shortcut && /* @__PURE__ */ jsx33(Kbd, { children: shortcut })
1415
1618
  ]
1416
1619
  }
1417
1620
  );
1418
1621
  }
1419
1622
 
1420
1623
  // src/SectionHeader.tsx
1421
- import { useState as useState9 } from "react";
1624
+ import { useState as useState10 } from "react";
1422
1625
  import { IconChevronRight as IconChevronRight2 } from "@tabler/icons-react";
1423
- import { jsx as jsx33, jsxs as jsxs22 } from "react/jsx-runtime";
1626
+ import { jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1424
1627
  function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, actions, showActions = "hover", className }) {
1425
- const [isHovered, setIsHovered] = useState9(false);
1628
+ const [isHovered, setIsHovered] = useState10(false);
1426
1629
  return /* @__PURE__ */ jsxs22(
1427
1630
  "div",
1428
1631
  {
@@ -1437,7 +1640,7 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1437
1640
  onMouseLeave: () => setIsHovered(false),
1438
1641
  children: [
1439
1642
  /* @__PURE__ */ jsxs22("div", { className: "flex shrink-0 items-center gap-1", children: [
1440
- chevron && /* @__PURE__ */ jsx33(
1643
+ chevron && /* @__PURE__ */ jsx34(
1441
1644
  IconChevronRight2,
1442
1645
  {
1443
1646
  size: 12,
@@ -1445,9 +1648,9 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1445
1648
  className: cn("text-text-secondary transition-transform duration-150", isExpanded && "rotate-90")
1446
1649
  }
1447
1650
  ),
1448
- /* @__PURE__ */ jsx33(SectionLabel, { children: title })
1651
+ /* @__PURE__ */ jsx34(SectionLabel, { children: title })
1449
1652
  ] }),
1450
- (showActions === "always" || isHovered) && actions && actions.length > 0 && /* @__PURE__ */ jsx33("div", { className: "flex items-center gap-1", children: actions.map((action) => /* @__PURE__ */ jsx33(
1653
+ (showActions === "always" || isHovered) && actions && actions.length > 0 && /* @__PURE__ */ jsx34("div", { className: "flex items-center gap-1", children: actions.map((action) => /* @__PURE__ */ jsx34(
1451
1654
  IconButton,
1452
1655
  {
1453
1656
  tooltip: action.tooltip,
@@ -1466,9 +1669,9 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1466
1669
  }
1467
1670
 
1468
1671
  // src/Tabs.tsx
1469
- import { jsx as jsx34, jsxs as jsxs23 } from "react/jsx-runtime";
1672
+ import { jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
1470
1673
  function Tabs({ tabs, active, onChange, size = "default", className }) {
1471
- return /* @__PURE__ */ jsx34("div", { role: "tablist", className: cn("flex items-center gap-2", className), children: tabs.map((tab) => /* @__PURE__ */ jsxs23(
1674
+ return /* @__PURE__ */ jsx35("div", { role: "tablist", className: cn("flex items-center gap-2", className), children: tabs.map((tab) => /* @__PURE__ */ jsxs23(
1472
1675
  "button",
1473
1676
  {
1474
1677
  type: "button",
@@ -1489,7 +1692,7 @@ function Tabs({ tabs, active, onChange, size = "default", className }) {
1489
1692
  tab.icon,
1490
1693
  /* @__PURE__ */ jsxs23("span", { className: "flex items-baseline", children: [
1491
1694
  tab.label,
1492
- tab.count !== void 0 ? /* @__PURE__ */ jsx34("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
1695
+ tab.count !== void 0 ? /* @__PURE__ */ jsx35("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
1493
1696
  ] })
1494
1697
  ]
1495
1698
  },
@@ -1498,10 +1701,10 @@ function Tabs({ tabs, active, onChange, size = "default", className }) {
1498
1701
  }
1499
1702
 
1500
1703
  // src/Toast.tsx
1501
- import { createContext as createContext2, useCallback as useCallback4, useContext as useContext2, useEffect as useEffect6, useMemo as useMemo3, useState as useState10 } from "react";
1704
+ import { createContext as createContext3, useCallback as useCallback5, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo3, useState as useState11 } from "react";
1502
1705
  import { createPortal as createPortal6 } from "react-dom";
1503
1706
  import { IconCircleCheck } from "@tabler/icons-react";
1504
- import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
1707
+ import { jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
1505
1708
  var SURFACE_BY_TYPE = {
1506
1709
  success: "bg-success-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]",
1507
1710
  brand: "bg-accent-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]",
@@ -1532,10 +1735,10 @@ function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAct
1532
1735
  ),
1533
1736
  children: [
1534
1737
  /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2 shrink-0", children: [
1535
- leadingIcon && /* @__PURE__ */ jsx35(IconCircleCheck, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_BY_TYPE[type]) }),
1536
- /* @__PURE__ */ jsx35("span", { className: "font-normal text-[14px] leading-[1.4] text-text-primary whitespace-nowrap", children: label })
1738
+ leadingIcon && /* @__PURE__ */ jsx36(IconCircleCheck, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_BY_TYPE[type]) }),
1739
+ /* @__PURE__ */ jsx36("span", { className: "font-normal text-[14px] leading-[1.4] text-text-primary whitespace-nowrap", children: label })
1537
1740
  ] }),
1538
- hasAction && /* @__PURE__ */ jsx35(
1741
+ hasAction && /* @__PURE__ */ jsx36(
1539
1742
  "button",
1540
1743
  {
1541
1744
  type: "button",
@@ -1545,21 +1748,21 @@ function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAct
1545
1748
  ACTION_BORDER_BY_TYPE[type],
1546
1749
  type === "neutral" ? "hover:border-border-strong" : "hover:opacity-80"
1547
1750
  ),
1548
- children: /* @__PURE__ */ jsx35("span", { className: "font-medium text-[12px] leading-[12px] text-text-primary whitespace-nowrap", children: actionLabel })
1751
+ children: /* @__PURE__ */ jsx36("span", { className: "font-medium text-[12px] leading-[12px] text-text-primary whitespace-nowrap", children: actionLabel })
1549
1752
  }
1550
1753
  )
1551
1754
  ]
1552
1755
  }
1553
1756
  );
1554
1757
  }
1555
- var ToastContext = createContext2(null);
1758
+ var ToastContext = createContext3(null);
1556
1759
  var toastSeq = 0;
1557
1760
  function ToastProvider({ children }) {
1558
- const [toast, setToast] = useState10(null);
1559
- const showToast = useCallback4((opts) => {
1761
+ const [toast, setToast] = useState11(null);
1762
+ const showToast = useCallback5((opts) => {
1560
1763
  setToast({ ...opts, id: ++toastSeq });
1561
1764
  }, []);
1562
- const dismissToast = useCallback4(() => {
1765
+ const dismissToast = useCallback5(() => {
1563
1766
  setToast(null);
1564
1767
  }, []);
1565
1768
  useEffect6(() => {
@@ -1575,7 +1778,7 @@ function ToastProvider({ children }) {
1575
1778
  return /* @__PURE__ */ jsxs24(ToastContext.Provider, { value, children: [
1576
1779
  children,
1577
1780
  toast && createPortal6(
1578
- /* @__PURE__ */ jsx35("div", { className: "fixed bottom-4 left-4 z-[100] pointer-events-none", children: /* @__PURE__ */ jsx35(
1781
+ /* @__PURE__ */ jsx36("div", { className: "fixed bottom-4 left-4 z-[100] pointer-events-none", children: /* @__PURE__ */ jsx36(
1579
1782
  Toast,
1580
1783
  {
1581
1784
  className: "pointer-events-auto",
@@ -1594,7 +1797,7 @@ function ToastProvider({ children }) {
1594
1797
  ] });
1595
1798
  }
1596
1799
  function useToast() {
1597
- const ctx = useContext2(ToastContext);
1800
+ const ctx = useContext3(ToastContext);
1598
1801
  if (!ctx) throw new Error("useToast must be used within ToastProvider");
1599
1802
  return ctx;
1600
1803
  }
@@ -1619,10 +1822,13 @@ export {
1619
1822
  IdentityMenu,
1620
1823
  IdentityPanel,
1621
1824
  InputChip,
1825
+ Kbd,
1622
1826
  Menu,
1623
1827
  MenuItem,
1828
+ MenuPanel,
1624
1829
  MenuRow,
1625
1830
  MenuSection,
1831
+ MenuSub,
1626
1832
  NavItem,
1627
1833
  Person,
1628
1834
  PersonTrigger,
@@ -1645,7 +1851,10 @@ export {
1645
1851
  Tooltip,
1646
1852
  TopBar,
1647
1853
  WithTooltip,
1854
+ clampBox,
1648
1855
  cn,
1856
+ fitMenu,
1857
+ fitSubmenu,
1649
1858
  hueFor,
1650
1859
  initialsFor,
1651
1860
  useFieldControlId,