@nimblebrain/synapse 0.8.0 → 0.9.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.
@@ -0,0 +1,1397 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/ui/components/Avatar.tsx
7
+
8
+ // src/ui/tokens.ts
9
+ var tokens = {
10
+ // ── Surfaces ──
11
+ bg: "var(--color-background-primary, #ffffff)",
12
+ bgRaised: "var(--color-background-secondary, #fafafa)",
13
+ bgSubtle: "var(--color-background-tertiary, #f3f4f6)",
14
+ // ── Text ──
15
+ fg: "var(--color-text-primary, #111827)",
16
+ fgMuted: "var(--color-text-secondary, #6b7280)",
17
+ fgFaint: "var(--color-text-tertiary, #9ca3af)",
18
+ accent: "var(--color-text-accent, #2563eb)",
19
+ accentFg: "var(--nb-color-accent-foreground, #ffffff)",
20
+ // ── Border / ring ──
21
+ border: "var(--color-border-primary, #e5e7eb)",
22
+ borderStrong: "var(--color-border-secondary, #d1d5db)",
23
+ ring: "var(--color-ring-primary, #2563eb)",
24
+ // ── Status / brand semantics ──
25
+ danger: "var(--nb-color-danger, #dc2626)",
26
+ success: "var(--nb-color-success, #059669)",
27
+ warning: "var(--nb-color-warning, #f59e0b)",
28
+ warm: "var(--nb-color-warm, #d4620a)",
29
+ warmLight: "var(--nb-color-warm-light, #fef5ee)",
30
+ processing: "var(--nb-color-processing, #7c3aed)",
31
+ processingLight: "var(--nb-color-processing-light, #f3eeff)",
32
+ infoLight: "var(--nb-color-info-light, #eef4ff)",
33
+ // ── Typography ──
34
+ fontSans: "var(--font-sans, system-ui, -apple-system, BlinkMacSystemFont, sans-serif)",
35
+ fontMono: "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)",
36
+ fontHeading: "var(--nb-font-heading, Georgia, 'Times New Roman', serif)",
37
+ weightNormal: "var(--font-weight-normal, 400)",
38
+ weightMedium: "var(--font-weight-medium, 500)",
39
+ weightSemibold: "var(--font-weight-semibold, 600)",
40
+ weightBold: "var(--font-weight-bold, 700)",
41
+ // ── Type scale (size + line-height pairs) ──
42
+ textXsSize: "var(--font-text-xs-size, 0.75rem)",
43
+ textXsLine: "var(--font-text-xs-line-height, 1rem)",
44
+ textSmSize: "var(--font-text-sm-size, 0.875rem)",
45
+ textSmLine: "var(--font-text-sm-line-height, 1.25rem)",
46
+ textBaseSize: "var(--font-text-base-size, 1rem)",
47
+ textBaseLine: "var(--font-text-base-line-height, 1.5rem)",
48
+ textLgSize: "var(--font-text-lg-size, 1.125rem)",
49
+ textLgLine: "var(--font-text-lg-line-height, 1.75rem)",
50
+ headingSmSize: "var(--font-heading-sm-size, 1.25rem)",
51
+ headingSmLine: "var(--font-heading-sm-line-height, 1.75rem)",
52
+ headingMdSize: "var(--font-heading-md-size, 1.5rem)",
53
+ headingMdLine: "var(--font-heading-md-line-height, 2rem)",
54
+ headingLgSize: "var(--font-heading-lg-size, 2rem)",
55
+ headingLgLine: "var(--font-heading-lg-line-height, 2.5rem)",
56
+ // ── Radius / border width ──
57
+ radiusXs: "var(--border-radius-xs, 0.25rem)",
58
+ radiusSm: "var(--border-radius-sm, 0.5rem)",
59
+ radiusMd: "var(--border-radius-md, 0.75rem)",
60
+ radiusLg: "var(--border-radius-lg, 1rem)",
61
+ radiusXl: "var(--border-radius-xl, 1.5rem)",
62
+ borderWidth: "var(--border-width-regular, 1px)",
63
+ // ── Shadows ──
64
+ shadowHairline: "var(--shadow-hairline, 0 0 0 1px rgba(0,0,0,0.06))",
65
+ shadowSm: "var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.05))",
66
+ shadowMd: "var(--shadow-md, 0 4px 6px -1px rgba(0,0,0,0.1))",
67
+ shadowLg: "var(--shadow-lg, 0 10px 15px -3px rgba(0,0,0,0.1))"
68
+ };
69
+ var TEXT_SCALE = {
70
+ xs: { fontSize: tokens.textXsSize, lineHeight: tokens.textXsLine },
71
+ sm: { fontSize: tokens.textSmSize, lineHeight: tokens.textSmLine },
72
+ base: { fontSize: tokens.textBaseSize, lineHeight: tokens.textBaseLine },
73
+ lg: { fontSize: tokens.textLgSize, lineHeight: tokens.textLgLine }
74
+ };
75
+ var HEADING_SCALE = {
76
+ sm: { fontSize: tokens.headingSmSize, lineHeight: tokens.headingSmLine },
77
+ md: { fontSize: tokens.headingMdSize, lineHeight: tokens.headingMdLine },
78
+ lg: { fontSize: tokens.headingLgSize, lineHeight: tokens.headingLgLine }
79
+ };
80
+ function textStyle(size) {
81
+ return TEXT_SCALE[size];
82
+ }
83
+ function headingStyle(size) {
84
+ return HEADING_SCALE[size];
85
+ }
86
+ function initialsOf(name) {
87
+ const parts = name.trim().split(/\s+/).filter(Boolean);
88
+ if (parts.length === 0) return "?";
89
+ const first = parts[0] ?? "";
90
+ if (parts.length === 1) return first.charAt(0).toUpperCase();
91
+ const last = parts[parts.length - 1] ?? "";
92
+ return (first.charAt(0) + last.charAt(0)).toUpperCase();
93
+ }
94
+ function Avatar({ name, src, size = 28, style, ...rest }) {
95
+ const [failed, setFailed] = react.useState(false);
96
+ react.useEffect(() => setFailed(false), [src]);
97
+ const showImage = Boolean(src) && !failed;
98
+ return /* @__PURE__ */ jsxRuntime.jsx(
99
+ "span",
100
+ {
101
+ style: {
102
+ display: "inline-flex",
103
+ alignItems: "center",
104
+ justifyContent: "center",
105
+ flexShrink: 0,
106
+ width: size,
107
+ height: size,
108
+ borderRadius: "50%",
109
+ overflow: "hidden",
110
+ background: tokens.bgSubtle,
111
+ color: tokens.fgMuted,
112
+ fontFamily: tokens.fontSans,
113
+ fontSize: Math.round(size * 0.4),
114
+ fontWeight: tokens.weightSemibold,
115
+ userSelect: "none",
116
+ ...style
117
+ },
118
+ ...rest,
119
+ children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
120
+ "img",
121
+ {
122
+ src,
123
+ alt: name,
124
+ width: size,
125
+ height: size,
126
+ onError: () => setFailed(true),
127
+ style: { width: "100%", height: "100%", objectFit: "cover" }
128
+ }
129
+ ) : initialsOf(name)
130
+ }
131
+ );
132
+ }
133
+ var TONE = {
134
+ neutral: { bg: tokens.bgSubtle, fg: tokens.fgMuted },
135
+ accent: { bg: tokens.infoLight, fg: tokens.accent },
136
+ success: { bg: `color-mix(in oklab, ${tokens.success} 14%, transparent)`, fg: tokens.success },
137
+ warning: { bg: `color-mix(in oklab, ${tokens.warning} 14%, transparent)`, fg: tokens.warning },
138
+ danger: { bg: `color-mix(in oklab, ${tokens.danger} 14%, transparent)`, fg: tokens.danger },
139
+ processing: { bg: tokens.processingLight, fg: tokens.processing },
140
+ warm: { bg: tokens.warmLight, fg: tokens.warm }
141
+ };
142
+ function Badge({ tone = "neutral", style, children, ...rest }) {
143
+ const { bg, fg } = TONE[tone];
144
+ return /* @__PURE__ */ jsxRuntime.jsx(
145
+ "span",
146
+ {
147
+ style: {
148
+ display: "inline-flex",
149
+ alignItems: "center",
150
+ gap: "0.25rem",
151
+ padding: "0.1rem 0.45rem",
152
+ borderRadius: tokens.radiusXs,
153
+ background: bg,
154
+ color: fg,
155
+ fontFamily: tokens.fontSans,
156
+ fontSize: tokens.textXsSize,
157
+ lineHeight: tokens.textXsLine,
158
+ fontWeight: tokens.weightSemibold,
159
+ letterSpacing: "0.02em",
160
+ textTransform: "uppercase",
161
+ whiteSpace: "nowrap",
162
+ ...style
163
+ },
164
+ ...rest,
165
+ children
166
+ }
167
+ );
168
+ }
169
+
170
+ // src/ui/internal/inject-style.ts
171
+ function ensureStyle(id, css) {
172
+ if (typeof document === "undefined") return;
173
+ if (document.getElementById(id)) return;
174
+ const style = document.createElement("style");
175
+ style.id = id;
176
+ style.textContent = css;
177
+ document.head.appendChild(style);
178
+ }
179
+ var STYLE_ID = "nb-synapse-button";
180
+ var RULES = `
181
+ .nb-btn {
182
+ --nb-btn-bg: transparent;
183
+ --nb-btn-bg-hover: transparent;
184
+ display: inline-flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ gap: 0.4rem;
188
+ border: var(--nb-btn-border, none);
189
+ border-radius: var(--nb-btn-radius);
190
+ background: var(--nb-btn-bg);
191
+ color: var(--nb-btn-fg);
192
+ font-family: var(--nb-btn-font);
193
+ font-weight: var(--nb-btn-weight);
194
+ cursor: pointer;
195
+ transition: background 130ms ease, color 130ms ease, opacity 130ms ease;
196
+ white-space: nowrap;
197
+ }
198
+ .nb-btn:hover { background: var(--nb-btn-bg-hover); }
199
+ .nb-btn:active { transform: translateY(0.5px); }
200
+ .nb-btn:disabled { opacity: 0.5; cursor: default; }
201
+ .nb-btn:disabled:hover { background: var(--nb-btn-bg); }
202
+ .nb-textlink {
203
+ border: none;
204
+ background: none;
205
+ padding: 0;
206
+ cursor: pointer;
207
+ font-family: var(--nb-tl-font);
208
+ color: var(--nb-tl-fg);
209
+ transition: color 130ms ease, opacity 130ms ease;
210
+ }
211
+ .nb-textlink:hover { color: var(--nb-tl-fg-hover); }
212
+ .nb-textlink:disabled { opacity: 0.45; cursor: default; }
213
+ `;
214
+ var SIZE_PAD = {
215
+ sm: "0.3rem 0.6rem",
216
+ md: "0.45rem 0.85rem"
217
+ };
218
+ function Button({
219
+ variant = "primary",
220
+ size = "md",
221
+ style,
222
+ className,
223
+ children,
224
+ ...rest
225
+ }) {
226
+ ensureStyle(STYLE_ID, RULES);
227
+ const vars = {
228
+ "--nb-btn-radius": tokens.radiusSm,
229
+ "--nb-btn-font": tokens.fontSans,
230
+ "--nb-btn-weight": tokens.weightMedium
231
+ };
232
+ if (variant === "primary") {
233
+ vars["--nb-btn-bg"] = tokens.accent;
234
+ vars["--nb-btn-bg-hover"] = `color-mix(in oklab, ${tokens.accent} 88%, black)`;
235
+ vars["--nb-btn-fg"] = tokens.accentFg;
236
+ } else if (variant === "secondary") {
237
+ vars["--nb-btn-bg"] = tokens.bgSubtle;
238
+ vars["--nb-btn-bg-hover"] = tokens.bgRaised;
239
+ vars["--nb-btn-fg"] = tokens.fg;
240
+ vars["--nb-btn-border"] = `${tokens.borderWidth} solid ${tokens.border}`;
241
+ } else {
242
+ vars["--nb-btn-bg"] = "transparent";
243
+ vars["--nb-btn-bg-hover"] = tokens.bgSubtle;
244
+ vars["--nb-btn-fg"] = tokens.fg;
245
+ }
246
+ const btnStyle = {
247
+ ...vars,
248
+ padding: SIZE_PAD[size],
249
+ fontSize: size === "sm" ? tokens.textSmSize : tokens.textBaseSize,
250
+ lineHeight: size === "sm" ? tokens.textSmLine : tokens.textBaseLine,
251
+ ...style
252
+ };
253
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: `nb-btn ${className ?? ""}`.trim(), style: btnStyle, ...rest, children });
254
+ }
255
+ function TextLink({ tone = "muted", style, className, children, ...rest }) {
256
+ ensureStyle(STYLE_ID, RULES);
257
+ const resting = tone === "danger" ? tokens.danger : tone === "default" ? tokens.fg : tokens.fgMuted;
258
+ const hover = tone === "danger" ? tokens.danger : tokens.accent;
259
+ const linkStyle = {
260
+ "--nb-tl-font": tokens.fontSans,
261
+ "--nb-tl-fg": resting,
262
+ "--nb-tl-fg-hover": hover,
263
+ fontSize: tokens.textXsSize,
264
+ lineHeight: tokens.textXsLine,
265
+ letterSpacing: "0.02em",
266
+ ...style
267
+ };
268
+ return /* @__PURE__ */ jsxRuntime.jsx(
269
+ "button",
270
+ {
271
+ type: "button",
272
+ className: `nb-textlink ${className ?? ""}`.trim(),
273
+ style: linkStyle,
274
+ ...rest,
275
+ children
276
+ }
277
+ );
278
+ }
279
+ var STYLE_ID2 = "nb-synapse-card";
280
+ var RULES2 = `
281
+ .nb-card--interactive {
282
+ cursor: pointer;
283
+ transition: box-shadow 140ms ease, border-color 140ms ease;
284
+ }
285
+ .nb-card--interactive:hover {
286
+ box-shadow: var(--nb-card-shadow-hover);
287
+ border-color: var(--nb-card-border-hover);
288
+ }
289
+ `;
290
+ function Card({
291
+ padding = "0.875rem",
292
+ interactive = false,
293
+ style,
294
+ className,
295
+ children,
296
+ ...rest
297
+ }) {
298
+ if (interactive) ensureStyle(STYLE_ID2, RULES2);
299
+ const cardStyle = {
300
+ background: tokens.bgRaised,
301
+ border: `${tokens.borderWidth} solid ${tokens.border}`,
302
+ borderRadius: tokens.radiusMd,
303
+ padding,
304
+ boxShadow: tokens.shadowSm,
305
+ "--nb-card-shadow-hover": tokens.shadowMd,
306
+ "--nb-card-border-hover": tokens.borderStrong,
307
+ ...style
308
+ };
309
+ return /* @__PURE__ */ jsxRuntime.jsx(
310
+ "div",
311
+ {
312
+ className: interactive ? `nb-card--interactive ${className ?? ""}`.trim() : className,
313
+ style: cardStyle,
314
+ ...rest,
315
+ children
316
+ }
317
+ );
318
+ }
319
+ var STYLE_ID3 = "nb-synapse-drawer";
320
+ var RULES3 = `
321
+ .nb-drawer {
322
+ margin: 0; padding: 0; border: none; max-width: 92%;
323
+ height: 100%; max-height: 100%; display: flex; flex-direction: column;
324
+ }
325
+ .nb-drawer--right { margin-left: auto; }
326
+ .nb-drawer--left { margin-right: auto; }
327
+ .nb-drawer--right[open] { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); }
328
+ .nb-drawer--left[open] { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); }
329
+ .nb-drawer::backdrop { background: rgba(0, 0, 0, 0.32); }
330
+ .nb-drawer[open]::backdrop { animation: nb-drawer-fade 200ms ease; }
331
+ @keyframes nb-drawer-in-right { from { transform: translateX(100%); } }
332
+ @keyframes nb-drawer-in-left { from { transform: translateX(-100%); } }
333
+ @keyframes nb-drawer-fade { from { opacity: 0; } }
334
+ `;
335
+ function DrawerRoot({
336
+ open,
337
+ onClose,
338
+ onEscape,
339
+ side = "right",
340
+ width = 480,
341
+ style,
342
+ children,
343
+ ...rest
344
+ }) {
345
+ ensureStyle(STYLE_ID3, RULES3);
346
+ const dialogRef = react.useRef(null);
347
+ react.useEffect(() => {
348
+ const dialog = dialogRef.current;
349
+ if (!dialog || typeof dialog.showModal !== "function") return;
350
+ if (open && !dialog.open) dialog.showModal();
351
+ else if (!open && dialog.open) dialog.close();
352
+ }, [open]);
353
+ const panelStyle = {
354
+ width,
355
+ background: tokens.bg,
356
+ color: tokens.fg,
357
+ boxShadow: tokens.shadowLg,
358
+ ...side === "right" ? { borderLeft: `${tokens.borderWidth} solid ${tokens.border}` } : { borderRight: `${tokens.borderWidth} solid ${tokens.border}` },
359
+ ...style
360
+ };
361
+ return (
362
+ // biome-ignore lint/a11y/useKeyWithClickEvents: backdrop click is a mouse convenience; the dialog's Escape (onCancel) is the keyboard dismissal path.
363
+ /* @__PURE__ */ jsxRuntime.jsx(
364
+ "dialog",
365
+ {
366
+ ref: dialogRef,
367
+ className: `nb-drawer nb-drawer--${side}`,
368
+ style: panelStyle,
369
+ onCancel: (e) => {
370
+ e.preventDefault();
371
+ (onEscape ?? onClose)();
372
+ },
373
+ onClick: (e) => {
374
+ if (e.target === dialogRef.current) onClose();
375
+ },
376
+ ...rest,
377
+ children
378
+ }
379
+ )
380
+ );
381
+ }
382
+ function Header({ onClose, style, children, ...rest }) {
383
+ return /* @__PURE__ */ jsxRuntime.jsxs(
384
+ "header",
385
+ {
386
+ style: {
387
+ flexShrink: 0,
388
+ display: "flex",
389
+ alignItems: "center",
390
+ gap: "0.75rem",
391
+ padding: "0.875rem 1rem",
392
+ borderBottom: `${tokens.borderWidth} solid ${tokens.border}`,
393
+ fontFamily: tokens.fontSans,
394
+ fontWeight: tokens.weightSemibold,
395
+ color: tokens.fg,
396
+ ...style
397
+ },
398
+ ...rest,
399
+ children: [
400
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minWidth: 0 }, children }),
401
+ onClose ? /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClose }) : null
402
+ ]
403
+ }
404
+ );
405
+ }
406
+ function CloseButton({ onClose }) {
407
+ return /* @__PURE__ */ jsxRuntime.jsx(
408
+ "button",
409
+ {
410
+ type: "button",
411
+ "aria-label": "Close",
412
+ onClick: onClose,
413
+ style: {
414
+ flexShrink: 0,
415
+ display: "inline-flex",
416
+ alignItems: "center",
417
+ justifyContent: "center",
418
+ width: 28,
419
+ height: 28,
420
+ border: "none",
421
+ background: "transparent",
422
+ color: tokens.fgMuted,
423
+ cursor: "pointer",
424
+ borderRadius: tokens.radiusSm
425
+ },
426
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
427
+ "path",
428
+ {
429
+ d: "M4 4l8 8M12 4l-8 8",
430
+ stroke: "currentColor",
431
+ strokeWidth: "1.5",
432
+ strokeLinecap: "round"
433
+ }
434
+ ) })
435
+ }
436
+ );
437
+ }
438
+ function Body({ style, children, ...rest }) {
439
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minHeight: 0, overflowY: "auto", padding: "1rem", ...style }, ...rest, children });
440
+ }
441
+ function Footer({ style, children, ...rest }) {
442
+ return /* @__PURE__ */ jsxRuntime.jsx(
443
+ "footer",
444
+ {
445
+ style: {
446
+ flexShrink: 0,
447
+ padding: "0.75rem 1rem",
448
+ borderTop: `${tokens.borderWidth} solid ${tokens.border}`,
449
+ ...style
450
+ },
451
+ ...rest,
452
+ children
453
+ }
454
+ );
455
+ }
456
+ var Drawer = Object.assign(DrawerRoot, { Header, Body, Footer });
457
+ var ALIGN = {
458
+ start: "flex-start",
459
+ center: "center",
460
+ end: "flex-end",
461
+ stretch: "stretch",
462
+ baseline: "baseline"
463
+ };
464
+ var JUSTIFY = {
465
+ start: "flex-start",
466
+ center: "center",
467
+ end: "flex-end",
468
+ between: "space-between",
469
+ around: "space-around"
470
+ };
471
+ function flexStyle(direction, { gap, align, justify, wrap }) {
472
+ return {
473
+ display: "flex",
474
+ flexDirection: direction,
475
+ gap,
476
+ alignItems: align ? ALIGN[align] : void 0,
477
+ justifyContent: justify ? JUSTIFY[justify] : void 0,
478
+ flexWrap: wrap ? "wrap" : void 0
479
+ };
480
+ }
481
+ function Stack({ gap, align, justify, wrap, style, children, ...rest }) {
482
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...flexStyle("column", { gap, align, justify, wrap }), ...style }, ...rest, children });
483
+ }
484
+ function Inline({
485
+ gap,
486
+ align = "center",
487
+ justify,
488
+ wrap,
489
+ style,
490
+ children,
491
+ ...rest
492
+ }) {
493
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...flexStyle("row", { gap, align, justify, wrap }), ...style }, ...rest, children });
494
+ }
495
+ function Spacer({ style, ...rest }) {
496
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, ...style }, ...rest });
497
+ }
498
+ function Divider({ orientation = "horizontal", style, ...rest }) {
499
+ const isVertical = orientation === "vertical";
500
+ return /* @__PURE__ */ jsxRuntime.jsx(
501
+ "div",
502
+ {
503
+ "aria-hidden": "true",
504
+ style: {
505
+ flexShrink: 0,
506
+ background: tokens.border,
507
+ ...isVertical ? { width: tokens.borderWidth, alignSelf: "stretch" } : { height: tokens.borderWidth, width: "100%" },
508
+ ...style
509
+ },
510
+ ...rest
511
+ }
512
+ );
513
+ }
514
+ var TONE2 = {
515
+ default: tokens.fg,
516
+ muted: tokens.fgMuted,
517
+ faint: tokens.fgFaint,
518
+ accent: tokens.accent,
519
+ danger: tokens.danger,
520
+ success: tokens.success
521
+ };
522
+ var WEIGHT = {
523
+ normal: tokens.weightNormal,
524
+ medium: tokens.weightMedium,
525
+ semibold: tokens.weightSemibold,
526
+ bold: tokens.weightBold
527
+ };
528
+ function Text({
529
+ size = "base",
530
+ weight = "normal",
531
+ tone = "default",
532
+ mono = false,
533
+ truncate = false,
534
+ as: As = "span",
535
+ style,
536
+ children,
537
+ ...rest
538
+ }) {
539
+ const truncation = truncate ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : {};
540
+ return /* @__PURE__ */ jsxRuntime.jsx(
541
+ As,
542
+ {
543
+ style: {
544
+ margin: 0,
545
+ fontFamily: mono ? tokens.fontMono : tokens.fontSans,
546
+ fontWeight: WEIGHT[weight],
547
+ color: TONE2[tone],
548
+ ...textStyle(size),
549
+ ...truncation,
550
+ ...style
551
+ },
552
+ ...rest,
553
+ children
554
+ }
555
+ );
556
+ }
557
+ function Heading({
558
+ size = "md",
559
+ weight = "semibold",
560
+ tone = "default",
561
+ as: As = "h2",
562
+ style,
563
+ children,
564
+ ...rest
565
+ }) {
566
+ return /* @__PURE__ */ jsxRuntime.jsx(
567
+ As,
568
+ {
569
+ style: {
570
+ margin: 0,
571
+ fontFamily: tokens.fontHeading,
572
+ fontWeight: WEIGHT[weight],
573
+ color: TONE2[tone],
574
+ letterSpacing: "-0.015em",
575
+ ...headingStyle(size),
576
+ ...style
577
+ },
578
+ ...rest,
579
+ children
580
+ }
581
+ );
582
+ }
583
+ function EmptyState({ icon, title, description, action, style, ...rest }) {
584
+ return /* @__PURE__ */ jsxRuntime.jsxs(
585
+ Stack,
586
+ {
587
+ align: "center",
588
+ gap: "0.5rem",
589
+ style: {
590
+ textAlign: "center",
591
+ padding: "4rem 1.5rem",
592
+ color: tokens.fgMuted,
593
+ ...style
594
+ },
595
+ ...rest,
596
+ children: [
597
+ icon ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: tokens.fgFaint, marginBottom: "0.25rem" }, children: icon }) : null,
598
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Heading, { size: "sm", children: title }) : null,
599
+ description ? /* @__PURE__ */ jsxRuntime.jsx(Text, { size: "sm", tone: "muted", style: { maxWidth: 360 }, children: description }) : null,
600
+ action ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: "0.75rem" }, children: action }) : null
601
+ ]
602
+ }
603
+ );
604
+ }
605
+ var STYLE_ID4 = "nb-synapse-listrow";
606
+ var RULES4 = `
607
+ .nb-listrow { transition: background 140ms ease; }
608
+ .nb-listrow--interactive { cursor: pointer; }
609
+ .nb-listrow--interactive:hover { background: var(--nb-listrow-hover); }
610
+ `;
611
+ function ListRow({
612
+ title,
613
+ meta,
614
+ leading,
615
+ trailing,
616
+ interactive = false,
617
+ style,
618
+ className,
619
+ ...rest
620
+ }) {
621
+ ensureStyle(STYLE_ID4, RULES4);
622
+ const rowStyle = {
623
+ "--nb-listrow-hover": tokens.bgSubtle,
624
+ display: "grid",
625
+ gridTemplateColumns: `${leading ? "auto " : ""}minmax(0, 1fr)${trailing ? " auto" : ""}`,
626
+ columnGap: "0.75rem",
627
+ alignItems: "baseline",
628
+ padding: "0.65rem 0.75rem",
629
+ borderRadius: tokens.radiusSm,
630
+ ...style
631
+ };
632
+ const interactiveProps = interactive ? {
633
+ role: "button",
634
+ tabIndex: 0,
635
+ onKeyDown: (e) => {
636
+ if (e.key === "Enter" || e.key === " ") {
637
+ e.preventDefault();
638
+ e.currentTarget.click();
639
+ }
640
+ }
641
+ } : {};
642
+ return /* @__PURE__ */ jsxRuntime.jsxs(
643
+ "div",
644
+ {
645
+ className: `nb-listrow ${interactive ? "nb-listrow--interactive" : ""} ${className ?? ""}`.trim(),
646
+ style: rowStyle,
647
+ ...interactiveProps,
648
+ ...rest,
649
+ children: [
650
+ leading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { alignSelf: "center", display: "flex" }, children: leading }) : null,
651
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0 }, children: [
652
+ /* @__PURE__ */ jsxRuntime.jsx(
653
+ "div",
654
+ {
655
+ style: {
656
+ fontFamily: tokens.fontSans,
657
+ fontSize: tokens.textBaseSize,
658
+ lineHeight: tokens.textBaseLine,
659
+ fontWeight: tokens.weightMedium,
660
+ color: tokens.fg,
661
+ overflow: "hidden",
662
+ textOverflow: "ellipsis",
663
+ whiteSpace: "nowrap"
664
+ },
665
+ children: title
666
+ }
667
+ ),
668
+ meta ? /* @__PURE__ */ jsxRuntime.jsx(
669
+ "div",
670
+ {
671
+ style: {
672
+ marginTop: "0.15rem",
673
+ fontFamily: tokens.fontSans,
674
+ fontSize: tokens.textXsSize,
675
+ lineHeight: tokens.textXsLine,
676
+ color: tokens.fgMuted,
677
+ overflow: "hidden",
678
+ textOverflow: "ellipsis",
679
+ whiteSpace: "nowrap"
680
+ },
681
+ children: meta
682
+ }
683
+ ) : null
684
+ ] }),
685
+ trailing ? /* @__PURE__ */ jsxRuntime.jsx(
686
+ "div",
687
+ {
688
+ style: {
689
+ alignSelf: "center",
690
+ display: "inline-flex",
691
+ alignItems: "center",
692
+ gap: "0.5rem",
693
+ fontSize: tokens.textXsSize,
694
+ color: tokens.fgMuted,
695
+ whiteSpace: "nowrap"
696
+ },
697
+ children: trailing
698
+ }
699
+ ) : null
700
+ ]
701
+ }
702
+ );
703
+ }
704
+ function Pagination({ page, pageCount, onChange, style, ...rest }) {
705
+ const total = Math.max(1, pageCount);
706
+ const atStart = page <= 1;
707
+ const atEnd = page >= total;
708
+ return /* @__PURE__ */ jsxRuntime.jsxs(
709
+ Inline,
710
+ {
711
+ justify: "between",
712
+ style: {
713
+ fontFamily: tokens.fontSans,
714
+ fontSize: tokens.textXsSize,
715
+ color: tokens.fgMuted,
716
+ letterSpacing: "0.02em",
717
+ ...style
718
+ },
719
+ ...rest,
720
+ children: [
721
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontVariantNumeric: "tabular-nums" }, children: [
722
+ "Page ",
723
+ page,
724
+ " of ",
725
+ total
726
+ ] }),
727
+ /* @__PURE__ */ jsxRuntime.jsxs(Inline, { gap: "1rem", children: [
728
+ /* @__PURE__ */ jsxRuntime.jsx(TextLink, { disabled: atStart, onClick: () => onChange(page - 1), children: "\u2190 Prev" }),
729
+ /* @__PURE__ */ jsxRuntime.jsx(TextLink, { disabled: atEnd, onClick: () => onChange(page + 1), children: "Next \u2192" })
730
+ ] })
731
+ ]
732
+ }
733
+ );
734
+ }
735
+ var STYLE_ID5 = "nb-synapse-prose";
736
+ var RULES5 = `
737
+ .nb-prose {
738
+ font-family: ${tokens.fontSans};
739
+ font-size: ${tokens.textBaseSize};
740
+ line-height: 1.7;
741
+ color: ${tokens.fg};
742
+ }
743
+ .nb-prose > :first-child { margin-top: 0; }
744
+ .nb-prose > :last-child { margin-bottom: 0; }
745
+ .nb-prose h1, .nb-prose h2 { font-family: ${tokens.fontHeading}; letter-spacing: -0.015em; color: ${tokens.fg}; }
746
+ .nb-prose h1 { font-size: ${tokens.headingMdSize}; line-height: ${tokens.headingMdLine}; margin: 2rem 0 0.75rem; }
747
+ .nb-prose h2 { font-size: ${tokens.headingSmSize}; line-height: ${tokens.headingSmLine}; margin: 1.75rem 0 0.5rem; }
748
+ .nb-prose h3 { font-size: ${tokens.textBaseSize}; font-weight: ${tokens.weightSemibold}; margin: 1.5rem 0 0.35rem; color: ${tokens.fg}; }
749
+ .nb-prose p { margin: 0 0 1rem; }
750
+ .nb-prose ul, .nb-prose ol { margin: 0 0 1rem; padding-left: 1.25rem; }
751
+ .nb-prose li { margin: 0.25rem 0; }
752
+ .nb-prose a { color: ${tokens.accent}; text-decoration: underline; text-underline-offset: 2px; }
753
+ .nb-prose blockquote { margin: 0 0 1rem; padding-left: 1rem; border-left: 2px solid ${tokens.accent}; color: ${tokens.fgMuted}; font-style: italic; }
754
+ .nb-prose code { font-family: ${tokens.fontMono}; font-size: 0.88em; padding: 0.08rem 0.32rem; border-radius: ${tokens.radiusXs}; background: ${tokens.bgSubtle}; }
755
+ .nb-prose pre { font-family: ${tokens.fontMono}; font-size: ${tokens.textSmSize}; line-height: 1.55; margin: 0 0 1.25rem; padding: 0.875rem 1rem; background: ${tokens.bgSubtle}; border-radius: ${tokens.radiusSm}; overflow-x: auto; }
756
+ .nb-prose pre code { padding: 0; background: none; }
757
+ .nb-prose hr { border: none; border-top: 1px solid ${tokens.border}; margin: 2rem 0; }
758
+ .nb-prose strong { font-weight: ${tokens.weightSemibold}; }
759
+ `;
760
+ function Prose({ html, children, className, ...rest }) {
761
+ ensureStyle(STYLE_ID5, RULES5);
762
+ const cls = `nb-prose ${className ?? ""}`.trim();
763
+ if (html !== void 0) {
764
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, dangerouslySetInnerHTML: { __html: html }, ...rest });
765
+ }
766
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, ...rest, children });
767
+ }
768
+ var STYLE_ID6 = "nb-synapse-searchfield";
769
+ var RULES6 = `
770
+ .nb-search { font-family: var(--nb-search-font); color: var(--nb-search-fg); outline: none; }
771
+ .nb-search::placeholder { color: var(--nb-search-placeholder); }
772
+ .nb-search--underline {
773
+ border: none;
774
+ border-bottom: 1px solid transparent;
775
+ background: transparent;
776
+ padding: 0.3rem 0.1rem;
777
+ transition: border-color 160ms ease;
778
+ }
779
+ .nb-search--underline:focus { border-bottom-color: var(--nb-search-accent); }
780
+ .nb-search--boxed {
781
+ border: 1px solid var(--nb-search-border);
782
+ border-radius: var(--nb-search-radius);
783
+ background: var(--nb-search-bg);
784
+ padding: 0.4rem 0.6rem;
785
+ transition: border-color 160ms ease, box-shadow 160ms ease;
786
+ }
787
+ .nb-search--boxed:focus {
788
+ border-color: var(--nb-search-accent);
789
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--nb-search-accent) 18%, transparent);
790
+ }
791
+ `;
792
+ function SearchField({
793
+ variant = "underline",
794
+ style,
795
+ className,
796
+ type = "search",
797
+ ...rest
798
+ }) {
799
+ ensureStyle(STYLE_ID6, RULES6);
800
+ const fieldStyle = {
801
+ "--nb-search-font": tokens.fontSans,
802
+ "--nb-search-fg": tokens.fg,
803
+ "--nb-search-placeholder": tokens.fgFaint,
804
+ "--nb-search-accent": tokens.accent,
805
+ "--nb-search-border": tokens.border,
806
+ "--nb-search-bg": tokens.bgSubtle,
807
+ "--nb-search-radius": tokens.radiusSm,
808
+ fontSize: tokens.textSmSize,
809
+ lineHeight: tokens.textSmLine,
810
+ ...style
811
+ };
812
+ return /* @__PURE__ */ jsxRuntime.jsx(
813
+ "input",
814
+ {
815
+ type,
816
+ className: `nb-search nb-search--${variant} ${className ?? ""}`.trim(),
817
+ style: fieldStyle,
818
+ ...rest
819
+ }
820
+ );
821
+ }
822
+ var STYLE_ID7 = "nb-synapse-segmented";
823
+ var RULES7 = `
824
+ .nb-seg {
825
+ display: inline-flex;
826
+ gap: 2px;
827
+ padding: 2px;
828
+ border-radius: var(--nb-seg-radius);
829
+ background: var(--nb-seg-track);
830
+ }
831
+ .nb-seg__btn {
832
+ border: none;
833
+ background: transparent;
834
+ color: var(--nb-seg-fg);
835
+ font-family: var(--nb-seg-font);
836
+ font-weight: var(--nb-seg-weight);
837
+ padding: 0.25rem 0.7rem;
838
+ border-radius: calc(var(--nb-seg-radius) - 2px);
839
+ cursor: pointer;
840
+ transition: color 130ms ease, background 130ms ease;
841
+ white-space: nowrap;
842
+ }
843
+ .nb-seg__btn:hover:not([aria-pressed="true"]) { color: var(--nb-seg-fg-strong); }
844
+ .nb-seg__btn[aria-pressed="true"] {
845
+ background: var(--nb-seg-active-bg);
846
+ color: var(--nb-seg-fg-strong);
847
+ box-shadow: var(--nb-seg-active-shadow);
848
+ }
849
+ `;
850
+ function SegmentedControl({
851
+ options,
852
+ value,
853
+ onChange,
854
+ style,
855
+ className,
856
+ ...rest
857
+ }) {
858
+ ensureStyle(STYLE_ID7, RULES7);
859
+ const trackStyle = {
860
+ "--nb-seg-radius": tokens.radiusSm,
861
+ "--nb-seg-track": tokens.bgSubtle,
862
+ "--nb-seg-fg": tokens.fgMuted,
863
+ "--nb-seg-fg-strong": tokens.fg,
864
+ "--nb-seg-font": tokens.fontSans,
865
+ "--nb-seg-weight": tokens.weightMedium,
866
+ "--nb-seg-active-bg": tokens.bgRaised,
867
+ "--nb-seg-active-shadow": tokens.shadowSm,
868
+ fontSize: tokens.textSmSize,
869
+ lineHeight: tokens.textSmLine,
870
+ ...style
871
+ };
872
+ return (
873
+ // biome-ignore lint/a11y/useSemanticElements: role="group" with aria-label is the correct grouping for a set of toggle buttons; <fieldset> would impose form semantics this control doesn't have.
874
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `nb-seg ${className ?? ""}`.trim(), role: "group", style: trackStyle, ...rest, children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
875
+ "button",
876
+ {
877
+ type: "button",
878
+ className: "nb-seg__btn",
879
+ "aria-pressed": opt.value === value,
880
+ onClick: () => onChange(opt.value),
881
+ children: opt.label
882
+ },
883
+ opt.value
884
+ )) })
885
+ );
886
+ }
887
+ var STYLE_ID8 = "nb-synapse-spinner";
888
+ var RULES8 = `
889
+ @keyframes nb-spin { to { transform: rotate(360deg); } }
890
+ .nb-spinner { animation: nb-spin 0.7s linear infinite; }
891
+ @media (prefers-reduced-motion: reduce) { .nb-spinner { animation-duration: 1.6s; } }
892
+ `;
893
+ function Spinner({ size = 16, color, style, className, ...rest }) {
894
+ ensureStyle(STYLE_ID8, RULES8);
895
+ const spinnerStyle = {
896
+ display: "inline-block",
897
+ flexShrink: 0,
898
+ width: size,
899
+ height: size,
900
+ borderRadius: "50%",
901
+ border: `2px solid ${tokens.border}`,
902
+ borderTopColor: color ?? tokens.accent,
903
+ boxSizing: "border-box",
904
+ ...style
905
+ };
906
+ return /* @__PURE__ */ jsxRuntime.jsx(
907
+ "span",
908
+ {
909
+ className: `nb-spinner ${className ?? ""}`.trim(),
910
+ role: "status",
911
+ "aria-label": "Loading",
912
+ style: spinnerStyle,
913
+ ...rest
914
+ }
915
+ );
916
+ }
917
+ var COLOR = {
918
+ working: tokens.accent,
919
+ completed: tokens.success,
920
+ failed: tokens.danger,
921
+ idle: tokens.fgFaint
922
+ };
923
+ var STYLE_ID9 = "nb-synapse-statusdot";
924
+ var KEYFRAMES = `
925
+ @keyframes nb-statusdot-pulse {
926
+ 0% { box-shadow: 0 0 0 0 var(--nb-statusdot-ring); transform: scale(1); }
927
+ 70% { box-shadow: 0 0 0 7px transparent; transform: scale(1.15); }
928
+ 100% { box-shadow: 0 0 0 0 transparent; transform: scale(1); }
929
+ }
930
+ .nb-statusdot--working {
931
+ animation: nb-statusdot-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
932
+ }
933
+ @media (prefers-reduced-motion: reduce) {
934
+ .nb-statusdot--working { animation: none; box-shadow: 0 0 0 2px var(--nb-statusdot-ring); }
935
+ }
936
+ `;
937
+ function StatusDot({
938
+ status = "idle",
939
+ color,
940
+ size = 8,
941
+ style,
942
+ className,
943
+ ...rest
944
+ }) {
945
+ ensureStyle(STYLE_ID9, KEYFRAMES);
946
+ const dotColor = color ?? COLOR[status];
947
+ const working = status === "working";
948
+ const dotStyle = {
949
+ display: "inline-block",
950
+ flexShrink: 0,
951
+ width: size,
952
+ height: size,
953
+ borderRadius: "50%",
954
+ background: dotColor,
955
+ // Ring color for the pulse keyframe — a faded copy of the dot color.
956
+ "--nb-statusdot-ring": `color-mix(in oklab, ${dotColor} 45%, transparent)`,
957
+ ...style
958
+ };
959
+ return /* @__PURE__ */ jsxRuntime.jsx(
960
+ "span",
961
+ {
962
+ className: working ? `nb-statusdot--working ${className ?? ""}`.trim() : className,
963
+ style: dotStyle,
964
+ ...rest
965
+ }
966
+ );
967
+ }
968
+ var STYLE_ID10 = "nb-synapse-table";
969
+ var RULES9 = `
970
+ .nb-table { width: 100%; border-collapse: collapse; font-family: var(--nb-table-font); }
971
+ .nb-table th, .nb-table td {
972
+ padding: 0.55rem 0.75rem;
973
+ border-bottom: var(--nb-table-border);
974
+ font-size: var(--nb-table-size); line-height: var(--nb-table-line);
975
+ }
976
+ .nb-table thead th {
977
+ position: sticky; top: 0; z-index: 1;
978
+ background: var(--nb-table-head-bg);
979
+ color: var(--nb-table-muted);
980
+ font-weight: var(--nb-table-semibold);
981
+ font-size: var(--nb-table-xs); text-align: left;
982
+ text-transform: uppercase; letter-spacing: 0.04em;
983
+ }
984
+ .nb-table tbody td { color: var(--nb-table-fg); }
985
+ .nb-table tbody tr.nb-table__row--clickable { cursor: pointer; }
986
+ .nb-table tbody tr.nb-table__row--clickable:hover { background: var(--nb-table-hover); }
987
+ `;
988
+ function Table({
989
+ data,
990
+ columns,
991
+ rowKey,
992
+ onRowClick,
993
+ empty,
994
+ style,
995
+ className,
996
+ ...rest
997
+ }) {
998
+ ensureStyle(STYLE_ID10, RULES9);
999
+ const vars = {
1000
+ "--nb-table-font": tokens.fontSans,
1001
+ "--nb-table-fg": tokens.fg,
1002
+ "--nb-table-muted": tokens.fgMuted,
1003
+ "--nb-table-head-bg": tokens.bg,
1004
+ "--nb-table-hover": tokens.bgSubtle,
1005
+ "--nb-table-border": `${tokens.borderWidth} solid ${tokens.border}`,
1006
+ "--nb-table-size": tokens.textSmSize,
1007
+ "--nb-table-line": tokens.textSmLine,
1008
+ "--nb-table-xs": tokens.textXsSize,
1009
+ "--nb-table-semibold": tokens.weightSemibold
1010
+ };
1011
+ if (data.length === 0 && empty !== void 0) {
1012
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: empty });
1013
+ }
1014
+ const tableStyle = { ...vars, ...style };
1015
+ return /* @__PURE__ */ jsxRuntime.jsxs("table", { className: `nb-table ${className ?? ""}`.trim(), style: tableStyle, ...rest, children: [
1016
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
1017
+ "th",
1018
+ {
1019
+ scope: "col",
1020
+ style: { width: col.width, textAlign: col.align ?? "left" },
1021
+ children: col.header
1022
+ },
1023
+ col.key
1024
+ )) }) }),
1025
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: data.map((row, i) => {
1026
+ const clickable = Boolean(onRowClick);
1027
+ return /* @__PURE__ */ jsxRuntime.jsx(
1028
+ "tr",
1029
+ {
1030
+ className: clickable ? "nb-table__row--clickable" : void 0,
1031
+ tabIndex: clickable ? 0 : void 0,
1032
+ onClick: clickable ? () => onRowClick?.(row, i) : void 0,
1033
+ onKeyDown: clickable ? (e) => {
1034
+ if (e.key === "Enter" || e.key === " ") {
1035
+ e.preventDefault();
1036
+ onRowClick?.(row, i);
1037
+ }
1038
+ } : void 0,
1039
+ children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { textAlign: col.align ?? "left" }, children: col.render(row, i) }, col.key))
1040
+ },
1041
+ rowKey(row, i)
1042
+ );
1043
+ }) })
1044
+ ] });
1045
+ }
1046
+ var READING_MAX = 760;
1047
+ var ContentWidthCtx = react.createContext("full");
1048
+ function columnStyle(width) {
1049
+ return width === "reading" ? { maxWidth: READING_MAX, marginInline: "auto", width: "100%" } : {};
1050
+ }
1051
+ function AppFrameRoot({ contentWidth = "full", style, children, ...rest }) {
1052
+ return /* @__PURE__ */ jsxRuntime.jsx(ContentWidthCtx.Provider, { value: contentWidth, children: /* @__PURE__ */ jsxRuntime.jsx(
1053
+ "div",
1054
+ {
1055
+ style: {
1056
+ display: "flex",
1057
+ flexDirection: "column",
1058
+ height: "100%",
1059
+ minHeight: 0,
1060
+ background: tokens.bg,
1061
+ color: tokens.fg,
1062
+ fontFamily: tokens.fontSans,
1063
+ ...style
1064
+ },
1065
+ ...rest,
1066
+ children
1067
+ }
1068
+ ) });
1069
+ }
1070
+ function Header2({ style, children, ...rest }) {
1071
+ const width = react.useContext(ContentWidthCtx);
1072
+ return /* @__PURE__ */ jsxRuntime.jsx("header", { style: { flexShrink: 0, padding: "1.25rem 1.5rem 0.75rem", ...style }, ...rest, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: columnStyle(width), children }) });
1073
+ }
1074
+ function Body2({ bleed = false, style, children, ...rest }) {
1075
+ const width = react.useContext(ContentWidthCtx);
1076
+ if (bleed) {
1077
+ return /* @__PURE__ */ jsxRuntime.jsx(
1078
+ "div",
1079
+ {
1080
+ style: {
1081
+ flex: 1,
1082
+ minHeight: 0,
1083
+ display: "flex",
1084
+ flexDirection: "column",
1085
+ overflow: "hidden",
1086
+ ...style
1087
+ },
1088
+ ...rest,
1089
+ children
1090
+ }
1091
+ );
1092
+ }
1093
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minHeight: 0, overflowY: "auto", ...style }, ...rest, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...columnStyle(width), padding: "0.75rem 1.5rem 1.5rem" }, children }) });
1094
+ }
1095
+ function Footer2({ style, children, ...rest }) {
1096
+ const width = react.useContext(ContentWidthCtx);
1097
+ return /* @__PURE__ */ jsxRuntime.jsx(
1098
+ "footer",
1099
+ {
1100
+ style: {
1101
+ flexShrink: 0,
1102
+ padding: "0.75rem 1.5rem",
1103
+ borderTop: `${tokens.borderWidth} solid ${tokens.border}`,
1104
+ ...style
1105
+ },
1106
+ ...rest,
1107
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: columnStyle(width), children })
1108
+ }
1109
+ );
1110
+ }
1111
+ var AppFrame = Object.assign(AppFrameRoot, { Header: Header2, Body: Body2, Footer: Footer2 });
1112
+ function useBreakpoint(breakpoint) {
1113
+ const ref = react.useRef(null);
1114
+ const [width, setWidth] = react.useState(null);
1115
+ react.useLayoutEffect(() => {
1116
+ const el = ref.current;
1117
+ if (!el) return;
1118
+ setWidth(el.getBoundingClientRect().width);
1119
+ if (typeof ResizeObserver === "undefined") return;
1120
+ const ro = new ResizeObserver((entries) => {
1121
+ const entry = entries[0];
1122
+ const w = entry?.borderBoxSize?.[0]?.inlineSize ?? entry?.contentRect.width;
1123
+ if (typeof w === "number") setWidth(w);
1124
+ });
1125
+ ro.observe(el);
1126
+ return () => ro.disconnect();
1127
+ }, []);
1128
+ return { ref, isNarrow: width !== null && width < breakpoint };
1129
+ }
1130
+ var ListDetailCtx = react.createContext(null);
1131
+ function useListDetail() {
1132
+ const ctx = react.useContext(ListDetailCtx);
1133
+ if (!ctx) throw new Error("useListDetail must be used within <ListDetailLayout>");
1134
+ return ctx;
1135
+ }
1136
+ function ListDetailLayoutRoot({
1137
+ selected = false,
1138
+ onBack,
1139
+ listWidth = 320,
1140
+ breakpoint = 720,
1141
+ style,
1142
+ children,
1143
+ ...rest
1144
+ }) {
1145
+ const { ref, isNarrow } = useBreakpoint(breakpoint);
1146
+ return /* @__PURE__ */ jsxRuntime.jsx(ListDetailCtx.Provider, { value: { collapsed: isNarrow, selected, listWidth, onBack }, children: /* @__PURE__ */ jsxRuntime.jsx(
1147
+ "div",
1148
+ {
1149
+ ref,
1150
+ style: { display: "flex", flexDirection: "row", height: "100%", minHeight: 0, ...style },
1151
+ ...rest,
1152
+ children
1153
+ }
1154
+ ) });
1155
+ }
1156
+ function List({ style, children, ...rest }) {
1157
+ const { collapsed, selected, listWidth } = useListDetail();
1158
+ if (collapsed && selected) return null;
1159
+ const border = `${tokens.borderWidth} solid ${tokens.border}`;
1160
+ return /* @__PURE__ */ jsxRuntime.jsx(
1161
+ "section",
1162
+ {
1163
+ style: {
1164
+ overflowY: "auto",
1165
+ ...collapsed ? { flex: 1, minWidth: 0 } : { width: listWidth, flexShrink: 0, borderRight: border },
1166
+ ...style
1167
+ },
1168
+ ...rest,
1169
+ children
1170
+ }
1171
+ );
1172
+ }
1173
+ function Detail({ style, children, ...rest }) {
1174
+ const { collapsed, selected } = useListDetail();
1175
+ if (collapsed && !selected) return null;
1176
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minWidth: 0, minHeight: 0, overflowY: "auto", ...style }, ...rest, children });
1177
+ }
1178
+ function Back({ children, style }) {
1179
+ const { collapsed, selected, onBack } = useListDetail();
1180
+ if (!collapsed || !selected) return null;
1181
+ return /* @__PURE__ */ jsxRuntime.jsx(TextLink, { onClick: () => onBack?.(), style: { marginBottom: "0.5rem", ...style }, children: children ?? "\u2190 Back" });
1182
+ }
1183
+ var ListDetailLayout = Object.assign(ListDetailLayoutRoot, { List, Detail, Back });
1184
+ var SidebarCtx = react.createContext(null);
1185
+ function useSidebar() {
1186
+ const ctx = react.useContext(SidebarCtx);
1187
+ if (!ctx) throw new Error("useSidebar must be used within <SidebarLayout>");
1188
+ return ctx;
1189
+ }
1190
+ var STYLE_ID11 = "nb-synapse-sidebarlayout";
1191
+ var RULES10 = `
1192
+ .nb-sidebar-drawer { transition: transform 220ms cubic-bezier(0.2, 0, 0, 1); }
1193
+ .nb-sidebar-scrim { animation: nb-sidebar-fade 180ms ease; }
1194
+ @keyframes nb-sidebar-fade { from { opacity: 0; } to { opacity: 1; } }
1195
+ @media (prefers-reduced-motion: reduce) {
1196
+ .nb-sidebar-drawer { transition: none; }
1197
+ .nb-sidebar-scrim { animation: none; }
1198
+ }
1199
+ `;
1200
+ function SidebarLayoutRoot({
1201
+ side = "left",
1202
+ width = 240,
1203
+ breakpoint = 640,
1204
+ collapseMode = "reflow",
1205
+ style,
1206
+ children,
1207
+ ...rest
1208
+ }) {
1209
+ const { ref, isNarrow } = useBreakpoint(breakpoint);
1210
+ const [open, setOpen] = react.useState(false);
1211
+ react.useEffect(() => {
1212
+ if (!open) return;
1213
+ const onKey = (e) => {
1214
+ if (e.key === "Escape") setOpen(false);
1215
+ };
1216
+ window.addEventListener("keydown", onKey);
1217
+ return () => window.removeEventListener("keydown", onKey);
1218
+ }, [open]);
1219
+ react.useEffect(() => {
1220
+ if (!isNarrow && open) setOpen(false);
1221
+ }, [isNarrow, open]);
1222
+ const reflowed = isNarrow && collapseMode === "reflow";
1223
+ const rootStyle = {
1224
+ position: "relative",
1225
+ display: "flex",
1226
+ flexDirection: reflowed ? "column" : side === "right" ? "row-reverse" : "row",
1227
+ height: "100%",
1228
+ minHeight: 0,
1229
+ ...style
1230
+ };
1231
+ return /* @__PURE__ */ jsxRuntime.jsx(
1232
+ SidebarCtx.Provider,
1233
+ {
1234
+ value: { collapsed: isNarrow, mode: collapseMode, side, width, open, setOpen },
1235
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref, style: rootStyle, ...rest, children })
1236
+ }
1237
+ );
1238
+ }
1239
+ function Sidebar({ style, children, ...rest }) {
1240
+ const { collapsed, mode, side, width, open, setOpen } = useSidebar();
1241
+ ensureStyle(STYLE_ID11, RULES10);
1242
+ if (!collapsed) {
1243
+ const border = `${tokens.borderWidth} solid ${tokens.border}`;
1244
+ return /* @__PURE__ */ jsxRuntime.jsx(
1245
+ "aside",
1246
+ {
1247
+ style: {
1248
+ width,
1249
+ flexShrink: 0,
1250
+ overflowY: "auto",
1251
+ display: "flex",
1252
+ flexDirection: "column",
1253
+ gap: "0.125rem",
1254
+ padding: "0.5rem 0.5rem",
1255
+ ...side === "right" ? { borderLeft: border } : { borderRight: border },
1256
+ ...style
1257
+ },
1258
+ ...rest,
1259
+ children
1260
+ }
1261
+ );
1262
+ }
1263
+ if (mode === "reflow") {
1264
+ return /* @__PURE__ */ jsxRuntime.jsx(
1265
+ "aside",
1266
+ {
1267
+ style: {
1268
+ display: "flex",
1269
+ flexDirection: "row",
1270
+ alignItems: "center",
1271
+ gap: "0.25rem",
1272
+ width: "100%",
1273
+ overflowX: "auto",
1274
+ padding: "0.5rem 0.75rem",
1275
+ borderBottom: `${tokens.borderWidth} solid ${tokens.border}`,
1276
+ ...style
1277
+ },
1278
+ ...rest,
1279
+ children
1280
+ }
1281
+ );
1282
+ }
1283
+ const closed = side === "right" ? "translateX(100%)" : "translateX(-100%)";
1284
+ const drawerStyle = {
1285
+ position: "absolute",
1286
+ top: 0,
1287
+ bottom: 0,
1288
+ [side]: 0,
1289
+ width,
1290
+ maxWidth: "85%",
1291
+ overflowY: "auto",
1292
+ background: tokens.bgRaised,
1293
+ boxShadow: tokens.shadowLg,
1294
+ transform: open ? "translateX(0)" : closed,
1295
+ zIndex: 11,
1296
+ ...side === "right" ? { borderLeft: `${tokens.borderWidth} solid ${tokens.border}` } : { borderRight: `${tokens.borderWidth} solid ${tokens.border}` },
1297
+ ...style
1298
+ };
1299
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1300
+ open ? /* @__PURE__ */ jsxRuntime.jsx(
1301
+ "button",
1302
+ {
1303
+ type: "button",
1304
+ "aria-label": "Close sidebar",
1305
+ className: "nb-sidebar-scrim",
1306
+ onClick: () => setOpen(false),
1307
+ style: {
1308
+ position: "absolute",
1309
+ inset: 0,
1310
+ border: "none",
1311
+ padding: 0,
1312
+ cursor: "pointer",
1313
+ background: "rgba(0,0,0,0.32)",
1314
+ zIndex: 10
1315
+ }
1316
+ }
1317
+ ) : null,
1318
+ /* @__PURE__ */ jsxRuntime.jsx("aside", { className: "nb-sidebar-drawer", inert: !open, style: drawerStyle, ...rest, children })
1319
+ ] });
1320
+ }
1321
+ function Main({ style, children, ...rest }) {
1322
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minWidth: 0, minHeight: 0, overflowY: "auto", ...style }, ...rest, children });
1323
+ }
1324
+ function Trigger({ children, style, onClick, ...rest }) {
1325
+ const ctx = react.useContext(SidebarCtx);
1326
+ if (!ctx?.collapsed || ctx.mode !== "drawer") return null;
1327
+ const { open, setOpen } = ctx;
1328
+ return /* @__PURE__ */ jsxRuntime.jsx(
1329
+ "button",
1330
+ {
1331
+ type: "button",
1332
+ "aria-label": "Toggle sidebar",
1333
+ "aria-expanded": open,
1334
+ onClick: (e) => {
1335
+ setOpen(!open);
1336
+ onClick?.(e);
1337
+ },
1338
+ style: {
1339
+ display: "inline-flex",
1340
+ alignItems: "center",
1341
+ justifyContent: "center",
1342
+ width: 32,
1343
+ height: 32,
1344
+ border: "none",
1345
+ background: "transparent",
1346
+ color: tokens.fg,
1347
+ cursor: "pointer",
1348
+ borderRadius: tokens.radiusSm,
1349
+ ...style
1350
+ },
1351
+ ...rest,
1352
+ children: children ?? /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 18 18", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
1353
+ "path",
1354
+ {
1355
+ d: "M2 4.5h14M2 9h14M2 13.5h14",
1356
+ stroke: "currentColor",
1357
+ strokeWidth: "1.5",
1358
+ strokeLinecap: "round"
1359
+ }
1360
+ ) })
1361
+ }
1362
+ );
1363
+ }
1364
+ var SidebarLayout = Object.assign(SidebarLayoutRoot, { Sidebar, Main, Trigger });
1365
+
1366
+ exports.AppFrame = AppFrame;
1367
+ exports.Avatar = Avatar;
1368
+ exports.Badge = Badge;
1369
+ exports.Button = Button;
1370
+ exports.Card = Card;
1371
+ exports.Divider = Divider;
1372
+ exports.Drawer = Drawer;
1373
+ exports.EmptyState = EmptyState;
1374
+ exports.Heading = Heading;
1375
+ exports.Inline = Inline;
1376
+ exports.ListDetailLayout = ListDetailLayout;
1377
+ exports.ListRow = ListRow;
1378
+ exports.Pagination = Pagination;
1379
+ exports.Prose = Prose;
1380
+ exports.SearchField = SearchField;
1381
+ exports.SegmentedControl = SegmentedControl;
1382
+ exports.SidebarLayout = SidebarLayout;
1383
+ exports.Spacer = Spacer;
1384
+ exports.Spinner = Spinner;
1385
+ exports.Stack = Stack;
1386
+ exports.StatusDot = StatusDot;
1387
+ exports.Table = Table;
1388
+ exports.Text = Text;
1389
+ exports.TextLink = TextLink;
1390
+ exports.headingStyle = headingStyle;
1391
+ exports.textStyle = textStyle;
1392
+ exports.tokens = tokens;
1393
+ exports.useBreakpoint = useBreakpoint;
1394
+ exports.useListDetail = useListDetail;
1395
+ exports.useSidebar = useSidebar;
1396
+ //# sourceMappingURL=index.cjs.map
1397
+ //# sourceMappingURL=index.cjs.map