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