@nimblebrain/synapse 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/index.cjs CHANGED
@@ -324,14 +324,25 @@ var RULES3 = `
324
324
  }
325
325
  .nb-drawer--right { margin-left: auto; }
326
326
  .nb-drawer--left { margin-right: auto; }
327
+ .nb-drawer--bottom {
328
+ margin-top: auto; width: 100%; max-width: 100%;
329
+ height: auto; max-height: 92%;
330
+ }
327
331
  .nb-drawer--right[open] { animation: nb-drawer-in-right 240ms cubic-bezier(0.2, 0, 0, 1); }
328
332
  .nb-drawer--left[open] { animation: nb-drawer-in-left 240ms cubic-bezier(0.2, 0, 0, 1); }
333
+ .nb-drawer--bottom[open] { animation: nb-drawer-in-bottom 240ms cubic-bezier(0.2, 0, 0, 1); }
329
334
  .nb-drawer::backdrop { background: rgba(0, 0, 0, 0.32); }
330
335
  .nb-drawer[open]::backdrop { animation: nb-drawer-fade 200ms ease; }
331
336
  @keyframes nb-drawer-in-right { from { transform: translateX(100%); } }
332
337
  @keyframes nb-drawer-in-left { from { transform: translateX(-100%); } }
338
+ @keyframes nb-drawer-in-bottom { from { transform: translateY(100%); } }
333
339
  @keyframes nb-drawer-fade { from { opacity: 0; } }
340
+ .nb-drawer-iconbtn { width: 28px; height: 28px; }
341
+ @media (pointer: coarse) {
342
+ .nb-drawer-iconbtn { min-width: 44px; min-height: 44px; }
343
+ }
334
344
  `;
345
+ var DrawerContext = react.createContext(null);
335
346
  function DrawerRoot({
336
347
  open,
337
348
  onClose,
@@ -344,42 +355,51 @@ function DrawerRoot({
344
355
  }) {
345
356
  ensureStyle(STYLE_ID3, RULES3);
346
357
  const dialogRef = react.useRef(null);
358
+ const labelId = react.useId();
359
+ const [hasTitle, setHasTitle] = react.useState(false);
347
360
  react.useEffect(() => {
348
361
  const dialog = dialogRef.current;
349
362
  if (!dialog || typeof dialog.showModal !== "function") return;
350
363
  if (open && !dialog.open) dialog.showModal();
351
364
  else if (!open && dialog.open) dialog.close();
352
365
  }, [open]);
366
+ const isBottom = side === "bottom";
353
367
  const panelStyle = {
354
- width,
368
+ ...isBottom ? { width: "100%", maxWidth: "100%" } : { width },
355
369
  background: tokens.bg,
356
370
  color: tokens.fg,
357
371
  boxShadow: tokens.shadowLg,
358
- ...side === "right" ? { borderLeft: `${tokens.borderWidth} solid ${tokens.border}` } : { borderRight: `${tokens.borderWidth} solid ${tokens.border}` },
372
+ ...side === "right" ? { borderLeft: `${tokens.borderWidth} solid ${tokens.border}` } : side === "left" ? { borderRight: `${tokens.borderWidth} solid ${tokens.border}` } : { borderTop: `${tokens.borderWidth} solid ${tokens.border}` },
359
373
  ...style
360
374
  };
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
- );
375
+ const ctxValue = react.useMemo(() => ({ labelId, setHasTitle }), [labelId]);
376
+ return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxRuntime.jsx(
377
+ "dialog",
378
+ {
379
+ ref: dialogRef,
380
+ className: `nb-drawer nb-drawer--${side}`,
381
+ style: panelStyle,
382
+ "aria-labelledby": hasTitle ? labelId : void 0,
383
+ onCancel: (e) => {
384
+ e.preventDefault();
385
+ (onEscape ?? onClose)();
386
+ },
387
+ onClick: (e) => {
388
+ if (e.target === dialogRef.current) onClose();
389
+ },
390
+ ...rest,
391
+ children
392
+ }
393
+ ) });
381
394
  }
382
- function Header({ onClose, style, children, ...rest }) {
395
+ function Header({ title, onBack, actions, onClose, style, children, ...rest }) {
396
+ const ctx = react.useContext(DrawerContext);
397
+ const hasTitle = title != null;
398
+ react.useEffect(() => {
399
+ if (!ctx || !hasTitle) return;
400
+ ctx.setHasTitle(true);
401
+ return () => ctx.setHasTitle(false);
402
+ }, [ctx, hasTitle]);
383
403
  return /* @__PURE__ */ jsxRuntime.jsxs(
384
404
  "header",
385
405
  {
@@ -397,41 +417,75 @@ function Header({ onClose, style, children, ...rest }) {
397
417
  },
398
418
  ...rest,
399
419
  children: [
400
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minWidth: 0 }, children }),
401
- onClose ? /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClose }) : null
420
+ onBack ? /* @__PURE__ */ jsxRuntime.jsx(IconButton, { label: "Back", onClick: onBack, children: /* @__PURE__ */ jsxRuntime.jsx(
421
+ "path",
422
+ {
423
+ d: "M10 3L5 8l5 5",
424
+ stroke: "currentColor",
425
+ strokeWidth: "1.5",
426
+ fill: "none",
427
+ strokeLinecap: "round",
428
+ strokeLinejoin: "round"
429
+ }
430
+ ) }) : null,
431
+ hasTitle ? /* @__PURE__ */ jsxRuntime.jsx(
432
+ "h2",
433
+ {
434
+ id: ctx?.labelId,
435
+ style: {
436
+ flex: 1,
437
+ minWidth: 0,
438
+ margin: 0,
439
+ fontFamily: tokens.fontSans,
440
+ fontSize: tokens.textBaseSize,
441
+ lineHeight: tokens.textBaseLine,
442
+ fontWeight: tokens.weightSemibold,
443
+ color: tokens.fg,
444
+ whiteSpace: "nowrap",
445
+ overflow: "hidden",
446
+ textOverflow: "ellipsis"
447
+ },
448
+ children: title
449
+ }
450
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minWidth: 0 }, children }),
451
+ actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: "0.4rem", flexShrink: 0 }, children: actions }) : null,
452
+ onClose ? /* @__PURE__ */ jsxRuntime.jsx(IconButton, { label: "Close", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
453
+ "path",
454
+ {
455
+ d: "M4 4l8 8M12 4l-8 8",
456
+ stroke: "currentColor",
457
+ strokeWidth: "1.5",
458
+ strokeLinecap: "round"
459
+ }
460
+ ) }) : null
402
461
  ]
403
462
  }
404
463
  );
405
464
  }
406
- function CloseButton({ onClose }) {
465
+ function IconButton({
466
+ label,
467
+ onClick,
468
+ children
469
+ }) {
407
470
  return /* @__PURE__ */ jsxRuntime.jsx(
408
471
  "button",
409
472
  {
410
473
  type: "button",
411
- "aria-label": "Close",
412
- onClick: onClose,
474
+ "aria-label": label,
475
+ onClick,
476
+ className: "nb-drawer-iconbtn",
413
477
  style: {
414
478
  flexShrink: 0,
415
479
  display: "inline-flex",
416
480
  alignItems: "center",
417
481
  justifyContent: "center",
418
- width: 28,
419
- height: 28,
420
482
  border: "none",
421
483
  background: "transparent",
422
484
  color: tokens.fgMuted,
423
485
  cursor: "pointer",
424
486
  borderRadius: tokens.radiusSm
425
487
  },
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
- ) })
488
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children })
435
489
  }
436
490
  );
437
491
  }
@@ -1161,8 +1215,13 @@ function List({ style, children, ...rest }) {
1161
1215
  "section",
1162
1216
  {
1163
1217
  style: {
1164
- overflowY: "auto",
1165
- ...collapsed ? { flex: 1, minWidth: 0 } : { width: listWidth, flexShrink: 0, borderRight: border },
1218
+ // Clip horizontally, scroll vertically. A too-wide child (e.g. an
1219
+ // auto-layout <table> that won't shrink below its content) would
1220
+ // otherwise spill out of this fixed-width rail and paint over the
1221
+ // detail pane. `minWidth: 0` lets the rail participate in flex
1222
+ // sizing without being forced wider by its content.
1223
+ overflow: "hidden auto",
1224
+ ...collapsed ? { flex: 1, minWidth: 0 } : { width: listWidth, minWidth: 0, flexShrink: 0, borderRight: border },
1166
1225
  ...style
1167
1226
  },
1168
1227
  ...rest,