@estiva-app/ui 0.9.0 → 0.10.1

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 (154) hide show
  1. package/dist/Avatar.d.ts +13 -1
  2. package/dist/Avatar.d.ts.map +1 -1
  3. package/dist/AvatarGroup.d.ts.map +1 -1
  4. package/dist/Banner.d.ts +16 -1
  5. package/dist/Banner.d.ts.map +1 -1
  6. package/dist/Breadcrumb.d.ts +9 -0
  7. package/dist/Breadcrumb.d.ts.map +1 -1
  8. package/dist/Button.d.ts +12 -2
  9. package/dist/Button.d.ts.map +1 -1
  10. package/dist/Checkbox.d.ts +2 -0
  11. package/dist/Checkbox.d.ts.map +1 -1
  12. package/dist/Chip.d.ts +3 -2
  13. package/dist/Chip.d.ts.map +1 -1
  14. package/dist/ChipInput.d.ts +3 -1
  15. package/dist/ChipInput.d.ts.map +1 -1
  16. package/dist/ConfirmDialog.d.ts +5 -0
  17. package/dist/ConfirmDialog.d.ts.map +1 -1
  18. package/dist/DialogShell.d.ts +27 -3
  19. package/dist/DialogShell.d.ts.map +1 -1
  20. package/dist/EditableText.d.ts +6 -0
  21. package/dist/EditableText.d.ts.map +1 -1
  22. package/dist/Field.d.ts +42 -25
  23. package/dist/Field.d.ts.map +1 -1
  24. package/dist/IconButton.d.ts +3 -2
  25. package/dist/IconButton.d.ts.map +1 -1
  26. package/dist/IdentityMenu.d.ts +25 -13
  27. package/dist/IdentityMenu.d.ts.map +1 -1
  28. package/dist/Menu.d.ts +65 -82
  29. package/dist/Menu.d.ts.map +1 -1
  30. package/dist/PersonTrigger.d.ts.map +1 -1
  31. package/dist/Popover.d.ts +86 -0
  32. package/dist/Popover.d.ts.map +1 -0
  33. package/dist/PreviewCard.d.ts +39 -0
  34. package/dist/PreviewCard.d.ts.map +1 -0
  35. package/dist/Reaction.d.ts.map +1 -1
  36. package/dist/ReactionPicker.d.ts +55 -0
  37. package/dist/ReactionPicker.d.ts.map +1 -0
  38. package/dist/SearchInput.d.ts +5 -0
  39. package/dist/SearchInput.d.ts.map +1 -1
  40. package/dist/Select.d.ts +26 -16
  41. package/dist/Select.d.ts.map +1 -1
  42. package/dist/Tabs.d.ts +11 -1
  43. package/dist/Tabs.d.ts.map +1 -1
  44. package/dist/TextInput.d.ts +7 -1
  45. package/dist/TextInput.d.ts.map +1 -1
  46. package/dist/Textarea.d.ts +8 -1
  47. package/dist/Textarea.d.ts.map +1 -1
  48. package/dist/Toolbar.d.ts +95 -0
  49. package/dist/Toolbar.d.ts.map +1 -0
  50. package/dist/Tooltip.d.ts +36 -10
  51. package/dist/Tooltip.d.ts.map +1 -1
  52. package/dist/cn.d.ts.map +1 -1
  53. package/dist/fit.d.ts +7 -72
  54. package/dist/fit.d.ts.map +1 -1
  55. package/dist/index.d.ts +6 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +888 -763
  58. package/dist/index.js.map +4 -4
  59. package/dist/triggerDisabled.d.ts +10 -0
  60. package/dist/triggerDisabled.d.ts.map +1 -0
  61. package/package.json +1 -1
  62. package/src/Avatar.mdx +17 -0
  63. package/src/Avatar.name.test.tsx +99 -0
  64. package/src/Avatar.tsx +25 -3
  65. package/src/AvatarGroup.tsx +4 -1
  66. package/src/Banner.mdx +10 -2
  67. package/src/Banner.stories.tsx +29 -0
  68. package/src/Banner.test.tsx +60 -0
  69. package/src/Banner.tsx +35 -3
  70. package/src/Breadcrumb.mdx +5 -2
  71. package/src/Breadcrumb.test.tsx +44 -0
  72. package/src/Breadcrumb.tsx +10 -2
  73. package/src/Button.compose.test.tsx +119 -0
  74. package/src/Button.mdx +7 -5
  75. package/src/Button.test.tsx +2 -1
  76. package/src/Button.tsx +21 -5
  77. package/src/Checkbox.tsx +3 -0
  78. package/src/Chip.tsx +3 -2
  79. package/src/ChipInput.tsx +4 -0
  80. package/src/ConfirmDialog.mdx +20 -1
  81. package/src/ConfirmDialog.test.tsx +109 -0
  82. package/src/ConfirmDialog.tsx +6 -0
  83. package/src/DialogShell.mdx +18 -4
  84. package/src/DialogShell.stories.tsx +56 -6
  85. package/src/DialogShell.test.tsx +184 -0
  86. package/src/DialogShell.tsx +108 -39
  87. package/src/EditableText.mdx +6 -1
  88. package/src/EditableText.test.tsx +156 -0
  89. package/src/EditableText.tsx +23 -5
  90. package/src/Field.mdx +23 -3
  91. package/src/Field.stories.tsx +35 -0
  92. package/src/Field.test.tsx +144 -26
  93. package/src/Field.tsx +94 -59
  94. package/src/IconButton.mdx +6 -2
  95. package/src/IconButton.test.tsx +5 -2
  96. package/src/IconButton.tsx +12 -6
  97. package/src/IdentityMenu.mdx +29 -6
  98. package/src/IdentityMenu.stories.tsx +41 -6
  99. package/src/IdentityMenu.tsx +74 -47
  100. package/src/Kbd.stories.tsx +3 -3
  101. package/src/Menu.mdx +88 -75
  102. package/src/Menu.stories.tsx +120 -52
  103. package/src/Menu.test.tsx +315 -0
  104. package/src/Menu.tsx +347 -251
  105. package/src/MenuItem.stories.tsx +135 -98
  106. package/src/NavItem.mdx +1 -1
  107. package/src/PersonTrigger.mdx +20 -14
  108. package/src/PersonTrigger.tsx +23 -1
  109. package/src/Popover.mdx +130 -0
  110. package/src/Popover.stories.tsx +222 -0
  111. package/src/Popover.test.tsx +188 -0
  112. package/src/Popover.tsx +156 -0
  113. package/src/PreviewCard.mdx +98 -0
  114. package/src/PreviewCard.stories.tsx +168 -0
  115. package/src/PreviewCard.test.tsx +83 -0
  116. package/src/PreviewCard.tsx +91 -0
  117. package/src/Rail.mdx +63 -0
  118. package/src/Rail.stories.tsx +81 -0
  119. package/src/Reaction.mdx +4 -2
  120. package/src/Reaction.tsx +4 -2
  121. package/src/ReactionPicker.mdx +85 -0
  122. package/src/ReactionPicker.stories.tsx +120 -0
  123. package/src/ReactionPicker.test.tsx +118 -0
  124. package/src/ReactionPicker.tsx +88 -0
  125. package/src/SearchInput.mdx +6 -2
  126. package/src/SearchInput.tsx +7 -1
  127. package/src/Select.mdx +16 -5
  128. package/src/Select.test.tsx +157 -0
  129. package/src/Select.tsx +89 -214
  130. package/src/Sidebar.mdx +6 -2
  131. package/src/Tabs.mdx +5 -0
  132. package/src/Tabs.test.tsx +15 -0
  133. package/src/Tabs.tsx +17 -2
  134. package/src/TextInput.mdx +8 -2
  135. package/src/TextInput.tsx +10 -7
  136. package/src/Textarea.mdx +6 -2
  137. package/src/Textarea.tsx +14 -9
  138. package/src/Toolbar.mdx +104 -0
  139. package/src/Toolbar.stories.tsx +142 -0
  140. package/src/Toolbar.test.tsx +253 -0
  141. package/src/Toolbar.tsx +158 -0
  142. package/src/Tooltip.mdx +43 -5
  143. package/src/Tooltip.stories.tsx +26 -0
  144. package/src/Tooltip.test.tsx +195 -0
  145. package/src/Tooltip.tsx +150 -48
  146. package/src/cn.ts +1 -1
  147. package/src/fit.test.ts +101 -0
  148. package/src/fit.ts +19 -63
  149. package/src/index.ts +6 -3
  150. package/src/triggerDisabled.ts +13 -0
  151. package/stories/Choosing.mdx +8 -3
  152. package/tailwind-preset.js +7 -0
  153. package/src/Menu.fit.test.ts +0 -90
  154. package/src/Select.fit.test.ts +0 -101
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var FONT_SIZE_TOKENS = [
19
19
  "input-helper",
20
20
  "chip"
21
21
  ];
22
- var BOX_SHADOW_TOKENS = ["sm", "md", "lg", "focus-ring", "glow-warning", "highlight-inset"];
22
+ var BOX_SHADOW_TOKENS = ["sm", "md", "lg", "focus-ring", "glow-warning", "glow-success", "glow-accent", "highlight-inset"];
23
23
  var DROP_SHADOW_TOKENS = ["glow-success", "glow-accent"];
24
24
  var twMerge = extendTailwindMerge({
25
25
  extend: {
@@ -119,22 +119,33 @@ var initialsFor = (name) => {
119
119
  const initials = words.map((w) => w[0]).join("") || name.trim().charAt(0);
120
120
  return initials.toUpperCase();
121
121
  };
122
- function Avatar({ src, name, alt = "", size = 36, className }) {
122
+ function Avatar({ src, name, alt = "", size = 36, label: spoken, className }) {
123
123
  const [broken, setBroken] = useState(false);
124
124
  const label = name || alt;
125
125
  const picture = src && !broken ? src : void 0;
126
- 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(
126
+ return /* @__PURE__ */ jsx3(
127
127
  "div",
128
128
  {
129
- className: "w-full h-full flex items-center justify-center font-semibold leading-none",
130
- style: {
131
- color: "#08121c",
132
- fontSize: Math.round(size * 0.36),
133
- background: `linear-gradient(160deg, color-mix(in srgb, ${hueFor(label)} 92%, #fff) 0%, color-mix(in srgb, ${hueFor(label)} 70%, #0b0d11) 100%)`
134
- },
135
- children: initialsFor(label)
129
+ ...spoken ? { role: "img", "aria-label": spoken } : { "aria-hidden": true },
130
+ className: cn("rounded-sm overflow-hidden shrink-0 bg-bg-inset", className),
131
+ style: { width: size, height: size },
132
+ children: picture ? /* @__PURE__ */ jsx3("img", { src: picture, alt: "", className: "w-full h-full object-cover", onError: () => setBroken(true) }) : label ? /* @__PURE__ */ jsx3(
133
+ "div",
134
+ {
135
+ className: "w-full h-full flex items-center justify-center font-semibold leading-none",
136
+ style: {
137
+ // The one ink colour that reads on all eight hues, which are a
138
+ // palette rather than tokens (see the note at the top) — so its
139
+ // ink cannot be a token either.
140
+ color: "#08121c",
141
+ fontSize: Math.round(size * 0.36),
142
+ background: `linear-gradient(160deg, color-mix(in srgb, ${hueFor(label)} 92%, #fff) 0%, color-mix(in srgb, ${hueFor(label)} 70%, #0b0d11) 100%)`
143
+ },
144
+ children: initialsFor(label)
145
+ }
146
+ ) : /* @__PURE__ */ jsx3("div", { className: "w-full h-full bg-accent-muted flex items-center justify-center text-text-muted", children: /* @__PURE__ */ jsx3(IconUserFilled, { size: Math.round(size * 0.5) }) })
136
147
  }
137
- ) : /* @__PURE__ */ jsx3("div", { className: "w-full h-full bg-accent-muted flex items-center justify-center text-text-muted", children: /* @__PURE__ */ jsx3(IconUserFilled, { size: Math.round(size * 0.5) }) }) });
148
+ );
138
149
  }
139
150
 
140
151
  // src/AvatarGroup.tsx
@@ -154,7 +165,7 @@ function AvatarGroup({ members, size = 24 }) {
154
165
  {
155
166
  className: "relative flex rounded-sm",
156
167
  style: { marginRight: -overlap, boxShadow: `0 0 0 ${ring}px var(--bg-surface)` },
157
- children: /* @__PURE__ */ jsx4(Avatar, { size, name: member.name, src: member.picture, alt: member.name })
168
+ children: /* @__PURE__ */ jsx4(Avatar, { size, name: member.name, src: member.picture, label: member.name })
158
169
  },
159
170
  i
160
171
  )
@@ -162,28 +173,18 @@ function AvatarGroup({ members, size = 24 }) {
162
173
  }
163
174
 
164
175
  // src/Banner.tsx
165
- import { jsx as jsx5 } from "react/jsx-runtime";
166
- var TONE_STYLES = {
167
- ok: "bg-success-muted text-success-default",
168
- error: "bg-error-muted text-error-default",
169
- info: "bg-info-muted text-info-default",
170
- warning: "bg-warning-muted text-warning-default"
171
- };
172
- function Banner({ tone, children, className }) {
173
- return /* @__PURE__ */ jsx5("div", { role: tone === "error" ? "alert" : "status", className: cn("px-4 py-2 text-body-2", TONE_STYLES[tone], className), children });
174
- }
176
+ import { IconX } from "@tabler/icons-react";
175
177
 
176
- // src/Button.tsx
178
+ // src/IconButton.tsx
177
179
  import { Button as BaseButton } from "@base-ui/react/button";
178
180
 
179
181
  // src/Tooltip.tsx
180
- import { useCallback, useLayoutEffect, useRef, useState as useState2 } from "react";
181
- import { createPortal } from "react-dom";
182
+ import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
182
183
 
183
184
  // src/Kbd.tsx
184
- import { jsx as jsx6 } from "react/jsx-runtime";
185
+ import { jsx as jsx5 } from "react/jsx-runtime";
185
186
  function Kbd({ children, className }) {
186
- return /* @__PURE__ */ jsx6(
187
+ return /* @__PURE__ */ jsx5(
187
188
  "kbd",
188
189
  {
189
190
  className: cn(
@@ -198,48 +199,133 @@ function Kbd({ children, className }) {
198
199
  }
199
200
 
200
201
  // src/Tooltip.tsx
201
- import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
202
- function Tooltip({ label, shortcut, className }) {
203
- return /* @__PURE__ */ jsxs3("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: [
204
- /* @__PURE__ */ jsx7("span", { className: "text-caption text-text-primary whitespace-nowrap", children: label }),
205
- shortcut && /* @__PURE__ */ jsx7(Kbd, { children: shortcut })
202
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
203
+ var OPEN_DELAY = 300;
204
+ function TooltipProvider({ delay = OPEN_DELAY, closeDelay, timeout, children }) {
205
+ return /* @__PURE__ */ jsx6(BaseTooltip.Provider, { delay, closeDelay, timeout, children });
206
+ }
207
+ function Tooltip({ label, shortcut, className, ...props }) {
208
+ return /* @__PURE__ */ jsxs3("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), ...props, children: [
209
+ /* @__PURE__ */ jsx6("span", { className: "text-caption text-text-primary whitespace-nowrap", children: label }),
210
+ shortcut && /* @__PURE__ */ jsx6(Kbd, { children: shortcut })
206
211
  ] });
207
212
  }
208
213
  var GAP = 6;
209
214
  var VIEWPORT_PAD = 8;
215
+ function TooltipSurface({ label, shortcut, placement }) {
216
+ return /* @__PURE__ */ jsx6(BaseTooltip.Portal, { children: /* @__PURE__ */ jsx6(
217
+ BaseTooltip.Positioner,
218
+ {
219
+ side: placement,
220
+ align: "center",
221
+ sideOffset: GAP,
222
+ collisionPadding: VIEWPORT_PAD,
223
+ positionMethod: "fixed",
224
+ className: "pointer-events-none z-[9999]",
225
+ children: /* @__PURE__ */ jsx6(
226
+ BaseTooltip.Popup,
227
+ {
228
+ render: /* @__PURE__ */ jsx6(
229
+ Tooltip,
230
+ {
231
+ label,
232
+ shortcut,
233
+ className: cn(
234
+ "transition-[opacity,transform] duration-[120ms] ease-out motion-reduce:transition-none",
235
+ "data-[ending-style]:duration-[80ms] data-[instant]:duration-0",
236
+ "data-[starting-style]:opacity-0 data-[ending-style]:opacity-0",
237
+ "data-[side=top]:data-[starting-style]:translate-y-[4px] data-[side=top]:data-[ending-style]:translate-y-[4px]",
238
+ "data-[side=bottom]:data-[starting-style]:-translate-y-[4px] data-[side=bottom]:data-[ending-style]:-translate-y-[4px]"
239
+ )
240
+ }
241
+ )
242
+ }
243
+ )
244
+ }
245
+ ) });
246
+ }
210
247
  function WithTooltip({ label, shortcut, placement = "top", wrapperClassName, children }) {
211
- const [show, setShow] = useState2(false);
212
- const ref = useRef(null);
213
- const tooltipRef = useRef(null);
214
- const [style, setStyle] = useState2({ position: "fixed", zIndex: 9999, pointerEvents: "none", visibility: "hidden" });
215
- const reposition = useCallback(() => {
216
- const trigger = ref.current;
217
- const tip = tooltipRef.current;
218
- if (!trigger || !tip) return;
219
- const triggerRect = trigger.getBoundingClientRect();
220
- const tipRect = tip.getBoundingClientRect();
221
- let top = placement === "bottom" ? triggerRect.bottom + GAP : triggerRect.top - tipRect.height - GAP;
222
- if (placement === "top" && top < VIEWPORT_PAD) top = triggerRect.bottom + GAP;
223
- else if (placement === "bottom" && top + tipRect.height > window.innerHeight - VIEWPORT_PAD) top = triggerRect.top - tipRect.height - GAP;
224
- top = Math.max(VIEWPORT_PAD, Math.min(top, window.innerHeight - tipRect.height - VIEWPORT_PAD));
225
- let left = triggerRect.left + triggerRect.width / 2 - tipRect.width / 2;
226
- left = Math.max(VIEWPORT_PAD, Math.min(left, window.innerWidth - tipRect.width - VIEWPORT_PAD));
227
- setStyle({ position: "fixed", top, left, zIndex: 9999, pointerEvents: "none", visibility: "visible" });
228
- }, [placement]);
229
- useLayoutEffect(() => {
230
- if (show) reposition();
231
- }, [show, reposition]);
232
- return /* @__PURE__ */ jsxs3("div", { ref, className: cn("inline-flex shrink-0", wrapperClassName), onMouseEnter: () => setShow(true), onMouseLeave: () => setShow(false), children: [
233
- children,
234
- show && createPortal(
235
- /* @__PURE__ */ jsx7("div", { ref: tooltipRef, style, children: /* @__PURE__ */ jsx7(Tooltip, { label, shortcut }) }),
236
- document.body
237
- )
248
+ return /* @__PURE__ */ jsxs3(BaseTooltip.Root, { disableHoverablePopup: true, children: [
249
+ /* @__PURE__ */ jsx6(BaseTooltip.Trigger, { delay: OPEN_DELAY, render: /* @__PURE__ */ jsx6("div", { className: cn("inline-flex shrink-0", wrapperClassName) }), children }),
250
+ /* @__PURE__ */ jsx6(TooltipSurface, { label, shortcut, placement })
251
+ ] });
252
+ }
253
+ function TooltipTrigger({ label, shortcut, placement = "top", children }) {
254
+ return /* @__PURE__ */ jsxs3(BaseTooltip.Root, { disableHoverablePopup: true, children: [
255
+ /* @__PURE__ */ jsx6(BaseTooltip.Trigger, { delay: OPEN_DELAY, render: children }),
256
+ /* @__PURE__ */ jsx6(TooltipSurface, { label, shortcut, placement })
238
257
  ] });
239
258
  }
240
259
 
241
- // src/Button.tsx
260
+ // src/IconButton.tsx
261
+ import { jsx as jsx7 } from "react/jsx-runtime";
262
+ function IconButton({
263
+ variant = "muted",
264
+ className,
265
+ children,
266
+ disabled,
267
+ disabledReason,
268
+ tooltip,
269
+ tooltipShortcut,
270
+ tooltipPlacement,
271
+ type = "button",
272
+ ...props
273
+ }) {
274
+ const button = /* @__PURE__ */ jsx7(
275
+ BaseButton,
276
+ {
277
+ type,
278
+ disabled: disabled || !!disabledReason,
279
+ focusableWhenDisabled: !!disabledReason,
280
+ className: (state) => cn(
281
+ "flex items-center justify-center p-1 rounded-lg transition-colors shrink-0 cursor-pointer",
282
+ !state.disabled && variant === "primary" && "bg-accent-primary hover:bg-accent-hover text-text-inverse",
283
+ !state.disabled && variant === "muted" && "text-text-secondary hover:bg-bg-hover hover:text-text-primary",
284
+ !state.disabled && variant === "outlined" && "border border-border-default hover:bg-bg-hover text-text-secondary",
285
+ state.disabled && variant === "primary" && "bg-bg-disabled text-text-disabled",
286
+ state.disabled && variant === "muted" && "text-text-disabled",
287
+ state.disabled && variant === "outlined" && "border border-border-default text-text-disabled",
288
+ // `pointer-events-none` only where the button is truly out of reach:
289
+ // with a `disabledReason` the button IS the tooltip's trigger, and a
290
+ // trigger the pointer cannot land on never opens one. Base UI already
291
+ // swallows the click.
292
+ state.disabled && !disabledReason && "pointer-events-none",
293
+ state.disabled && "cursor-not-allowed",
294
+ className
295
+ ),
296
+ ...props,
297
+ children
298
+ }
299
+ );
300
+ const label = disabledReason ?? tooltip;
301
+ if (label) {
302
+ return /* @__PURE__ */ jsx7(TooltipTrigger, { label, shortcut: disabledReason ? void 0 : tooltipShortcut, placement: tooltipPlacement, children: button });
303
+ }
304
+ return button;
305
+ }
306
+
307
+ // src/Banner.tsx
242
308
  import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
309
+ var TONE_STYLES = {
310
+ ok: "bg-success-muted text-success-default",
311
+ error: "bg-error-muted text-error-default",
312
+ info: "bg-info-muted text-info-default",
313
+ warning: "bg-warning-muted text-warning-default"
314
+ };
315
+ function Banner({ tone, children, onDismiss, dismissLabel = "Dismiss", className }) {
316
+ const role = tone === "error" ? "alert" : "status";
317
+ if (!onDismiss) {
318
+ return /* @__PURE__ */ jsx8("div", { role, className: cn("px-4 py-2 text-body-2", TONE_STYLES[tone], className), children });
319
+ }
320
+ return /* @__PURE__ */ jsxs4("div", { role, className: cn("flex items-center gap-3 px-4 py-2 text-body-2", TONE_STYLES[tone], className), children: [
321
+ /* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1", children }),
322
+ /* @__PURE__ */ jsx8(IconButton, { "aria-label": dismissLabel, onClick: onDismiss, className: "-mr-1 shrink-0 text-current hover:text-current", children: /* @__PURE__ */ jsx8(IconX, { size: 16, stroke: 1.5 }) })
323
+ ] });
324
+ }
325
+
326
+ // src/Button.tsx
327
+ import { Button as BaseButton2 } from "@base-ui/react/button";
328
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
243
329
  function Button({
244
330
  variant = "muted",
245
331
  size = "default",
@@ -252,8 +338,8 @@ function Button({
252
338
  ...props
253
339
  }) {
254
340
  const hasLeadingIcon = !!leadingIcon;
255
- const button = /* @__PURE__ */ jsxs4(
256
- BaseButton,
341
+ const button = /* @__PURE__ */ jsxs5(
342
+ BaseButton2,
257
343
  {
258
344
  type,
259
345
  disabled: disabled || !!disabledReason,
@@ -269,7 +355,13 @@ function Button({
269
355
  !state.disabled && variant === "outlined" && "border border-border-default hover:bg-bg-hover text-text-primary cursor-pointer",
270
356
  !state.disabled && variant === "muted" && "hover:bg-bg-hover text-text-primary cursor-pointer",
271
357
  !state.disabled && variant === "destructive" && "hover:bg-error-muted text-error-default cursor-pointer",
272
- state.disabled && "bg-bg-disabled text-text-disabled pointer-events-none",
358
+ state.disabled && "bg-bg-disabled text-text-disabled",
359
+ // `pointer-events-none` only where the button is truly out of reach.
360
+ // With a `disabledReason` the button IS the tooltip's trigger, and a
361
+ // trigger the pointer cannot land on never opens one — Base UI already
362
+ // swallows the click (`focusableWhenDisabled` gives `aria-disabled`
363
+ // and a prevented `onClick`), so nothing else needs it.
364
+ state.disabled && !disabledReason && "pointer-events-none",
273
365
  state.disabled && variant === "outlined" && "border border-border-default",
274
366
  className
275
367
  ),
@@ -280,13 +372,13 @@ function Button({
280
372
  ]
281
373
  }
282
374
  );
283
- return disabledReason ? /* @__PURE__ */ jsx8(WithTooltip, { label: disabledReason, children: button }) : button;
375
+ return disabledReason ? /* @__PURE__ */ jsx9(TooltipTrigger, { label: disabledReason, children: button }) : button;
284
376
  }
285
377
 
286
378
  // src/Checkbox.tsx
287
379
  import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
288
380
  import { IconCheck } from "@tabler/icons-react";
289
- import { jsx as jsx9 } from "react/jsx-runtime";
381
+ import { jsx as jsx10 } from "react/jsx-runtime";
290
382
  function squareClasses(checked, disabled, interactive, className) {
291
383
  return cn(
292
384
  "inline-flex items-center justify-center size-4 shrink-0 rounded-[4px] border transition-colors",
@@ -299,24 +391,25 @@ function squareClasses(checked, disabled, interactive, className) {
299
391
  var tickClasses = (checked) => cn("flex", !checked && "invisible");
300
392
  function Checkbox({ checked, onChange, disabled = false, className, ...aria }) {
301
393
  if (!onChange) {
302
- return /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", className: squareClasses(checked, disabled, false, className), children: /* @__PURE__ */ jsx9("span", { className: tickClasses(checked), children: /* @__PURE__ */ jsx9(IconCheck, { size: 12, stroke: 3 }) }) });
394
+ return /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", className: squareClasses(checked, disabled, false, className), children: /* @__PURE__ */ jsx10("span", { className: tickClasses(checked), children: /* @__PURE__ */ jsx10(IconCheck, { size: 12, stroke: 3 }) }) });
303
395
  }
304
- return /* @__PURE__ */ jsx9(
396
+ return /* @__PURE__ */ jsx10(
305
397
  BaseCheckbox.Root,
306
398
  {
307
399
  checked,
308
400
  disabled,
309
401
  "aria-label": aria["aria-label"],
402
+ "aria-required": aria["aria-required"],
310
403
  onCheckedChange: (next) => onChange(next),
311
404
  onClick: (e) => e.stopPropagation(),
312
405
  className: (state) => squareClasses(state.checked, state.disabled, true, className),
313
- children: /* @__PURE__ */ jsx9(BaseCheckbox.Indicator, { keepMounted: true, className: (state) => tickClasses(state.checked), children: /* @__PURE__ */ jsx9(IconCheck, { size: 12, stroke: 3 }) })
406
+ children: /* @__PURE__ */ jsx10(BaseCheckbox.Indicator, { keepMounted: true, className: (state) => tickClasses(state.checked), children: /* @__PURE__ */ jsx10(IconCheck, { size: 12, stroke: 3 }) })
314
407
  }
315
408
  );
316
409
  }
317
410
 
318
411
  // src/Chip.tsx
319
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
412
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
320
413
  var typeStyles = {
321
414
  neutral: "bg-bg-inset text-text-primary",
322
415
  brand: "bg-accent-muted text-accent-primary signal:border signal:border-accent-outline",
@@ -326,17 +419,17 @@ var typeStyles = {
326
419
  error: "bg-error-muted text-error-default signal:border signal:border-error-outline"
327
420
  };
328
421
  function Chip({ type = "neutral", label, leadingIcon, trailingIcon, className }) {
329
- return /* @__PURE__ */ jsxs5("div", { className: cn("inline-flex items-center justify-center gap-1.5 rounded-full max-h-[20px] min-w-[16px] px-2 py-1", typeStyles[type], className), children: [
330
- leadingIcon && /* @__PURE__ */ jsx10("span", { className: "flex size-3 shrink-0 items-center justify-center", children: leadingIcon }),
331
- label && /* @__PURE__ */ jsx10("span", { className: "text-chip whitespace-nowrap signal:font-mono signal:text-[10px] signal:font-semibold signal:tracking-[0.02em] signal:tabular-nums", children: label }),
332
- trailingIcon && /* @__PURE__ */ jsx10("span", { className: "flex size-3 shrink-0 items-center justify-center", children: trailingIcon })
422
+ return /* @__PURE__ */ jsxs6("div", { className: cn("inline-flex items-center justify-center gap-1.5 rounded-full max-h-[20px] min-w-[16px] px-2 py-1", typeStyles[type], className), children: [
423
+ leadingIcon && /* @__PURE__ */ jsx11("span", { className: "flex size-3 shrink-0 items-center justify-center", children: leadingIcon }),
424
+ label && /* @__PURE__ */ jsx11("span", { className: "text-chip whitespace-nowrap signal:font-mono signal:text-[10px] signal:font-semibold signal:tracking-[0.02em] signal:tabular-nums", children: label }),
425
+ trailingIcon && /* @__PURE__ */ jsx11("span", { className: "flex size-3 shrink-0 items-center justify-center", children: trailingIcon })
333
426
  ] });
334
427
  }
335
428
 
336
429
  // src/ChipInput.tsx
337
- import { useState as useState4, useRef as useRef3, useMemo, useEffect as useEffect2, useLayoutEffect as useLayoutEffect3 } from "react";
338
- import { createPortal as createPortal3 } from "react-dom";
339
- import { IconX } from "@tabler/icons-react";
430
+ import { useState as useState2, useRef, useMemo, useEffect, useLayoutEffect } from "react";
431
+ import { createPortal } from "react-dom";
432
+ import { IconX as IconX2 } from "@tabler/icons-react";
340
433
 
341
434
  // src/fit.ts
342
435
  var MARGIN = 8;
@@ -354,35 +447,22 @@ function fitMenu({
354
447
  const maxHeight = Math.max(Math.min(cap, openUp ? above : below), 120);
355
448
  return openUp ? { left, bottom: viewport.height - anchor.top + GAP2, maxHeight } : { left, top: anchor.bottom + GAP2, maxHeight };
356
449
  }
357
- function clampBox({
358
- box,
359
- viewport
360
- }) {
361
- const left = Math.max(MARGIN, Math.min(box.left, viewport.width - box.width - MARGIN));
362
- const maxHeight = Math.max(Math.min(box.height, viewport.height - 2 * MARGIN), 120);
363
- const top = Math.max(MARGIN, Math.min(box.top, viewport.height - maxHeight - MARGIN));
364
- return { left, top, maxHeight };
365
- }
366
- function fitSubmenu({
367
- row,
368
- panel,
369
- viewport
370
- }) {
371
- const fitsRight = row.right + GAP2 + panel.width <= viewport.width - MARGIN;
372
- const left = Math.max(MARGIN, fitsRight ? row.right + GAP2 : row.left - GAP2 - panel.width);
373
- const top = Math.max(MARGIN, Math.min(row.top, viewport.height - panel.height - MARGIN));
374
- return { left, top };
375
- }
376
450
 
377
451
  // src/Menu.tsx
378
- import { createContext, useCallback as useCallback2, useContext, useEffect, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState3 } from "react";
452
+ import { createContext, useContext } from "react";
379
453
  import { IconChevronRight } from "@tabler/icons-react";
380
- import { createPortal as createPortal2 } from "react-dom";
454
+ import { Menu as BaseMenu } from "@base-ui/react/menu";
455
+
456
+ // src/triggerDisabled.ts
457
+ function triggerDisabled(trigger) {
458
+ const props = trigger.props;
459
+ return !!(props.disabled || props.disabledReason);
460
+ }
381
461
 
382
462
  // src/SectionLabel.tsx
383
- import { jsx as jsx11 } from "react/jsx-runtime";
463
+ import { jsx as jsx12 } from "react/jsx-runtime";
384
464
  function SectionLabel({ children, className }) {
385
- return /* @__PURE__ */ jsx11(
465
+ return /* @__PURE__ */ jsx12(
386
466
  "span",
387
467
  {
388
468
  className: cn(
@@ -395,224 +475,217 @@ function SectionLabel({ children, className }) {
395
475
  }
396
476
 
397
477
  // src/Menu.tsx
398
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
399
- var MenuHoverContext = createContext(null);
478
+ import { Fragment, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
479
+ var MenuContext = createContext(null);
400
480
  function MenuPanel({ children, className, ...props }) {
401
- return /* @__PURE__ */ jsx12(
481
+ return /* @__PURE__ */ jsx13(
402
482
  "div",
403
483
  {
404
- className: cn("flex flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg", className),
484
+ className: cn(
485
+ "flex flex-col rounded-lg border border-border-default bg-bg-elevated p-2 shadow-lg",
486
+ /*
487
+ * A divider in a menu runs the width of the rows it separates.
488
+ *
489
+ * `Divider` is inset 12px each side, which is right in a page and
490
+ * wrong here: measured in a 180px menu, the rule started 21px from the
491
+ * panel's edge where the rows start at 9px — 12px narrower on each side
492
+ * than the things it divides. `IdentityMenu` was already cancelling it
493
+ * by hand with `mx-0`, which is the sign it belonged here (Katerina,
494
+ * 2026-09-08). Direct children only, so a divider a caller puts inside
495
+ * a row keeps its own spacing.
496
+ */
497
+ "[&>[role=separator]]:mx-0",
498
+ className
499
+ ),
405
500
  ...props,
406
501
  children
407
502
  }
408
503
  );
409
504
  }
410
- function Menu({ onClose, anchor, align = "left", position, closeOnLeave = false, children, className }) {
411
- const ref = useRef2(null);
412
- const leaveTimer = useRef2(void 0);
413
- const hold = useCallback2(() => clearTimeout(leaveTimer.current), []);
414
- const release = useCallback2(() => {
415
- if (!closeOnLeave) return;
416
- clearTimeout(leaveTimer.current);
417
- leaveTimer.current = setTimeout(onClose, 150);
418
- }, [closeOnLeave, onClose]);
419
- useEffect(() => () => clearTimeout(leaveTimer.current), []);
420
- const portalled = Boolean(anchor || position);
421
- const [placed, setPlaced] = useState3(null);
422
- useEffect(() => {
423
- const onDown = (event) => {
424
- if (!ref.current?.contains(event.target)) onClose();
425
- };
426
- const onKey = (event) => {
427
- if (event.key === "Escape") onClose();
428
- };
429
- document.addEventListener("mousedown", onDown);
430
- document.addEventListener("keydown", onKey);
431
- return () => {
432
- document.removeEventListener("mousedown", onDown);
433
- document.removeEventListener("keydown", onKey);
434
- };
435
- }, [onClose]);
436
- const posLeft = position && "left" in position ? position.left : void 0;
437
- const posRight = position && "right" in position ? position.right : void 0;
438
- const posTop = position?.top;
439
- useLayoutEffect2(() => {
440
- if (!portalled || !ref.current) return;
441
- const viewport = { width: window.innerWidth, height: window.innerHeight };
442
- const menu = ref.current;
443
- if (anchor) {
444
- const rect = anchor instanceof Element ? anchor.getBoundingClientRect() : anchor;
445
- const left = align === "right" ? rect.right - menu.offsetWidth : rect.left;
446
- setPlaced(
447
- fitMenu({
448
- anchor: { left, top: rect.top, bottom: rect.bottom },
449
- menu: { width: menu.offsetWidth, contentHeight: menu.scrollHeight },
450
- viewport
451
- })
452
- );
453
- } else if (posTop !== void 0) {
454
- const left = posLeft ?? viewport.width - (posRight ?? 0) - menu.offsetWidth;
455
- setPlaced(clampBox({ box: { left, top: posTop, width: menu.offsetWidth, height: menu.offsetHeight }, viewport }));
456
- }
457
- }, [portalled, anchor, align, posLeft, posRight, posTop]);
458
- useEffect(() => {
459
- if (!anchor) return;
460
- const onScroll = (event) => {
461
- if (event.target instanceof Node && ref.current?.contains(event.target)) return;
462
- onClose();
463
- };
464
- window.addEventListener("resize", onClose);
465
- window.addEventListener("scroll", onScroll, true);
466
- return () => {
467
- window.removeEventListener("resize", onClose);
468
- window.removeEventListener("scroll", onScroll, true);
469
- };
470
- }, [anchor, onClose]);
471
- const style = portalled ? placed ? { left: placed.left, top: placed.top, bottom: placed.bottom, maxHeight: placed.maxHeight } : (
472
- // The provisional render: measured by the layout effect, never seen.
473
- { left: 0, top: 0, visibility: "hidden" }
474
- ) : void 0;
475
- const node = /* @__PURE__ */ jsx12(MenuHoverContext.Provider, { value: { hold, release }, children: /* @__PURE__ */ jsx12(
476
- MenuPanel,
505
+ var isProduction = () => globalThis.process?.env?.NODE_ENV === "production";
506
+ var GAP3 = 4;
507
+ var VIEWPORT_PAD2 = 8;
508
+ var HOVER_OPEN_DELAY = 0;
509
+ var HOVER_CLOSE_DELAY = 150;
510
+ function Menu({ trigger, align = "left", openOnHover = false, open, onOpenChange, actionsRef, children, className }) {
511
+ return /* @__PURE__ */ jsxs7(
512
+ BaseMenu.Root,
477
513
  {
478
- ref,
479
- role: "menu",
480
- "data-interactive": true,
481
- className: cn(
482
- "z-50 min-w-[180px]",
483
- portalled ? "fixed overflow-y-auto" : "absolute right-0 top-full mt-1",
484
- className
485
- ),
486
- style,
487
- onClick: (event) => event.stopPropagation(),
488
- onMouseEnter: closeOnLeave ? hold : void 0,
489
- onMouseLeave: closeOnLeave ? release : void 0,
490
- children
514
+ open,
515
+ onOpenChange: onOpenChange ? (next) => onOpenChange(next) : void 0,
516
+ actionsRef,
517
+ modal: false,
518
+ children: [
519
+ /* @__PURE__ */ jsx13(
520
+ BaseMenu.Trigger,
521
+ {
522
+ render: trigger,
523
+ disabled: triggerDisabled(trigger),
524
+ openOnHover,
525
+ delay: HOVER_OPEN_DELAY,
526
+ closeDelay: HOVER_CLOSE_DELAY
527
+ }
528
+ ),
529
+ /* @__PURE__ */ jsx13(BaseMenu.Portal, { children: /* @__PURE__ */ jsx13(
530
+ BaseMenu.Positioner,
531
+ {
532
+ side: "bottom",
533
+ align: align === "right" ? "end" : "start",
534
+ sideOffset: GAP3,
535
+ collisionPadding: VIEWPORT_PAD2,
536
+ className: "z-50 data-[anchor-hidden]:hidden",
537
+ children: /* @__PURE__ */ jsx13(
538
+ BaseMenu.Popup,
539
+ {
540
+ "data-interactive": true,
541
+ className: cn("min-w-[180px] max-h-[var(--available-height)] overflow-y-auto outline-none", className),
542
+ render: /* @__PURE__ */ jsx13(MenuPanel, {}),
543
+ children: /* @__PURE__ */ jsx13(MenuContext.Provider, { value: { openOnHover }, children })
544
+ }
545
+ )
546
+ }
547
+ ) })
548
+ ]
491
549
  }
492
- ) });
493
- return portalled ? createPortal2(node, document.body) : node;
550
+ );
494
551
  }
495
552
  function MenuSub({ label, leading, selected, children, className }) {
496
- const [open, setOpen] = useState3(false);
497
- const rowRef = useRef2(null);
498
- const panelRef = useRef2(null);
499
- const closeTimer = useRef2(void 0);
500
- const [placed, setPlaced] = useState3(null);
501
- const menuHover = useContext(MenuHoverContext);
502
- const enter = () => {
503
- clearTimeout(closeTimer.current);
504
- menuHover?.hold();
505
- setOpen(true);
506
- };
507
- const leave = () => {
508
- closeTimer.current = setTimeout(() => setOpen(false), 150);
509
- menuHover?.release();
510
- };
511
- useEffect(() => () => clearTimeout(closeTimer.current), []);
512
- useLayoutEffect2(() => {
513
- if (!open || !rowRef.current || !panelRef.current) {
514
- setPlaced(null);
515
- return;
516
- }
517
- const row = rowRef.current.getBoundingClientRect();
518
- setPlaced(
519
- fitSubmenu({
520
- row: { left: row.left, right: row.right, top: row.top },
521
- panel: { width: panelRef.current.offsetWidth, height: panelRef.current.offsetHeight },
522
- viewport: { width: window.innerWidth, height: window.innerHeight }
523
- })
553
+ const menu = useContext(MenuContext);
554
+ if (!isProduction() && menu?.openOnHover) {
555
+ console.error(
556
+ '[@estiva-app/ui] Menu: `openOnHover` and `MenuSub` cannot be used together \u2014 leaving the submenu panel strands the menu open. Open this menu on a press instead. See Menu.mdx, "Hover-opened menus".'
524
557
  );
525
- }, [open]);
526
- return /* @__PURE__ */ jsxs6("div", { ref: rowRef, onMouseEnter: enter, onMouseLeave: leave, children: [
527
- /* @__PURE__ */ jsx12(MenuItem, { label, leading, selected, submenu: true }),
528
- open && createPortal2(
529
- /* @__PURE__ */ jsx12(
530
- MenuPanel,
531
- {
532
- ref: panelRef,
533
- role: "menu",
534
- "data-interactive": true,
535
- className: cn("fixed z-50 w-[160px]", className),
536
- style: placed ?? { left: 0, top: 0, visibility: "hidden" },
537
- onMouseEnter: enter,
538
- onMouseLeave: leave,
539
- children
540
- }
541
- ),
542
- document.body
543
- )
558
+ }
559
+ return /* @__PURE__ */ jsxs7(BaseMenu.SubmenuRoot, { children: [
560
+ /* @__PURE__ */ jsx13(
561
+ BaseMenu.SubmenuTrigger,
562
+ {
563
+ openOnHover: true,
564
+ delay: 0,
565
+ closeDelay: 150,
566
+ nativeButton: true,
567
+ render: /* @__PURE__ */ jsx13("button", { type: "button" }),
568
+ className: menuItemClassName({ size: "default", selected }),
569
+ children: /* @__PURE__ */ jsx13(MenuItemBody, { label, leading, submenu: true })
570
+ }
571
+ ),
572
+ /* @__PURE__ */ jsx13(BaseMenu.Portal, { children: /* @__PURE__ */ jsx13(
573
+ BaseMenu.Positioner,
574
+ {
575
+ side: "inline-end",
576
+ align: "start",
577
+ sideOffset: GAP3,
578
+ collisionPadding: VIEWPORT_PAD2,
579
+ className: "z-50 data-[anchor-hidden]:hidden",
580
+ children: /* @__PURE__ */ jsx13(BaseMenu.Popup, { className: cn("w-[160px]", className), "data-interactive": true, render: /* @__PURE__ */ jsx13(MenuPanel, {}), children })
581
+ }
582
+ ) })
583
+ ] });
584
+ }
585
+ function menuItemClassName({ size, selected, className }) {
586
+ return cn(
587
+ // shrink-0: a menu is a flex column that scrolls at its max height,
588
+ // and a flex child shrinks before its container does — so every row
589
+ // in an overflowing menu was squashed to its `min-h`, and a row given
590
+ // an explicit height silently lost it (Peek's `[` menu: h-12 rows
591
+ // measured 40px). The same fix NavItem took on 2026-09-02.
592
+ // `group`: the `hint` slot reveals itself from this row's own :hover,
593
+ // so the hint and the fill are one CSS state change, not two engines.
594
+ //
595
+ // No transition on the fill (Katerina, 2026-09-05). It faded over
596
+ // 150ms, and anything appearing with it had to fade too or arrive
597
+ // ahead of it — which, sweeping a pointer down a list, read as the
598
+ // hint flickering in and out. Both are instant now: they still change
599
+ // on exactly the same :hover, so they cannot come apart, and a row
600
+ // lights and unlights crisply as the pointer crosses it.
601
+ "group flex w-full shrink-0 cursor-pointer items-center rounded-lg text-left outline-none hover:bg-bg-hover data-[highlighted]:bg-bg-hover",
602
+ // tall: as tall as its content, never shorter than 40px (Katerina,
603
+ // 2026-09-01) — a single-line picker row sits at 40, a row with a
604
+ // 32px face and a role line comes out at its natural 48. One rule,
605
+ // not a hand-picked height per file.
606
+ size === "tall" ? "min-h-10 gap-3 px-3 py-1.5" : "gap-2 px-2 py-1.5",
607
+ selected && "bg-bg-hover",
608
+ className
609
+ );
610
+ }
611
+ function MenuItemBody({ label, children, size = "default", description, leading, trailing, hint, shortcut, submenu, destructive, selected }) {
612
+ const edge = trailing ?? (shortcut ? /* @__PURE__ */ jsx13(Kbd, { children: shortcut }) : submenu ? /* @__PURE__ */ jsx13(IconChevronRight, { size: 16, stroke: 1.5, className: "shrink-0 text-text-muted" }) : null);
613
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
614
+ leading && /* @__PURE__ */ jsx13("span", { className: "flex shrink-0 items-center", children: leading }),
615
+ children ?? /* @__PURE__ */ jsxs7("span", { className: cn("flex min-w-0 flex-1 flex-col", size === "tall" && "gap-[2px]"), children: [
616
+ /* @__PURE__ */ jsx13("span", { className: cn("truncate text-[14px] leading-[140%]", destructive ? "text-error-default" : "text-text-primary"), children: label }),
617
+ description && /* @__PURE__ */ jsx13("span", { className: "truncate text-[12px] leading-[120%] text-text-secondary", children: description })
618
+ ] }),
619
+ (edge || hint) && // One grid cell holding both, right-aligned: the slot is as wide as
620
+ // the wider of the two and never changes, so revealing the hint moves
621
+ // nothing. No transition here either — the hint switches on the same
622
+ // :hover / `selected` as the fill, in the same frame.
623
+ /* @__PURE__ */ jsxs7("span", { className: "grid shrink-0 items-center justify-items-end [&>*]:col-start-1 [&>*]:row-start-1", children: [
624
+ edge && /* @__PURE__ */ jsx13("span", { className: cn("flex items-center", hint && "group-hover:opacity-0 group-data-[highlighted]:opacity-0", hint && selected && "opacity-0"), children: edge }),
625
+ hint && /* @__PURE__ */ jsx13("span", { className: cn("flex items-center opacity-0 group-hover:opacity-100 group-data-[highlighted]:opacity-100", selected && "opacity-100"), children: hint })
626
+ ] })
544
627
  ] });
545
628
  }
546
629
  function MenuItem({ label, children, size = "default", description, leading, trailing, hint, shortcut, submenu, destructive, selected, className, ...props }) {
547
- const edge = trailing ?? (shortcut ? /* @__PURE__ */ jsx12(Kbd, { children: shortcut }) : submenu ? /* @__PURE__ */ jsx12(IconChevronRight, { size: 16, stroke: 1.5, className: "shrink-0 text-text-muted" }) : null);
548
- return /* @__PURE__ */ jsxs6(
549
- "button",
630
+ const body = /* @__PURE__ */ jsx13(
631
+ MenuItemBody,
550
632
  {
551
- type: "button",
552
- role: "menuitem",
553
- className: cn(
554
- // shrink-0: a menu is a flex column that scrolls at its max height,
555
- // and a flex child shrinks before its container does — so every row
556
- // in an overflowing menu was squashed to its `min-h`, and a row given
557
- // an explicit height silently lost it (Peek's `[` menu: h-12 rows
558
- // measured 40px). The same fix NavItem took on 2026-09-02.
559
- // `group`: the `hint` slot reveals itself from this row's own :hover,
560
- // so the hint and the fill are one CSS state change, not two engines.
561
- //
562
- // No transition on the fill (Katerina, 2026-09-05). It faded over
563
- // 150ms, and anything appearing with it had to fade too or arrive
564
- // ahead of it — which, sweeping a pointer down a list, read as the
565
- // hint flickering in and out. Both are instant now: they still change
566
- // on exactly the same :hover, so they cannot come apart, and a row
567
- // lights and unlights crisply as the pointer crosses it.
568
- "group flex w-full shrink-0 cursor-pointer items-center rounded-lg text-left hover:bg-bg-hover",
569
- // tall: as tall as its content, never shorter than 40px (Katerina,
570
- // 2026-09-01) — a single-line picker row sits at 40, a row with a
571
- // 32px face and a role line comes out at its natural 48. One rule,
572
- // not a hand-picked height per file.
573
- size === "tall" ? "min-h-10 gap-3 px-3 py-1.5" : "gap-2 px-2 py-1.5",
574
- selected && "bg-bg-hover",
575
- className
576
- ),
633
+ label,
634
+ size,
635
+ description,
636
+ leading,
637
+ trailing,
638
+ hint,
639
+ shortcut,
640
+ submenu,
641
+ destructive,
642
+ selected,
643
+ children
644
+ }
645
+ );
646
+ const rowClassName = menuItemClassName({ size, selected, className });
647
+ if (!useContext(MenuContext)) {
648
+ return /* @__PURE__ */ jsx13("button", { type: "button", className: rowClassName, ...props, children: body });
649
+ }
650
+ return /* @__PURE__ */ jsx13(
651
+ BaseMenu.Item,
652
+ {
653
+ nativeButton: true,
654
+ render: /* @__PURE__ */ jsx13("button", { type: "button" }),
655
+ className: rowClassName,
577
656
  ...props,
578
- children: [
579
- leading && /* @__PURE__ */ jsx12("span", { className: "flex shrink-0 items-center", children: leading }),
580
- children ?? /* @__PURE__ */ jsxs6("span", { className: cn("flex min-w-0 flex-1 flex-col", size === "tall" && "gap-[2px]"), children: [
581
- /* @__PURE__ */ jsx12("span", { className: cn("truncate text-[14px] leading-[140%]", destructive ? "text-error-default" : "text-text-primary"), children: label }),
582
- description && /* @__PURE__ */ jsx12("span", { className: "truncate text-[12px] leading-[120%] text-text-secondary", children: description })
583
- ] }),
584
- (edge || hint) && // One grid cell holding both, right-aligned: the slot is as wide as
585
- // the wider of the two and never changes, so revealing the hint moves
586
- // nothing. No transition here either — the hint switches on the same
587
- // :hover / `selected` as the fill, in the same frame.
588
- /* @__PURE__ */ jsxs6("span", { className: "grid shrink-0 items-center justify-items-end [&>*]:col-start-1 [&>*]:row-start-1", children: [
589
- edge && /* @__PURE__ */ jsx12("span", { className: cn("flex items-center", hint && "group-hover:opacity-0", hint && selected && "opacity-0"), children: edge }),
590
- hint && /* @__PURE__ */ jsx12("span", { className: cn("flex items-center opacity-0 group-hover:opacity-100", selected && "opacity-100"), children: hint })
591
- ] })
592
- ]
657
+ children: body
593
658
  }
594
659
  );
595
660
  }
596
661
  function EnterHint({ target }) {
597
- return /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1.5 text-text-muted", children: [
598
- /* @__PURE__ */ jsx12(Kbd, { children: "\u21A9 Enter" }),
599
- target && /* @__PURE__ */ jsx12("span", { className: "text-[9px] font-medium leading-[115%] signal:font-mono signal:text-[9.5px] signal:tracking-[0.04em]", children: target })
662
+ return /* @__PURE__ */ jsxs7("span", { className: "flex shrink-0 items-center gap-1.5 text-text-muted", children: [
663
+ /* @__PURE__ */ jsx13(Kbd, { children: "\u21A9 Enter" }),
664
+ target && /* @__PURE__ */ jsx13("span", { className: "text-[9px] font-medium leading-[115%] signal:font-mono signal:text-[9.5px] signal:tracking-[0.04em]", children: target })
600
665
  ] });
601
666
  }
602
667
  function MenuSection({ label, children, className }) {
603
- return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col", children: [
604
- /* @__PURE__ */ jsx12("div", { className: cn("flex h-8 items-center px-2", className), children: /* @__PURE__ */ jsx12(SectionLabel, { className: "text-text-secondary", children: label }) }),
668
+ const inMenu = useContext(MenuContext) !== null;
669
+ const heading = /* @__PURE__ */ jsx13("div", { className: cn("flex h-8 items-center px-2", className), children: /* @__PURE__ */ jsx13(SectionLabel, { className: "text-text-secondary", children: label }) });
670
+ if (!inMenu) {
671
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col", children: [
672
+ heading,
673
+ children
674
+ ] });
675
+ }
676
+ return /* @__PURE__ */ jsxs7(BaseMenu.Group, { className: "flex flex-col", children: [
677
+ /* @__PURE__ */ jsx13(BaseMenu.GroupLabel, { render: heading }),
605
678
  children
606
679
  ] });
607
680
  }
608
681
  function MenuRow({ children, className }) {
609
- return /* @__PURE__ */ jsx12("div", { className: cn("flex items-center gap-2 px-2 py-1.5", className), children });
682
+ return /* @__PURE__ */ jsx13("div", { className: cn("flex items-center gap-2 px-2 py-1.5", className), children });
610
683
  }
611
684
 
612
685
  // src/ChipInput.tsx
613
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
686
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
614
687
  function InputChip({ label, leading, onRemove, className }) {
615
- return /* @__PURE__ */ jsxs7(
688
+ return /* @__PURE__ */ jsxs8(
616
689
  "div",
617
690
  {
618
691
  className: cn(
@@ -628,9 +701,9 @@ function InputChip({ label, leading, onRemove, className }) {
628
701
  className
629
702
  ),
630
703
  children: [
631
- leading && /* @__PURE__ */ jsx13("span", { className: "flex shrink-0 items-center", children: leading }),
632
- /* @__PURE__ */ jsx13("span", { className: "text-caption font-medium text-text-primary", children: label }),
633
- onRemove && /* @__PURE__ */ jsx13(
704
+ leading && /* @__PURE__ */ jsx14("span", { className: "flex shrink-0 items-center", children: leading }),
705
+ /* @__PURE__ */ jsx14("span", { className: "text-caption font-medium text-text-primary", children: label }),
706
+ onRemove && /* @__PURE__ */ jsx14(
634
707
  "button",
635
708
  {
636
709
  type: "button",
@@ -640,7 +713,7 @@ function InputChip({ label, leading, onRemove, className }) {
640
713
  },
641
714
  className: "size-4 flex items-center justify-center rounded-full hover:bg-bg-hover text-text-secondary",
642
715
  "aria-label": `Remove ${label}`,
643
- children: /* @__PURE__ */ jsx13(IconX, { size: 10, stroke: 1.5 })
716
+ children: /* @__PURE__ */ jsx14(IconX2, { size: 10, stroke: 1.5 })
644
717
  }
645
718
  )
646
719
  ]
@@ -655,14 +728,15 @@ function ChipInput({
655
728
  autoFocus,
656
729
  excludeIds = [],
657
730
  chipLeading,
658
- rowLeading
731
+ rowLeading,
732
+ ...aria
659
733
  }) {
660
- const [query, setQuery] = useState4("");
661
- const [highlight, setHighlight] = useState4(0);
662
- const [isFocused, setIsFocused] = useState4(false);
663
- const [anchorRect, setAnchorRect] = useState4(null);
664
- const wrapperRef = useRef3(null);
665
- const inputRef = useRef3(null);
734
+ const [query, setQuery] = useState2("");
735
+ const [highlight, setHighlight] = useState2(0);
736
+ const [isFocused, setIsFocused] = useState2(false);
737
+ const [anchorRect, setAnchorRect] = useState2(null);
738
+ const wrapperRef = useRef(null);
739
+ const inputRef = useRef(null);
666
740
  const matches = useMemo(() => {
667
741
  const selectedIds = new Set(value.map((o) => o.id));
668
742
  const excludedIds = new Set(excludeIds);
@@ -674,11 +748,11 @@ function ChipInput({
674
748
  return o.label.toLowerCase().includes(q) || (o.description ?? "").toLowerCase().includes(q);
675
749
  });
676
750
  }, [query, value, excludeIds, options]);
677
- useEffect2(() => {
751
+ useEffect(() => {
678
752
  setHighlight(0);
679
753
  }, [query, matches.length]);
680
754
  const showDropdown = isFocused && query.trim().length > 0 && matches.length > 0;
681
- useLayoutEffect3(() => {
755
+ useLayoutEffect(() => {
682
756
  if (!showDropdown) return;
683
757
  const update = () => {
684
758
  if (wrapperRef.current) setAnchorRect(wrapperRef.current.getBoundingClientRect());
@@ -728,16 +802,16 @@ function ChipInput({
728
802
  }
729
803
  }
730
804
  }
731
- return /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
732
- /* @__PURE__ */ jsxs7(
805
+ return /* @__PURE__ */ jsxs8("div", { className: "relative", children: [
806
+ /* @__PURE__ */ jsxs8(
733
807
  "div",
734
808
  {
735
809
  ref: wrapperRef,
736
810
  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",
737
811
  onClick: () => inputRef.current?.focus(),
738
812
  children: [
739
- value.map((o) => /* @__PURE__ */ jsx13(InputChip, { label: o.label, leading: chipLeading?.(o), onRemove: () => removeOption(o.id) }, o.id)),
740
- /* @__PURE__ */ jsx13(
813
+ value.map((o) => /* @__PURE__ */ jsx14(InputChip, { label: o.label, leading: chipLeading?.(o), onRemove: () => removeOption(o.id) }, o.id)),
814
+ /* @__PURE__ */ jsx14(
741
815
  "input",
742
816
  {
743
817
  ref: inputRef,
@@ -751,14 +825,15 @@ function ChipInput({
751
825
  setTimeout(() => setIsFocused(false), 150);
752
826
  },
753
827
  placeholder: value.length === 0 ? placeholder : "",
828
+ "aria-required": aria["aria-required"],
754
829
  className: "flex-1 min-w-[120px] bg-transparent text-body-2 text-text-primary placeholder:text-text-muted outline-none border-none"
755
830
  }
756
831
  )
757
832
  ]
758
833
  }
759
834
  ),
760
- showDropdown && anchorRect && createPortal3(
761
- /* @__PURE__ */ jsx13(
835
+ showDropdown && anchorRect && createPortal(
836
+ /* @__PURE__ */ jsx14(
762
837
  "div",
763
838
  {
764
839
  className: "fixed z-[60] overflow-y-auto bg-bg-elevated border border-border-default rounded-lg shadow-lg",
@@ -771,7 +846,7 @@ function ChipInput({
771
846
  }),
772
847
  width: anchorRect.width
773
848
  },
774
- children: matches.map((o, i) => /* @__PURE__ */ jsx13(
849
+ children: matches.map((o, i) => /* @__PURE__ */ jsx14(
775
850
  MenuItem,
776
851
  {
777
852
  size: "tall",
@@ -795,51 +870,8 @@ function ChipInput({
795
870
  ] });
796
871
  }
797
872
 
798
- // src/IconButton.tsx
799
- import { Button as BaseButton2 } from "@base-ui/react/button";
800
- import { jsx as jsx14 } from "react/jsx-runtime";
801
- function IconButton({
802
- variant = "muted",
803
- className,
804
- children,
805
- disabled,
806
- disabledReason,
807
- tooltip,
808
- tooltipShortcut,
809
- tooltipPlacement,
810
- type = "button",
811
- ...props
812
- }) {
813
- const button = /* @__PURE__ */ jsx14(
814
- BaseButton2,
815
- {
816
- type,
817
- disabled: disabled || !!disabledReason,
818
- focusableWhenDisabled: !!disabledReason,
819
- className: (state) => cn(
820
- "flex items-center justify-center p-1 rounded-lg transition-colors shrink-0 cursor-pointer",
821
- !state.disabled && variant === "primary" && "bg-accent-primary hover:bg-accent-hover text-text-inverse",
822
- !state.disabled && variant === "muted" && "text-text-secondary hover:bg-bg-hover hover:text-text-primary",
823
- !state.disabled && variant === "outlined" && "border border-border-default hover:bg-bg-hover text-text-secondary",
824
- state.disabled && variant === "primary" && "bg-bg-disabled text-text-disabled",
825
- state.disabled && variant === "muted" && "text-text-disabled",
826
- state.disabled && variant === "outlined" && "border border-border-default text-text-disabled",
827
- state.disabled && "pointer-events-none cursor-not-allowed",
828
- className
829
- ),
830
- ...props,
831
- children
832
- }
833
- );
834
- const label = disabledReason ?? tooltip;
835
- if (label) {
836
- return /* @__PURE__ */ jsx14(WithTooltip, { label, shortcut: disabledReason ? void 0 : tooltipShortcut, placement: tooltipPlacement, children: button });
837
- }
838
- return button;
839
- }
840
-
841
873
  // src/IdentityMenu.tsx
842
- import { useRef as useRef4, useState as useState5 } from "react";
874
+ import { useCallback, useRef as useRef2 } from "react";
843
875
 
844
876
  // src/Divider.tsx
845
877
  import { jsx as jsx15 } from "react/jsx-runtime";
@@ -855,13 +887,13 @@ function Divider({ orientation = "horizontal", className }) {
855
887
  }
856
888
 
857
889
  // src/Person.tsx
858
- import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
890
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
859
891
  function Person({ name, picture, fallback = "\u2014", size = 20, className }) {
860
892
  return (
861
893
  // gap-2: the face-to-name distance is one rule (8px) wherever a person
862
894
  // appears — Katerina, 2026-09-01, set on Person so PersonTrigger and every
863
895
  // row agree by construction.
864
- /* @__PURE__ */ jsxs8("span", { className: cn("flex min-w-0 items-center gap-2", className), children: [
896
+ /* @__PURE__ */ jsxs9("span", { className: cn("flex min-w-0 items-center gap-2", className), children: [
865
897
  /* @__PURE__ */ jsx16(Avatar, { name, src: picture, size }),
866
898
  /* @__PURE__ */ jsx16("span", { className: cn("truncate", !name && "text-text-muted"), children: name ?? fallback })
867
899
  ] })
@@ -871,7 +903,7 @@ function Person({ name, picture, fallback = "\u2014", size = 20, className }) {
871
903
  // src/PersonTrigger.tsx
872
904
  import { Button as BaseButton3 } from "@base-ui/react/button";
873
905
  import { IconChevronDown } from "@tabler/icons-react";
874
- import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
906
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
875
907
  function PersonTrigger({ name, picture, fallback, size, open = false, compact = false, className, ...props }) {
876
908
  if (compact) {
877
909
  return /* @__PURE__ */ jsx17(
@@ -880,13 +912,14 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
880
912
  type: "button",
881
913
  "aria-haspopup": "menu",
882
914
  "aria-expanded": open,
883
- className: cn("cursor-pointer rounded-full focus:outline-none", className),
915
+ "aria-label": props["aria-label"] ?? name ?? fallback,
916
+ className: cn("cursor-pointer rounded-full", className),
884
917
  ...props,
885
918
  children: /* @__PURE__ */ jsx17(Avatar, { name, src: picture, size: size ?? 36 })
886
919
  }
887
920
  );
888
921
  }
889
- return /* @__PURE__ */ jsxs9(
922
+ return /* @__PURE__ */ jsxs10(
890
923
  BaseButton3,
891
924
  {
892
925
  type: "button",
@@ -895,6 +928,11 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
895
928
  className: cn(
896
929
  "flex h-8 cursor-pointer items-center gap-1.5 rounded-md pl-1.5 pr-1.5 text-body-2 text-text-primary transition-colors hover:bg-bg-hover",
897
930
  open && "bg-bg-hover",
931
+ // Under a `Menu` the open state is Base UI's, not a prop: the trigger
932
+ // carries `data-popup-open` while its menu is up, and sets its own
933
+ // `aria-expanded`. Both spellings hold the fill, so this works whether
934
+ // the caller drives it or the menu does.
935
+ "data-[popup-open]:bg-bg-hover",
898
936
  className
899
937
  ),
900
938
  ...props,
@@ -907,30 +945,34 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
907
945
  }
908
946
 
909
947
  // src/IdentityMenu.tsx
910
- import { Fragment, jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
911
- function IdentityPanel({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, onClose, anchor, className, children }) {
948
+ import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
949
+ function IdentityPanel({ className, onClose = () => {
950
+ }, ...rest }) {
951
+ return /* @__PURE__ */ jsx18(MenuPanel, { className: cn("w-72", className), children: /* @__PURE__ */ jsx18(IdentityRows, { ...rest, onClose }) });
952
+ }
953
+ function IdentityRows({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, onClose, children }) {
912
954
  const act = (action) => () => {
913
955
  onClose();
914
956
  action();
915
957
  };
916
958
  const appRows = typeof children === "function" ? children(onClose) : children;
917
- return /* @__PURE__ */ jsxs10(Menu, { onClose, anchor, align: "right", className: cn("w-72", className), children: [
918
- /* @__PURE__ */ jsxs10(MenuSection, { label: signedIn ? "Signed in as" : "Acting as", children: [
959
+ return /* @__PURE__ */ jsxs11(Fragment2, { children: [
960
+ /* @__PURE__ */ jsxs11(MenuSection, { label: signedIn ? "Signed in as" : "Acting as", children: [
919
961
  /* @__PURE__ */ jsx18(MenuRow, { children: /* @__PURE__ */ jsx18(Person, { name: me.name, picture: me.picture, fallback: "Anonymous", size: 28, className: "text-body-2-strong" }) }),
920
962
  /* @__PURE__ */ jsx18(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." }),
921
- me.email && /* @__PURE__ */ jsxs10(Note, { children: [
963
+ me.email && /* @__PURE__ */ jsxs11(Note, { children: [
922
964
  me.email,
923
965
  " \xB7 known to Estiva, never published to the relay"
924
966
  ] })
925
967
  ] }),
926
- relayUrl && /* @__PURE__ */ jsxs10(Fragment, { children: [
968
+ relayUrl && /* @__PURE__ */ jsxs11(Fragment2, { children: [
927
969
  /* @__PURE__ */ jsx18(Divider, { className: "mx-0 my-2" }),
928
- /* @__PURE__ */ jsxs10(MenuSection, { label: "Workspace", children: [
970
+ /* @__PURE__ */ jsxs11(MenuSection, { label: "Workspace", children: [
929
971
  /* @__PURE__ */ jsx18(MenuRow, { children: /* @__PURE__ */ jsx18("span", { className: "break-all font-mono text-caption text-text-primary", children: relayUrl }) }),
930
972
  /* @__PURE__ */ jsx18(Note, { children: "Everyone on this relay shares this workspace, in every app." })
931
973
  ] })
932
974
  ] }),
933
- appRows && /* @__PURE__ */ jsxs10(Fragment, { children: [
975
+ appRows && /* @__PURE__ */ jsxs11(Fragment2, { children: [
934
976
  /* @__PURE__ */ jsx18(Divider, { className: "mx-0 my-2" }),
935
977
  appRows
936
978
  ] }),
@@ -947,280 +989,178 @@ function IdentityPanel({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, o
947
989
  ] });
948
990
  }
949
991
  function IdentityMenu({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, compact = false, className, children }) {
950
- const [open, setOpen] = useState5(false);
951
- const anchorRef = useRef4(null);
952
- return /* @__PURE__ */ jsxs10("div", { ref: anchorRef, className: cn("relative", className), children: [
953
- /* @__PURE__ */ jsx18(
954
- PersonTrigger,
955
- {
956
- name: me.name,
957
- picture: me.picture,
958
- fallback: "Anonymous",
959
- compact,
960
- size: compact ? 36 : void 0,
961
- open,
962
- onMouseDown: (event) => event.stopPropagation(),
963
- onClick: () => setOpen((value) => !value),
964
- "aria-label": compact ? "Account menu" : void 0
965
- }
966
- ),
967
- open && /* @__PURE__ */ jsx18(
968
- IdentityPanel,
992
+ const actions = useRef2(null);
993
+ const close = useCallback(() => actions.current?.close(), []);
994
+ return (
995
+ /* The wrapper carries the caller's `className` and nothing else. It used
996
+ to be `relative`, because the panel was positioned against it; the panel
997
+ hangs from the trigger and portals now, so a positioning context here
998
+ would only be a lie about what this box does. */
999
+ /* @__PURE__ */ jsx18("div", { className, children: /* @__PURE__ */ jsx18(
1000
+ Menu,
969
1001
  {
970
- me,
971
- signedIn,
972
- relayUrl,
973
- idBase,
974
- onCopyKey,
975
- onSignOut,
976
- onClose: () => setOpen(false),
977
- anchor: anchorRef.current,
978
- children
1002
+ align: "right",
1003
+ actionsRef: actions,
1004
+ className: "w-72",
1005
+ trigger: /* @__PURE__ */ jsx18(
1006
+ PersonTrigger,
1007
+ {
1008
+ name: me.name,
1009
+ picture: me.picture,
1010
+ fallback: "Anonymous",
1011
+ compact,
1012
+ size: compact ? 36 : void 0,
1013
+ "aria-label": compact ? "Account menu" : void 0
1014
+ }
1015
+ ),
1016
+ children: /* @__PURE__ */ jsx18(
1017
+ IdentityRows,
1018
+ {
1019
+ me,
1020
+ signedIn,
1021
+ relayUrl,
1022
+ idBase,
1023
+ onCopyKey,
1024
+ onSignOut,
1025
+ onClose: close,
1026
+ children
1027
+ }
1028
+ )
979
1029
  }
980
- )
981
- ] });
1030
+ ) })
1031
+ );
982
1032
  }
983
1033
  function Note({ children }) {
984
1034
  return /* @__PURE__ */ jsx18("span", { className: "px-2 pb-1.5 text-caption text-text-secondary", children });
985
1035
  }
986
1036
 
987
1037
  // src/Field.tsx
988
- import { createContext as createContext2, useContext as useContext2, useId } from "react";
989
- import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
990
- var FieldControlIdContext = createContext2(void 0);
991
- function useFieldControlId(ownId) {
992
- const fromField = useContext2(FieldControlIdContext);
993
- return fromField ?? ownId;
994
- }
995
- function Field({ label, required = false, htmlFor, children }) {
996
- const generated = useId();
997
- const id = htmlFor ?? generated;
998
- return /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-2", children: [
999
- /* @__PURE__ */ jsxs11(
1000
- "label",
1001
- {
1002
- htmlFor: id,
1003
- className: `text-input-label text-text-primary${required ? " flex items-center" : ""}`,
1004
- children: [
1005
- label,
1006
- required && /* @__PURE__ */ jsx19("span", { className: "text-error-default ml-0.5", children: "*" })
1007
- ]
1008
- }
1009
- ),
1010
- /* @__PURE__ */ jsx19(FieldControlIdContext.Provider, { value: id, children })
1038
+ import { cloneElement, isValidElement } from "react";
1039
+ import { Field as BaseField } from "@base-ui/react/field";
1040
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
1041
+ function Field({ label, required = false, helper, error, children }) {
1042
+ const control = required && isValidElement(children) ? cloneElement(children, {
1043
+ "aria-required": children.props["aria-required"] ?? true
1044
+ }) : children;
1045
+ return /* @__PURE__ */ jsxs12(BaseField.Root, { invalid: !!error, className: "flex flex-col gap-2", children: [
1046
+ /* @__PURE__ */ jsxs12(BaseField.Label, { className: cn("text-input-label text-text-primary", required && "flex items-center"), children: [
1047
+ label,
1048
+ required && /* @__PURE__ */ jsx19("span", { "aria-hidden": "true", className: "text-error-default ml-0.5", children: "*" })
1049
+ ] }),
1050
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-1.5", children: [
1051
+ control,
1052
+ error != null ? /* @__PURE__ */ jsx19(BaseField.Error, { match: true, className: "text-caption text-error-default", children: error }) : helper != null ? /* @__PURE__ */ jsx19(BaseField.Description, { className: "text-caption text-text-muted", children: helper }) : null
1053
+ ] })
1011
1054
  ] });
1012
1055
  }
1013
1056
 
1014
1057
  // src/Select.tsx
1015
1058
  import { IconCheck as IconCheck2, IconChevronDown as IconChevronDown2 } from "@tabler/icons-react";
1016
- import { useCallback as useCallback3, useEffect as useEffect3, useId as useId2, useLayoutEffect as useLayoutEffect4, useMemo as useMemo2, useRef as useRef5, useState as useState6 } from "react";
1017
- import { createPortal as createPortal4 } from "react-dom";
1018
- import { Fragment as Fragment2, jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
1019
- function Select({ value, onChange, options, size = "default", ariaLabel, placeholder = "Select\u2026", disabled, className }) {
1020
- const id = useId2();
1021
- const triggerRef = useRef5(null);
1022
- const menuRef = useRef5(null);
1023
- const [rect, setRect] = useState6(null);
1024
- const [placement, setPlacement] = useState6(null);
1025
- const open = rect !== null;
1026
- const selectedIndex = useMemo2(() => Math.max(0, options.findIndex((o) => o.value === value)), [options, value]);
1027
- const [activeIndex, setActiveIndex] = useState6(selectedIndex);
1028
- const close = useCallback3((refocus) => {
1029
- setRect(null);
1030
- if (refocus) triggerRef.current?.focus();
1031
- }, []);
1032
- const openMenu = useCallback3(() => {
1033
- setActiveIndex(selectedIndex);
1034
- setRect(triggerRef.current?.getBoundingClientRect() ?? null);
1035
- }, [selectedIndex]);
1036
- useLayoutEffect4(() => {
1037
- if (!rect || !menuRef.current) {
1038
- setPlacement(null);
1039
- return;
1040
- }
1041
- setPlacement(
1042
- fitMenu({
1043
- anchor: { left: rect.left, top: rect.top, bottom: rect.bottom },
1044
- menu: { width: menuRef.current.offsetWidth, contentHeight: menuRef.current.scrollHeight },
1045
- viewport: { width: window.innerWidth, height: window.innerHeight },
1046
- // The option list keeps its classic height (the old max-h-72); a menu
1047
- // panel passes no cap and stands as tall as the room it opens into.
1048
- cap: 288
1049
- })
1050
- );
1051
- }, [rect, options.length]);
1052
- useEffect3(() => {
1053
- if (!open) return;
1054
- document.getElementById(`${id}-${activeIndex}`)?.scrollIntoView?.({ block: "nearest" });
1055
- }, [open, activeIndex, id]);
1056
- useEffect3(() => {
1057
- if (!open) return;
1058
- const onDown = (e) => {
1059
- if (menuRef.current?.contains(e.target)) return;
1060
- if (triggerRef.current?.contains(e.target)) return;
1061
- setRect(null);
1062
- };
1063
- const onResize = () => setRect(null);
1064
- const onScroll = (e) => {
1065
- if (e.target instanceof Node && menuRef.current?.contains(e.target)) return;
1066
- setRect(null);
1067
- };
1068
- document.addEventListener("mousedown", onDown);
1069
- window.addEventListener("resize", onResize);
1070
- window.addEventListener("scroll", onScroll, true);
1071
- return () => {
1072
- document.removeEventListener("mousedown", onDown);
1073
- window.removeEventListener("resize", onResize);
1074
- window.removeEventListener("scroll", onScroll, true);
1075
- };
1076
- }, [open]);
1077
- const pick = (index) => {
1078
- const option = options[index];
1079
- if (!option) return;
1080
- onChange(option.value);
1081
- close(true);
1082
- };
1083
- const onKeyDown = (e) => {
1084
- if (!open) {
1085
- if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
1086
- e.preventDefault();
1087
- openMenu();
1088
- }
1089
- return;
1090
- }
1091
- switch (e.key) {
1092
- case "Escape":
1093
- e.preventDefault();
1094
- close(true);
1095
- break;
1096
- case "ArrowDown":
1097
- e.preventDefault();
1098
- setActiveIndex((i) => Math.min(options.length - 1, i + 1));
1099
- break;
1100
- case "ArrowUp":
1101
- e.preventDefault();
1102
- setActiveIndex((i) => Math.max(0, i - 1));
1103
- break;
1104
- case "Home":
1105
- e.preventDefault();
1106
- setActiveIndex(0);
1107
- break;
1108
- case "End":
1109
- e.preventDefault();
1110
- setActiveIndex(options.length - 1);
1111
- break;
1112
- case "Enter":
1113
- case " ":
1114
- e.preventDefault();
1115
- pick(activeIndex);
1116
- break;
1117
- case "Tab":
1118
- close(false);
1119
- break;
1120
- }
1121
- };
1059
+ import { Select as BaseSelect } from "@base-ui/react/select";
1060
+ import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
1061
+ var GAP4 = 4;
1062
+ var VIEWPORT_PAD3 = 8;
1063
+ function Select({ value, onChange, options, size = "default", ariaLabel, placeholder = "Select\u2026", disabled, className, ...aria }) {
1122
1064
  const selected = options.find((o) => o.value === value);
1123
- return /* @__PURE__ */ jsxs12(Fragment2, { children: [
1124
- /* @__PURE__ */ jsxs12(
1125
- "button",
1126
- {
1127
- ref: triggerRef,
1128
- type: "button",
1129
- disabled,
1130
- "aria-haspopup": "listbox",
1131
- "aria-expanded": open,
1132
- "aria-label": ariaLabel,
1133
- onClick: () => open ? close(false) : openMenu(),
1134
- onKeyDown,
1135
- className: cn(
1136
- /*
1137
- * `min-w-0 max-w-full`: a trigger must never outgrow its container
1138
- * (Katerina, 2026-09-01 a long label stretched the files panel's
1139
- * Lead row past its card). In a flex row the default min-width:auto
1140
- * forbids shrinking below the label's width, which is what kept
1141
- * `truncate` from ever engaging; in a block container max-w-full is
1142
- * the cap. Full-width callers are unaffected.
1143
- */
1144
- "flex w-full min-w-0 max-w-full items-center justify-between gap-2 rounded-lg border bg-bg-inset text-left",
1145
- "border-border-default text-text-primary outline-none transition-colors",
1146
- // The focused border survives a hover: hover alone strengthens the
1147
- // hairline, but hover while focused must not grey the focus colour —
1148
- // the stacked variant outranks plain hover by specificity.
1149
- "hover:border-border-strong focus-visible:hover:border-border-focus aria-expanded:hover:border-border-focus disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled",
1150
- "focus-visible:border-border-focus aria-expanded:border-border-focus",
1151
- "signal:transition-shadow signal:focus-visible:shadow-focus-ring",
1152
- size === "default" && "px-3 py-2 text-[14px] leading-[1.4] font-normal",
1153
- size === "small" && "h-6 px-2 text-[12px] leading-[1.4] font-normal",
1154
- className
1065
+ return /* @__PURE__ */ jsxs13(
1066
+ BaseSelect.Root,
1067
+ {
1068
+ value,
1069
+ onValueChange: (next) => onChange(next),
1070
+ disabled,
1071
+ modal: false,
1072
+ children: [
1073
+ /* @__PURE__ */ jsxs13(
1074
+ BaseSelect.Trigger,
1075
+ {
1076
+ "aria-label": ariaLabel,
1077
+ "aria-required": aria["aria-required"],
1078
+ className: cn(
1079
+ /*
1080
+ * `min-w-0 max-w-full`: a trigger must never outgrow its container
1081
+ * (Katerina, 2026-09-01 a long label stretched the files panel's
1082
+ * Lead row past its card). In a flex row the default min-width:auto
1083
+ * forbids shrinking below the label's width, which is what kept
1084
+ * `truncate` from ever engaging; in a block container max-w-full is
1085
+ * the cap. Full-width callers are unaffected.
1086
+ */
1087
+ "flex w-full min-w-0 max-w-full items-center justify-between gap-2 rounded-lg border bg-bg-inset text-left",
1088
+ "border-border-default text-text-primary outline-none transition-colors",
1089
+ // The focused border survives a hover: hover alone strengthens the
1090
+ // hairline, but hover while focused must not grey the focus colour —
1091
+ // the stacked variant outranks plain hover by specificity.
1092
+ "hover:border-border-strong focus-visible:hover:border-border-focus aria-expanded:hover:border-border-focus disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled",
1093
+ "focus-visible:border-border-focus aria-expanded:border-border-focus",
1094
+ "signal:transition-shadow signal:focus-visible:shadow-focus-ring",
1095
+ size === "default" && "px-3 py-2 text-[14px] leading-[1.4] font-normal",
1096
+ size === "small" && "h-6 px-2 text-[12px] leading-[1.4] font-normal",
1097
+ className
1098
+ ),
1099
+ children: [
1100
+ /* @__PURE__ */ jsxs13("span", { className: cn("flex min-w-0 items-center gap-2", !selected && "text-text-muted"), children: [
1101
+ selected?.leading && /* @__PURE__ */ jsx20("span", { className: "flex shrink-0 items-center", children: selected.leading }),
1102
+ /* @__PURE__ */ jsx20(BaseSelect.Value, { className: "truncate", children: () => selected?.label ?? placeholder })
1103
+ ] }),
1104
+ /* @__PURE__ */ jsx20(
1105
+ BaseSelect.Icon,
1106
+ {
1107
+ render: /* @__PURE__ */ jsx20(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1108
+ }
1109
+ )
1110
+ ]
1111
+ }
1155
1112
  ),
1156
- children: [
1157
- /* @__PURE__ */ jsxs12("span", { className: cn("flex min-w-0 items-center gap-2", !selected && "text-text-muted"), children: [
1158
- selected?.leading && /* @__PURE__ */ jsx20("span", { className: "flex shrink-0 items-center", children: selected.leading }),
1159
- /* @__PURE__ */ jsx20("span", { className: "truncate", children: selected?.label ?? placeholder })
1160
- ] }),
1161
- /* @__PURE__ */ jsx20(IconChevronDown2, { size: size === "small" ? 14 : 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1162
- ]
1163
- }
1164
- ),
1165
- open && rect && createPortal4(
1166
- /* @__PURE__ */ jsx20(
1167
- "div",
1168
- {
1169
- ref: menuRef,
1170
- role: "listbox",
1171
- "aria-activedescendant": `${id}-${activeIndex}`,
1172
- tabIndex: -1,
1173
- style: placement ? { ...placement, minWidth: rect.width } : (
1174
- // Provisional frame: measured by the layout effect above,
1175
- // replaced before paint. Hidden so a cut-off position is
1176
- // never visible, not even for a frame.
1177
- { top: rect.bottom + 4, left: rect.left, minWidth: rect.width, visibility: "hidden" }
1178
- ),
1179
- className: "fixed z-50 overflow-y-auto rounded-lg border border-border-default bg-bg-elevated p-1 shadow-lg",
1180
- children: options.map((option, index) => /* @__PURE__ */ jsxs12(
1181
- "div",
1182
- {
1183
- id: `${id}-${index}`,
1184
- role: "option",
1185
- "aria-selected": option.value === value,
1186
- onMouseEnter: () => setActiveIndex(index),
1187
- onMouseDown: (e) => {
1188
- e.preventDefault();
1189
- pick(index);
1190
- },
1191
- className: cn(
1192
- "flex h-9 cursor-pointer items-center justify-between gap-2 rounded-lg px-3 transition-colors",
1193
- "text-[14px] font-normal leading-[1.4] text-text-primary",
1194
- index === activeIndex && "bg-bg-hover",
1195
- option.value === value && "font-medium"
1196
- ),
1197
- children: [
1198
- /* @__PURE__ */ jsxs12("span", { className: "flex min-w-0 items-center gap-2", children: [
1199
- option.leading && /* @__PURE__ */ jsx20("span", { className: "flex shrink-0 items-center", children: option.leading }),
1200
- /* @__PURE__ */ jsx20("span", { className: "truncate", children: option.label })
1201
- ] }),
1202
- option.value === value && /* @__PURE__ */ jsx20(IconCheck2, { size: 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1203
- ]
1204
- },
1205
- option.value
1206
- ))
1207
- }
1208
- ),
1209
- document.body
1210
- )
1211
- ] });
1113
+ /* @__PURE__ */ jsx20(BaseSelect.Portal, { children: /* @__PURE__ */ jsx20(
1114
+ BaseSelect.Positioner,
1115
+ {
1116
+ side: "bottom",
1117
+ align: "start",
1118
+ sideOffset: GAP4,
1119
+ collisionPadding: VIEWPORT_PAD3,
1120
+ alignItemWithTrigger: false,
1121
+ className: "z-50 data-[anchor-hidden]:hidden",
1122
+ children: /* @__PURE__ */ jsx20(
1123
+ BaseSelect.Popup,
1124
+ {
1125
+ className: "max-h-[min(288px,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto rounded-lg border border-border-default bg-bg-elevated p-1 shadow-lg",
1126
+ children: options.map((option) => /* @__PURE__ */ jsxs13(
1127
+ BaseSelect.Item,
1128
+ {
1129
+ value: option.value,
1130
+ className: "flex h-9 cursor-pointer items-center justify-between gap-2 rounded-lg px-3 text-[14px] font-normal leading-[1.4] text-text-primary transition-colors data-[highlighted]:bg-bg-hover data-[selected]:font-medium",
1131
+ children: [
1132
+ /* @__PURE__ */ jsxs13("span", { className: "flex min-w-0 items-center gap-2", children: [
1133
+ option.leading && /* @__PURE__ */ jsx20("span", { className: "flex shrink-0 items-center", children: option.leading }),
1134
+ /* @__PURE__ */ jsx20(BaseSelect.ItemText, { className: "truncate", children: option.label })
1135
+ ] }),
1136
+ /* @__PURE__ */ jsx20(
1137
+ BaseSelect.ItemIndicator,
1138
+ {
1139
+ render: /* @__PURE__ */ jsx20(IconCheck2, { size: 16, stroke: 1.5, className: "shrink-0 text-text-secondary" })
1140
+ }
1141
+ )
1142
+ ]
1143
+ },
1144
+ option.value
1145
+ ))
1146
+ }
1147
+ )
1148
+ }
1149
+ ) })
1150
+ ]
1151
+ }
1152
+ );
1212
1153
  }
1213
1154
 
1214
1155
  // src/TextInput.tsx
1215
1156
  import { forwardRef } from "react";
1157
+ import { Input } from "@base-ui/react/input";
1216
1158
  import { jsx as jsx21 } from "react/jsx-runtime";
1217
- var TextInput = forwardRef(function TextInput2({ className, type = "text", id, ...props }, ref) {
1218
- const controlId = useFieldControlId(id);
1159
+ var TextInput = forwardRef(function TextInput2({ className, type = "text", ...props }, ref) {
1219
1160
  return /* @__PURE__ */ jsx21(
1220
- "input",
1161
+ Input,
1221
1162
  {
1222
1163
  ref,
1223
- id: controlId,
1224
1164
  type,
1225
1165
  className: cn(
1226
1166
  "bg-bg-inset border border-border-default focus:border-border-focus rounded-lg px-3 py-2",
@@ -1237,14 +1177,14 @@ var TextInput = forwardRef(function TextInput2({ className, type = "text", id, .
1237
1177
 
1238
1178
  // src/Textarea.tsx
1239
1179
  import { forwardRef as forwardRef2 } from "react";
1180
+ import { Field as BaseField2 } from "@base-ui/react/field";
1240
1181
  import { jsx as jsx22 } from "react/jsx-runtime";
1241
- var Textarea = forwardRef2(function Textarea2({ className, id, ...props }, ref) {
1242
- const controlId = useFieldControlId(id);
1182
+ var Textarea = forwardRef2(function Textarea2({ className, ...props }, ref) {
1243
1183
  return /* @__PURE__ */ jsx22(
1244
- "textarea",
1184
+ BaseField2.Control,
1245
1185
  {
1246
1186
  ref,
1247
- id: controlId,
1187
+ render: /* @__PURE__ */ jsx22("textarea", {}),
1248
1188
  className: cn(
1249
1189
  "bg-bg-inset border border-border-default focus:border-border-focus rounded-lg px-3 py-2",
1250
1190
  "text-[14px] leading-[1.4] font-normal text-text-primary placeholder:text-text-muted",
@@ -1259,51 +1199,58 @@ var Textarea = forwardRef2(function Textarea2({ className, id, ...props }, ref)
1259
1199
  });
1260
1200
 
1261
1201
  // src/ConfirmDialog.tsx
1262
- import { useState as useState7 } from "react";
1202
+ import { useState as useState3 } from "react";
1263
1203
 
1264
1204
  // src/DialogShell.tsx
1265
- import { useEffect as useEffect4 } from "react";
1266
- import { createPortal as createPortal5 } from "react-dom";
1267
- import { IconX as IconX2 } from "@tabler/icons-react";
1268
- import { Fragment as Fragment3, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
1269
- function DialogShell({ title, onClose, headerContent, footer, children, bodyClassName, width = 502 }) {
1270
- useEffect4(() => {
1271
- const onKey = (event) => {
1272
- if (event.key === "Escape") onClose();
1273
- };
1274
- document.addEventListener("keydown", onKey);
1275
- return () => document.removeEventListener("keydown", onKey);
1276
- }, [onClose]);
1277
- return createPortal5(
1278
- /* @__PURE__ */ jsxs13(Fragment3, { children: [
1279
- /* @__PURE__ */ jsx23("div", { className: "fixed inset-0 z-40 bg-scrim", onClick: onClose }),
1280
- /* @__PURE__ */ jsx23("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs13(
1281
- "div",
1282
- {
1283
- role: "dialog",
1284
- "aria-modal": "true",
1285
- "aria-label": title,
1286
- className: "bg-bg-elevated border border-border-subtle rounded-lg shadow-lg pointer-events-auto flex flex-col overflow-hidden",
1287
- style: { width },
1288
- children: [
1289
- /* @__PURE__ */ jsxs13("div", { className: "h-12 flex items-center justify-between pl-5 pr-4 border-b border-border-subtle shrink-0", children: [
1290
- headerContent ?? /* @__PURE__ */ jsx23("span", { className: "text-h4 text-text-primary", children: title }),
1291
- /* @__PURE__ */ jsx23(IconButton, { tooltip: "Close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx23(IconX2, { size: 16, stroke: 1.5 }) })
1292
- ] }),
1293
- /* @__PURE__ */ jsx23("div", { className: cn("pl-5 pr-4 py-4", footer != null && "border-b border-border-subtle", bodyClassName), children }),
1294
- footer != null && /* @__PURE__ */ jsx23("div", { className: "h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0", children: footer })
1295
- ]
1296
- }
1297
- ) })
1298
- ] }),
1299
- document.body
1205
+ import { useRef as useRef3 } from "react";
1206
+ import { Dialog } from "@base-ui/react/dialog";
1207
+ import { AlertDialog } from "@base-ui/react/alert-dialog";
1208
+ import { IconX as IconX3 } from "@tabler/icons-react";
1209
+ import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1210
+ function DialogShell({ title, onClose, headerContent, footer, children, bodyClassName, width = 502, alert = false }) {
1211
+ const Parts = alert ? AlertDialog : Dialog;
1212
+ const popupRef = useRef3(null);
1213
+ return /* @__PURE__ */ jsx23(
1214
+ Parts.Root,
1215
+ {
1216
+ open: true,
1217
+ onOpenChange: (open) => {
1218
+ if (!open) onClose();
1219
+ },
1220
+ children: /* @__PURE__ */ jsxs14(Parts.Portal, { children: [
1221
+ /* @__PURE__ */ jsx23(Parts.Backdrop, { className: "fixed inset-0 z-40 bg-scrim" }),
1222
+ /* @__PURE__ */ jsx23("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs14(
1223
+ Parts.Popup,
1224
+ {
1225
+ ref: popupRef,
1226
+ initialFocus: popupRef,
1227
+ "aria-label": headerContent != null ? title : void 0,
1228
+ className: "bg-bg-elevated border border-border-subtle rounded-lg shadow-lg pointer-events-auto flex flex-col overflow-hidden outline-none",
1229
+ style: { width },
1230
+ children: [
1231
+ /* @__PURE__ */ jsxs14("div", { className: "h-12 flex items-center justify-between pl-5 pr-4 border-b border-border-subtle shrink-0", children: [
1232
+ headerContent ?? /* @__PURE__ */ jsx23(Parts.Title, { className: "text-h4 text-text-primary", render: /* @__PURE__ */ jsx23("span", {}), children: title }),
1233
+ /* @__PURE__ */ jsx23(
1234
+ Parts.Close,
1235
+ {
1236
+ render: /* @__PURE__ */ jsx23(IconButton, { tooltip: "Close", "aria-label": "Close", children: /* @__PURE__ */ jsx23(IconX3, { size: 16, stroke: 1.5 }) })
1237
+ }
1238
+ )
1239
+ ] }),
1240
+ /* @__PURE__ */ jsx23("div", { className: cn("pl-5 pr-4 py-4", footer != null && "border-b border-border-subtle", bodyClassName), children }),
1241
+ footer != null && /* @__PURE__ */ jsx23("div", { className: "h-12 flex items-center justify-end gap-2 pl-5 pr-4 shrink-0", children: footer })
1242
+ ]
1243
+ }
1244
+ ) })
1245
+ ] })
1246
+ }
1300
1247
  );
1301
1248
  }
1302
1249
 
1303
1250
  // src/ConfirmDialog.tsx
1304
- import { Fragment as Fragment4, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
1251
+ import { Fragment as Fragment3, jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1305
1252
  function ConfirmDialog({ title, children, confirmLabel, destructive = false, onConfirm, onClose }) {
1306
- const [busy, setBusy] = useState7(false);
1253
+ const [busy, setBusy] = useState3(false);
1307
1254
  const confirm = async () => {
1308
1255
  setBusy(true);
1309
1256
  try {
@@ -1316,10 +1263,11 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
1316
1263
  return /* @__PURE__ */ jsx24(
1317
1264
  DialogShell,
1318
1265
  {
1266
+ alert: true,
1319
1267
  title,
1320
1268
  onClose,
1321
1269
  bodyClassName: "flex flex-col gap-3 text-body-2 text-text-primary",
1322
- footer: /* @__PURE__ */ jsxs14(Fragment4, { children: [
1270
+ footer: /* @__PURE__ */ jsxs15(Fragment3, { children: [
1323
1271
  /* @__PURE__ */ jsx24(Button, { variant: "muted", onClick: onClose, disabled: busy, children: "Cancel" }),
1324
1272
  /* @__PURE__ */ jsx24(Button, { variant: destructive ? "destructive" : "primary", onClick: () => void confirm(), disabled: busy, children: confirmLabel })
1325
1273
  ] }),
@@ -1329,14 +1277,16 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
1329
1277
  }
1330
1278
 
1331
1279
  // src/EditableText.tsx
1332
- import { useEffect as useEffect5, useRef as useRef6, useState as useState8 } from "react";
1280
+ import { useEffect as useEffect2, useRef as useRef4, useState as useState4 } from "react";
1281
+ import { Field as BaseField3 } from "@base-ui/react/field";
1282
+ import { Input as Input2 } from "@base-ui/react/input";
1333
1283
  import { jsx as jsx25 } from "react/jsx-runtime";
1334
1284
  function EditableText({ value, display, displayNode, placeholder, onCommit, multiline = false, className, label, readOnly = false }) {
1335
- const [editing, setEditing] = useState8(false);
1336
- const [draft, setDraft] = useState8(value);
1337
- const [busy, setBusy] = useState8(false);
1338
- const fieldRef = useRef6(null);
1339
- useEffect5(() => {
1285
+ const [editing, setEditing] = useState4(false);
1286
+ const [draft, setDraft] = useState4(value);
1287
+ const [busy, setBusy] = useState4(false);
1288
+ const fieldRef = useRef4(null);
1289
+ useEffect2(() => {
1340
1290
  if (editing) {
1341
1291
  fieldRef.current?.focus();
1342
1292
  fieldRef.current?.select();
@@ -1381,23 +1331,30 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1381
1331
  className
1382
1332
  );
1383
1333
  if (readOnly) {
1384
- return /* @__PURE__ */ jsx25(
1385
- "div",
1386
- {
1387
- "aria-label": label,
1388
- className: cn("w-full px-2 py-1", multiline && !displayNode && "whitespace-pre-wrap", display ?? value ? "text-text-primary" : "text-text-muted", className),
1389
- children: display ?? value ? displayNode ?? (display ?? value) : placeholder
1390
- }
1334
+ return (
1335
+ /*
1336
+ No `aria-label` here. It sat on a bare `<div>`, which has no role, and
1337
+ ARIA does not let an author name a generic element — so the name was
1338
+ written and never read. Read-only, this is a line of text: whatever
1339
+ names the region around it (a `Field`, a `Property`) names this too.
1340
+ */
1341
+ /* @__PURE__ */ jsx25(
1342
+ "div",
1343
+ {
1344
+ className: cn("w-full px-2 py-1", multiline && !displayNode && "whitespace-pre-wrap", display ?? value ? "text-text-primary" : "text-text-muted", className),
1345
+ children: display ?? value ? displayNode ?? (display ?? value) : placeholder
1346
+ }
1347
+ )
1391
1348
  );
1392
1349
  }
1393
1350
  if (editing) {
1394
1351
  return multiline ? /* @__PURE__ */ jsx25(
1395
- "textarea",
1352
+ BaseField3.Control,
1396
1353
  {
1397
1354
  ref: fieldRef,
1355
+ render: /* @__PURE__ */ jsx25("textarea", { rows: 4 }),
1398
1356
  "aria-label": label,
1399
1357
  value: draft,
1400
- rows: 4,
1401
1358
  disabled: busy,
1402
1359
  onChange: (event) => setDraft(event.target.value),
1403
1360
  onKeyDown,
@@ -1405,7 +1362,7 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1405
1362
  className: fieldClass
1406
1363
  }
1407
1364
  ) : /* @__PURE__ */ jsx25(
1408
- "input",
1365
+ Input2,
1409
1366
  {
1410
1367
  ref: fieldRef,
1411
1368
  "aria-label": label,
@@ -1423,7 +1380,6 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1423
1380
  {
1424
1381
  type: "button",
1425
1382
  onClick: open,
1426
- title: "Click to edit",
1427
1383
  "aria-label": `Edit ${label.toLowerCase()}`,
1428
1384
  className: cn(
1429
1385
  "w-full rounded-md border border-transparent px-2 py-1 text-left transition-colors hover:border-border-default",
@@ -1438,22 +1394,22 @@ function EditableText({ value, display, displayNode, placeholder, onCommit, mult
1438
1394
 
1439
1395
  // src/EmptyState.tsx
1440
1396
  import { IconMessage2 } from "@tabler/icons-react";
1441
- import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
1397
+ import { jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
1442
1398
  function EmptyState({ icon, message, className }) {
1443
- return /* @__PURE__ */ jsxs15("div", { className: cn("flex flex-col gap-2 items-center", className), children: [
1399
+ return /* @__PURE__ */ jsxs16("div", { className: cn("flex flex-col gap-2 items-center", className), children: [
1444
1400
  /* @__PURE__ */ jsx26("span", { className: "text-text-secondary", children: icon ?? /* @__PURE__ */ jsx26(IconMessage2, { size: 16, stroke: 1.5 }) }),
1445
1401
  /* @__PURE__ */ jsx26("p", { className: "text-body-2 text-text-secondary text-center", children: message })
1446
1402
  ] });
1447
1403
  }
1448
1404
 
1449
1405
  // src/Skeleton.tsx
1450
- import { jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
1406
+ import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
1451
1407
  function SkeletonBar({ className, style }) {
1452
1408
  return /* @__PURE__ */ jsx27("div", { className: cn("bg-bg-inset rounded-sm animate-pulse", className), style });
1453
1409
  }
1454
1410
  var ROW_BAR_WIDTHS = [150, 100, 170, 120, 90, 140, 110, 160];
1455
1411
  function SkeletonRow({ barWidth = 130 }) {
1456
- return /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 px-2 h-[32px] rounded-lg", children: [
1412
+ return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2 px-2 h-[32px] rounded-lg", children: [
1457
1413
  /* @__PURE__ */ jsx27(SkeletonBar, { className: "w-4 h-4 shrink-0" }),
1458
1414
  /* @__PURE__ */ jsx27(SkeletonBar, { className: "h-3.5", style: { width: barWidth } })
1459
1415
  ] });
@@ -1463,20 +1419,20 @@ function SkeletonList({ rows = 8, className }) {
1463
1419
  }
1464
1420
 
1465
1421
  // src/Breadcrumb.tsx
1466
- import { Fragment as Fragment5, useCallback as useCallback4, useLayoutEffect as useLayoutEffect5, useRef as useRef7, useState as useState9 } from "react";
1467
- import { jsx as jsx28, jsxs as jsxs17 } from "react/jsx-runtime";
1422
+ import { Fragment as Fragment4, useCallback as useCallback2, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState5 } from "react";
1423
+ import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1468
1424
  function Breadcrumb({ items, className }) {
1469
- const navRef = useRef7(null);
1470
- const labelRefs = useRef7([]);
1471
- const [truncated, setTruncated] = useState9(/* @__PURE__ */ new Set());
1472
- const measure = useCallback4(() => {
1425
+ const navRef = useRef5(null);
1426
+ const labelRefs = useRef5([]);
1427
+ const [truncated, setTruncated] = useState5(/* @__PURE__ */ new Set());
1428
+ const measure = useCallback2(() => {
1473
1429
  const next = /* @__PURE__ */ new Set();
1474
1430
  labelRefs.current.forEach((el, index) => {
1475
1431
  if (el && el.scrollWidth > el.clientWidth) next.add(index);
1476
1432
  });
1477
1433
  setTruncated((prev) => prev.size === next.size && [...next].every((i) => prev.has(i)) ? prev : next);
1478
1434
  }, []);
1479
- useLayoutEffect5(() => {
1435
+ useLayoutEffect2(() => {
1480
1436
  measure();
1481
1437
  const nav = navRef.current;
1482
1438
  if (!nav || typeof ResizeObserver === "undefined") return;
@@ -1497,8 +1453,8 @@ function Breadcrumb({ items, className }) {
1497
1453
  const setLabelRef = (el) => {
1498
1454
  labelRefs.current[index] = el;
1499
1455
  };
1500
- const crumb = item.href ? /* @__PURE__ */ jsx28("a", { ref: setLabelRef, href: item.href, className: cn(text, "hover:text-text-primary"), children: item.label }) : /* @__PURE__ */ jsx28("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
1501
- return /* @__PURE__ */ jsxs17(Fragment5, { children: [
1456
+ const crumb = item.href ? /* @__PURE__ */ jsx28("a", { ref: setLabelRef, href: item.href, onClick: item.onClick, className: cn(text, "hover:text-text-primary"), children: item.label }) : /* @__PURE__ */ jsx28("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
1457
+ return /* @__PURE__ */ jsxs18(Fragment4, { children: [
1502
1458
  index > 0 && /* @__PURE__ */ jsx28("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: "/" }),
1503
1459
  truncated.has(index) ? (
1504
1460
  // `min-w-0 shrink` undoes the wrapper's own shrink-0 — the crumb
@@ -1511,9 +1467,9 @@ function Breadcrumb({ items, className }) {
1511
1467
  }
1512
1468
 
1513
1469
  // src/NavItem.tsx
1514
- import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
1470
+ import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
1515
1471
  function NavItem({ label, href, count, countLabel, active = false, icon, className, ...props }) {
1516
- return /* @__PURE__ */ jsxs18(
1472
+ return /* @__PURE__ */ jsxs19(
1517
1473
  "a",
1518
1474
  {
1519
1475
  href,
@@ -1536,16 +1492,171 @@ function NavItem({ label, href, count, countLabel, active = false, icon, classNa
1536
1492
  );
1537
1493
  }
1538
1494
 
1495
+ // src/Popover.tsx
1496
+ import { useMemo as useMemo2 } from "react";
1497
+ import { Popover as BasePopover } from "@base-ui/react/popover";
1498
+ import { jsx as jsx30, jsxs as jsxs20 } from "react/jsx-runtime";
1499
+ var GAP5 = 4;
1500
+ var VIEWPORT_PAD4 = 8;
1501
+ function Popover({ trigger, anchor, align = "left", side = "bottom", open, onOpenChange, finalFocus, actionsRef, ariaLabel, children, className }) {
1502
+ const anchorTarget = useMemo2(() => {
1503
+ if (!anchor) return void 0;
1504
+ if (anchor instanceof Element) return anchor;
1505
+ return { getBoundingClientRect: () => anchor };
1506
+ }, [anchor]);
1507
+ return /* @__PURE__ */ jsxs20(
1508
+ BasePopover.Root,
1509
+ {
1510
+ open,
1511
+ onOpenChange: onOpenChange ? (next) => onOpenChange(next) : void 0,
1512
+ actionsRef,
1513
+ modal: false,
1514
+ children: [
1515
+ trigger && /* @__PURE__ */ jsx30(BasePopover.Trigger, { render: trigger, disabled: triggerDisabled(trigger) }),
1516
+ /* @__PURE__ */ jsx30(BasePopover.Portal, { children: /* @__PURE__ */ jsx30(
1517
+ BasePopover.Positioner,
1518
+ {
1519
+ anchor: anchorTarget,
1520
+ side,
1521
+ align: align === "right" ? "end" : "start",
1522
+ sideOffset: GAP5,
1523
+ collisionPadding: VIEWPORT_PAD4,
1524
+ className: "z-50 data-[anchor-hidden]:hidden",
1525
+ children: /* @__PURE__ */ jsx30(
1526
+ BasePopover.Popup,
1527
+ {
1528
+ "aria-label": ariaLabel,
1529
+ initialFocus: trigger ? void 0 : false,
1530
+ finalFocus,
1531
+ className: cn("min-w-[180px] max-h-[var(--available-height)] overflow-y-auto outline-none", className),
1532
+ render: /* @__PURE__ */ jsx30(MenuPanel, {}),
1533
+ children
1534
+ }
1535
+ )
1536
+ }
1537
+ ) })
1538
+ ]
1539
+ }
1540
+ );
1541
+ }
1542
+
1543
+ // src/Toolbar.tsx
1544
+ import { Toolbar as BaseToolbar } from "@base-ui/react/toolbar";
1545
+ import { jsx as jsx31 } from "react/jsx-runtime";
1546
+ function Toolbar({ "aria-label": ariaLabel, orientation = "horizontal", loopFocus = true, surface = true, children, className }) {
1547
+ const strip = /* @__PURE__ */ jsx31(
1548
+ BaseToolbar.Root,
1549
+ {
1550
+ "aria-label": ariaLabel,
1551
+ orientation,
1552
+ loopFocus,
1553
+ className: cn("flex items-center gap-1", orientation === "vertical" && "flex-col", className),
1554
+ children
1555
+ }
1556
+ );
1557
+ if (!surface) return strip;
1558
+ return /* @__PURE__ */ jsx31(MenuPanel, { className: "w-fit p-1", children: strip });
1559
+ }
1560
+ function ToolbarButton({ ref, disabled, disabledReason, ...props }) {
1561
+ return /* @__PURE__ */ jsx31(
1562
+ BaseToolbar.Button,
1563
+ {
1564
+ ref,
1565
+ disabled: disabled || !!disabledReason,
1566
+ focusableWhenDisabled: true,
1567
+ render: /* @__PURE__ */ jsx31(IconButton, { disabled, disabledReason, ...props })
1568
+ }
1569
+ );
1570
+ }
1571
+ function ToolbarInput(props) {
1572
+ return /* @__PURE__ */ jsx31(BaseToolbar.Input, { render: /* @__PURE__ */ jsx31(TextInput, {}), ...props });
1573
+ }
1574
+ function ToolbarSeparator({ className }) {
1575
+ return /* @__PURE__ */ jsx31(
1576
+ BaseToolbar.Separator,
1577
+ {
1578
+ orientation: "vertical",
1579
+ className: cn("mx-1 h-4 w-px shrink-0 bg-border-default", className)
1580
+ }
1581
+ );
1582
+ }
1583
+
1584
+ // src/ReactionPicker.tsx
1585
+ import { jsx as jsx32 } from "react/jsx-runtime";
1586
+ function ReactionPicker({ options, onSelect, "aria-label": ariaLabel = "Reactions", surface = true, className }) {
1587
+ return /* @__PURE__ */ jsx32(Toolbar, { "aria-label": ariaLabel, surface, className: cn("gap-0.5", className), children: options.map((option) => (
1588
+ /*
1589
+ A `ToolbarButton`, not a button in a tooltip wrapper. The wrapper
1590
+ would become the toolbar's item and the button inside it would never
1591
+ join the walk — and it is unnecessary since stage 4, because an
1592
+ `IconButton` with a `tooltip` IS the trigger.
1593
+
1594
+ The tooltip names it for a pointer, `aria-label` for everything else,
1595
+ and both say the meaning rather than the glyph.
1596
+ */
1597
+ /* @__PURE__ */ jsx32(
1598
+ ToolbarButton,
1599
+ {
1600
+ "aria-label": option.label,
1601
+ tooltip: option.label,
1602
+ onClick: () => onSelect(option.emoji),
1603
+ className: "size-7 text-[18px] leading-none",
1604
+ children: /* @__PURE__ */ jsx32("span", { "aria-hidden": "true", children: option.emoji })
1605
+ },
1606
+ option.emoji
1607
+ )
1608
+ )) });
1609
+ }
1610
+
1611
+ // src/PreviewCard.tsx
1612
+ import { PreviewCard as BasePreviewCard } from "@base-ui/react/preview-card";
1613
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1614
+ var GAP6 = 12;
1615
+ var VIEWPORT_PAD5 = 8;
1616
+ var OPEN_DELAY2 = 350;
1617
+ var CLOSE_DELAY = 200;
1618
+ function PreviewCard({ content, children, side = "right", delay = OPEN_DELAY2, closeDelay = CLOSE_DELAY, className, wrapperClassName }) {
1619
+ return /* @__PURE__ */ jsxs21(BasePreviewCard.Root, { children: [
1620
+ /* @__PURE__ */ jsx33(
1621
+ BasePreviewCard.Trigger,
1622
+ {
1623
+ delay,
1624
+ closeDelay,
1625
+ render: /* @__PURE__ */ jsx33("span", { className: cn("inline-flex", wrapperClassName) }),
1626
+ children
1627
+ }
1628
+ ),
1629
+ /* @__PURE__ */ jsx33(BasePreviewCard.Portal, { children: /* @__PURE__ */ jsx33(
1630
+ BasePreviewCard.Positioner,
1631
+ {
1632
+ side,
1633
+ align: "start",
1634
+ sideOffset: GAP6,
1635
+ collisionPadding: VIEWPORT_PAD5,
1636
+ className: "z-50 data-[anchor-hidden]:hidden",
1637
+ children: /* @__PURE__ */ jsx33(
1638
+ BasePreviewCard.Popup,
1639
+ {
1640
+ className: cn("w-[360px] max-h-[min(300px,var(--available-height))] gap-3 overflow-y-auto p-3 outline-none [&>*]:shrink-0", className),
1641
+ render: /* @__PURE__ */ jsx33(MenuPanel, {}),
1642
+ children: content
1643
+ }
1644
+ )
1645
+ }
1646
+ ) })
1647
+ ] });
1648
+ }
1649
+
1539
1650
  // src/Rail.tsx
1540
- import { jsx as jsx30 } from "react/jsx-runtime";
1651
+ import { jsx as jsx34 } from "react/jsx-runtime";
1541
1652
  function Rail({ "aria-label": ariaLabel = "Navigation", children, className }) {
1542
- return /* @__PURE__ */ jsx30("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
1653
+ return /* @__PURE__ */ jsx34("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
1543
1654
  }
1544
1655
 
1545
1656
  // src/RailItem.tsx
1546
- import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1657
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1547
1658
  function RailItem({ href, icon, label, active = false, className, ...props }) {
1548
- return /* @__PURE__ */ jsxs19(
1659
+ return /* @__PURE__ */ jsxs22(
1549
1660
  "a",
1550
1661
  {
1551
1662
  href,
@@ -1553,14 +1664,14 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
1553
1664
  className: cn("group flex w-full shrink-0 flex-col items-center gap-0.5 px-2 py-0.5", className),
1554
1665
  ...props,
1555
1666
  children: [
1556
- /* @__PURE__ */ jsx31(
1667
+ /* @__PURE__ */ jsx35(
1557
1668
  "div",
1558
1669
  {
1559
1670
  className: cn(
1560
1671
  "flex items-center justify-center rounded-lg p-2 transition-colors",
1561
1672
  active ? "bg-bg-selected" : "group-hover:bg-bg-hover"
1562
1673
  ),
1563
- children: /* @__PURE__ */ jsx31(
1674
+ children: /* @__PURE__ */ jsx35(
1564
1675
  "span",
1565
1676
  {
1566
1677
  className: cn(
@@ -1572,16 +1683,16 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
1572
1683
  )
1573
1684
  }
1574
1685
  ),
1575
- /* @__PURE__ */ jsx31("span", { className: cn("text-center text-[9px] font-medium leading-[115%]", active ? "text-text-primary" : "text-text-secondary"), children: label })
1686
+ /* @__PURE__ */ jsx35("span", { className: cn("text-center text-[9px] font-medium leading-[115%]", active ? "text-text-primary" : "text-text-secondary"), children: label })
1576
1687
  ]
1577
1688
  }
1578
1689
  );
1579
1690
  }
1580
1691
 
1581
1692
  // src/Sidebar.tsx
1582
- import { jsx as jsx32 } from "react/jsx-runtime";
1693
+ import { jsx as jsx36 } from "react/jsx-runtime";
1583
1694
  function Sidebar({ "aria-label": ariaLabel = "Workspace", children, className }) {
1584
- return /* @__PURE__ */ jsx32(
1695
+ return /* @__PURE__ */ jsx36(
1585
1696
  "nav",
1586
1697
  {
1587
1698
  "aria-label": ariaLabel,
@@ -1595,29 +1706,29 @@ function Sidebar({ "aria-label": ariaLabel = "Workspace", children, className })
1595
1706
  }
1596
1707
 
1597
1708
  // src/Property.tsx
1598
- import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
1709
+ import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
1599
1710
  function Property({ label, layout = "row", children, className }) {
1600
1711
  if (layout === "stacked") {
1601
1712
  return (
1602
1713
  // gap-3 — the one label-above-content distance (Katerina, 2026-09-01):
1603
1714
  // Peek's topic-details sections already sit at 12px, Ship's rail was
1604
1715
  // 6px, and unifying means the bigger, calmer one.
1605
- /* @__PURE__ */ jsxs20("div", { className: cn("flex flex-col gap-3", className), children: [
1606
- /* @__PURE__ */ jsx33("span", { className: "text-menu uppercase tracking-[0.08em] text-text-secondary", children: label }),
1716
+ /* @__PURE__ */ jsxs23("div", { className: cn("flex flex-col gap-3", className), children: [
1717
+ /* @__PURE__ */ jsx37("span", { className: "text-menu uppercase tracking-[0.08em] text-text-secondary", children: label }),
1607
1718
  children
1608
1719
  ] })
1609
1720
  );
1610
1721
  }
1611
- return /* @__PURE__ */ jsxs20("div", { className: cn("flex items-center gap-2", className), children: [
1612
- /* @__PURE__ */ jsx33("span", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
1722
+ return /* @__PURE__ */ jsxs23("div", { className: cn("flex items-center gap-2", className), children: [
1723
+ /* @__PURE__ */ jsx37("span", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
1613
1724
  children
1614
1725
  ] });
1615
1726
  }
1616
1727
 
1617
1728
  // src/Reaction.tsx
1618
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
1729
+ import { jsx as jsx38, jsxs as jsxs24 } from "react/jsx-runtime";
1619
1730
  function Reaction({ emoji, count, pressed = false, className, type, ...props }) {
1620
- return /* @__PURE__ */ jsxs21(
1731
+ return /* @__PURE__ */ jsxs24(
1621
1732
  "button",
1622
1733
  {
1623
1734
  type: type ?? "button",
@@ -1633,8 +1744,8 @@ function Reaction({ emoji, count, pressed = false, className, type, ...props })
1633
1744
  ),
1634
1745
  ...props,
1635
1746
  children: [
1636
- /* @__PURE__ */ jsx34("span", { "aria-hidden": "true", className: "shrink-0 text-[16px] leading-none", children: emoji }),
1637
- /* @__PURE__ */ jsx34("span", { className: "text-chip signal:font-mono signal:text-[10px] signal:font-semibold signal:tabular-nums", children: count })
1747
+ /* @__PURE__ */ jsx38("span", { "aria-hidden": "true", className: "shrink-0 text-[16px] leading-none", children: emoji }),
1748
+ /* @__PURE__ */ jsx38("span", { className: "text-chip signal:font-mono signal:text-[10px] signal:font-semibold signal:tabular-nums", children: count })
1638
1749
  ]
1639
1750
  }
1640
1751
  );
@@ -1642,9 +1753,10 @@ function Reaction({ emoji, count, pressed = false, className, type, ...props })
1642
1753
 
1643
1754
  // src/SearchInput.tsx
1644
1755
  import "react";
1645
- import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
1756
+ import { Input as Input3 } from "@base-ui/react/input";
1757
+ import { jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
1646
1758
  function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...props }) {
1647
- return /* @__PURE__ */ jsxs22(
1759
+ return /* @__PURE__ */ jsxs25(
1648
1760
  "div",
1649
1761
  {
1650
1762
  className: cn(
@@ -1654,27 +1766,27 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
1654
1766
  className
1655
1767
  ),
1656
1768
  children: [
1657
- /* @__PURE__ */ jsx35(
1658
- "input",
1769
+ /* @__PURE__ */ jsx39(
1770
+ Input3,
1659
1771
  {
1660
1772
  className: "flex-1 min-w-0 bg-transparent text-input-value text-text-primary placeholder:text-text-muted outline-none",
1661
1773
  placeholder,
1662
1774
  ...props
1663
1775
  }
1664
1776
  ),
1665
- shortcut && /* @__PURE__ */ jsx35(Kbd, { children: shortcut })
1777
+ shortcut && /* @__PURE__ */ jsx39(Kbd, { children: shortcut })
1666
1778
  ]
1667
1779
  }
1668
1780
  );
1669
1781
  }
1670
1782
 
1671
1783
  // src/SectionHeader.tsx
1672
- import { useState as useState10 } from "react";
1784
+ import { useState as useState6 } from "react";
1673
1785
  import { IconChevronRight as IconChevronRight2 } from "@tabler/icons-react";
1674
- import { jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
1786
+ import { jsx as jsx40, jsxs as jsxs26 } from "react/jsx-runtime";
1675
1787
  function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, actions, showActions = "hover", className }) {
1676
- const [isHovered, setIsHovered] = useState10(false);
1677
- return /* @__PURE__ */ jsxs23(
1788
+ const [isHovered, setIsHovered] = useState6(false);
1789
+ return /* @__PURE__ */ jsxs26(
1678
1790
  "div",
1679
1791
  {
1680
1792
  className: cn(
@@ -1687,8 +1799,8 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1687
1799
  onMouseEnter: () => setIsHovered(true),
1688
1800
  onMouseLeave: () => setIsHovered(false),
1689
1801
  children: [
1690
- /* @__PURE__ */ jsxs23("div", { className: "flex shrink-0 items-center gap-1", children: [
1691
- chevron && /* @__PURE__ */ jsx36(
1802
+ /* @__PURE__ */ jsxs26("div", { className: "flex shrink-0 items-center gap-1", children: [
1803
+ chevron && /* @__PURE__ */ jsx40(
1692
1804
  IconChevronRight2,
1693
1805
  {
1694
1806
  size: 12,
@@ -1696,9 +1808,9 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1696
1808
  className: cn("text-text-secondary transition-transform duration-150", isExpanded && "rotate-90")
1697
1809
  }
1698
1810
  ),
1699
- /* @__PURE__ */ jsx36(SectionLabel, { children: title })
1811
+ /* @__PURE__ */ jsx40(SectionLabel, { children: title })
1700
1812
  ] }),
1701
- (showActions === "always" || isHovered) && actions && actions.length > 0 && /* @__PURE__ */ jsx36("div", { className: "flex items-center gap-1", children: actions.map((action) => /* @__PURE__ */ jsx36(
1813
+ (showActions === "always" || isHovered) && actions && actions.length > 0 && /* @__PURE__ */ jsx40("div", { className: "flex items-center gap-1", children: actions.map((action) => /* @__PURE__ */ jsx40(
1702
1814
  IconButton,
1703
1815
  {
1704
1816
  tooltip: action.tooltip,
@@ -1718,9 +1830,9 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, ac
1718
1830
 
1719
1831
  // src/Tabs.tsx
1720
1832
  import { Tabs as BaseTabs } from "@base-ui/react/tabs";
1721
- import { jsx as jsx37, jsxs as jsxs24 } from "react/jsx-runtime";
1722
- function Tabs({ tabs, active, onChange, size = "default", className }) {
1723
- return /* @__PURE__ */ jsx37(
1833
+ import { jsx as jsx41, jsxs as jsxs27 } from "react/jsx-runtime";
1834
+ function Tabs({ tabs, active, onChange, size = "default", className, ...aria }) {
1835
+ return /* @__PURE__ */ jsx41(
1724
1836
  BaseTabs.Root,
1725
1837
  {
1726
1838
  value: active,
@@ -1728,36 +1840,45 @@ function Tabs({ tabs, active, onChange, size = "default", className }) {
1728
1840
  if (details.reason === "none") onChange(value);
1729
1841
  },
1730
1842
  className,
1731
- children: /* @__PURE__ */ jsx37(BaseTabs.List, { activateOnFocus: true, className: "flex items-center gap-2", children: tabs.map((tab) => /* @__PURE__ */ jsxs24(
1732
- BaseTabs.Tab,
1843
+ children: /* @__PURE__ */ jsx41(
1844
+ BaseTabs.List,
1733
1845
  {
1734
- value: tab.id,
1735
- className: (state) => cn(
1736
- "flex cursor-pointer items-center transition-colors",
1737
- // gap: default is Ship's 6px; small keeps Peek's original 4px, or
1738
- // "small is Peek's geometry" stops being true.
1739
- size === "default" ? "gap-1.5 rounded-md px-2 py-1 text-body-2" : "gap-1 rounded px-1.5 py-0.5 text-caption",
1740
- state.active ? "bg-bg-active text-text-primary" : "text-text-secondary hover:bg-bg-hover"
1741
- ),
1742
- children: [
1743
- tab.icon,
1744
- /* @__PURE__ */ jsxs24("span", { className: "flex items-baseline", children: [
1745
- tab.label,
1746
- tab.count !== void 0 ? /* @__PURE__ */ jsx37("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
1747
- ] })
1748
- ]
1749
- },
1750
- tab.id
1751
- )) })
1846
+ activateOnFocus: true,
1847
+ "aria-label": aria["aria-label"],
1848
+ "aria-labelledby": aria["aria-labelledby"],
1849
+ className: "flex items-center gap-2",
1850
+ children: tabs.map((tab) => /* @__PURE__ */ jsxs27(
1851
+ BaseTabs.Tab,
1852
+ {
1853
+ value: tab.id,
1854
+ className: (state) => cn(
1855
+ "flex cursor-pointer items-center transition-colors",
1856
+ // gap: default is Ship's 6px; small keeps Peek's original 4px, or
1857
+ // "small is Peek's geometry" stops being true.
1858
+ size === "default" ? "gap-1.5 rounded-md px-2 py-1 text-body-2" : "gap-1 rounded px-1.5 py-0.5 text-caption",
1859
+ state.active ? "bg-bg-active text-text-primary" : "text-text-secondary hover:bg-bg-hover"
1860
+ ),
1861
+ children: [
1862
+ tab.icon,
1863
+ /* @__PURE__ */ jsxs27("span", { className: "flex items-baseline", children: [
1864
+ tab.label,
1865
+ tab.count !== void 0 ? /* @__PURE__ */ jsx41("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
1866
+ ] })
1867
+ ]
1868
+ },
1869
+ tab.id
1870
+ ))
1871
+ }
1872
+ )
1752
1873
  }
1753
1874
  );
1754
1875
  }
1755
1876
 
1756
1877
  // src/Toast.tsx
1757
- import { createContext as createContext3, useCallback as useCallback5, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo3, useState as useState11 } from "react";
1758
- import { createPortal as createPortal6 } from "react-dom";
1878
+ import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect3, useMemo as useMemo3, useState as useState7 } from "react";
1879
+ import { createPortal as createPortal2 } from "react-dom";
1759
1880
  import { IconCircleCheck } from "@tabler/icons-react";
1760
- import { jsx as jsx38, jsxs as jsxs25 } from "react/jsx-runtime";
1881
+ import { jsx as jsx42, jsxs as jsxs28 } from "react/jsx-runtime";
1761
1882
  var SURFACE_STYLES = {
1762
1883
  success: "bg-success-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]",
1763
1884
  brand: "bg-accent-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-[shadow:var(--shadow-md)]",
@@ -1775,7 +1896,7 @@ var ACTION_BORDER_STYLES = {
1775
1896
  };
1776
1897
  function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAction, className }) {
1777
1898
  const hasAction = !!(actionLabel && onAction);
1778
- return /* @__PURE__ */ jsxs25(
1899
+ return /* @__PURE__ */ jsxs28(
1779
1900
  "div",
1780
1901
  {
1781
1902
  className: cn(
@@ -1787,11 +1908,11 @@ function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAct
1787
1908
  className
1788
1909
  ),
1789
1910
  children: [
1790
- /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2 shrink-0", children: [
1791
- leadingIcon && /* @__PURE__ */ jsx38(IconCircleCheck, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_STYLES[type]) }),
1792
- /* @__PURE__ */ jsx38("span", { className: "font-normal text-[14px] leading-[1.4] text-text-primary whitespace-nowrap", children: label })
1911
+ /* @__PURE__ */ jsxs28("div", { className: "flex items-center gap-2 shrink-0", children: [
1912
+ leadingIcon && /* @__PURE__ */ jsx42(IconCircleCheck, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_STYLES[type]) }),
1913
+ /* @__PURE__ */ jsx42("span", { className: "font-normal text-[14px] leading-[1.4] text-text-primary whitespace-nowrap", children: label })
1793
1914
  ] }),
1794
- hasAction && /* @__PURE__ */ jsx38(
1915
+ hasAction && /* @__PURE__ */ jsx42(
1795
1916
  "button",
1796
1917
  {
1797
1918
  type: "button",
@@ -1801,24 +1922,24 @@ function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAct
1801
1922
  ACTION_BORDER_STYLES[type],
1802
1923
  type === "neutral" ? "hover:border-border-strong" : "hover:opacity-80"
1803
1924
  ),
1804
- children: /* @__PURE__ */ jsx38("span", { className: "font-medium text-[12px] leading-[12px] text-text-primary whitespace-nowrap", children: actionLabel })
1925
+ children: /* @__PURE__ */ jsx42("span", { className: "font-medium text-[12px] leading-[12px] text-text-primary whitespace-nowrap", children: actionLabel })
1805
1926
  }
1806
1927
  )
1807
1928
  ]
1808
1929
  }
1809
1930
  );
1810
1931
  }
1811
- var ToastContext = createContext3(null);
1932
+ var ToastContext = createContext2(null);
1812
1933
  var toastSeq = 0;
1813
1934
  function ToastProvider({ children }) {
1814
- const [toast, setToast] = useState11(null);
1815
- const showToast = useCallback5((opts) => {
1935
+ const [toast, setToast] = useState7(null);
1936
+ const showToast = useCallback3((opts) => {
1816
1937
  setToast({ ...opts, id: ++toastSeq });
1817
1938
  }, []);
1818
- const dismissToast = useCallback5(() => {
1939
+ const dismissToast = useCallback3(() => {
1819
1940
  setToast(null);
1820
1941
  }, []);
1821
- useEffect6(() => {
1942
+ useEffect3(() => {
1822
1943
  if (!toast) return;
1823
1944
  const duration = toast.durationMs ?? 5e3;
1824
1945
  if (duration <= 0) return;
@@ -1828,10 +1949,10 @@ function ToastProvider({ children }) {
1828
1949
  return () => clearTimeout(timer);
1829
1950
  }, [toast]);
1830
1951
  const value = useMemo3(() => ({ showToast, dismissToast }), [showToast, dismissToast]);
1831
- return /* @__PURE__ */ jsxs25(ToastContext.Provider, { value, children: [
1952
+ return /* @__PURE__ */ jsxs28(ToastContext.Provider, { value, children: [
1832
1953
  children,
1833
- toast && createPortal6(
1834
- /* @__PURE__ */ jsx38("div", { className: "fixed bottom-4 left-4 z-[100] pointer-events-none", children: /* @__PURE__ */ jsx38(
1954
+ toast && createPortal2(
1955
+ /* @__PURE__ */ jsx42("div", { className: "fixed bottom-4 left-4 z-[100] pointer-events-none", children: /* @__PURE__ */ jsx42(
1835
1956
  Toast,
1836
1957
  {
1837
1958
  className: "pointer-events-auto",
@@ -1850,7 +1971,7 @@ function ToastProvider({ children }) {
1850
1971
  ] });
1851
1972
  }
1852
1973
  function useToast() {
1853
- const ctx = useContext3(ToastContext);
1974
+ const ctx = useContext2(ToastContext);
1854
1975
  if (!ctx) throw new Error("useToast must be used within ToastProvider");
1855
1976
  return ctx;
1856
1977
  }
@@ -1885,10 +2006,13 @@ export {
1885
2006
  NavItem,
1886
2007
  Person,
1887
2008
  PersonTrigger,
2009
+ Popover,
2010
+ PreviewCard,
1888
2011
  Property,
1889
2012
  Rail,
1890
2013
  RailItem,
1891
2014
  Reaction,
2015
+ ReactionPicker,
1892
2016
  SearchInput,
1893
2017
  SectionHeader,
1894
2018
  SectionLabel,
@@ -1902,16 +2026,17 @@ export {
1902
2026
  Textarea,
1903
2027
  Toast,
1904
2028
  ToastProvider,
2029
+ Toolbar,
2030
+ ToolbarButton,
2031
+ ToolbarInput,
2032
+ ToolbarSeparator,
1905
2033
  Tooltip,
2034
+ TooltipProvider,
1906
2035
  TopBar,
1907
2036
  WithTooltip,
1908
- clampBox,
1909
2037
  cn,
1910
- fitMenu,
1911
- fitSubmenu,
1912
2038
  hueFor,
1913
2039
  initialsFor,
1914
- useFieldControlId,
1915
2040
  useToast
1916
2041
  };
1917
2042
  //# sourceMappingURL=index.js.map