@nimblebrain/synapse 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +61 -111
  2. package/dist/{chunk-SN7YXLAV.cjs → chunk-EHLQWCYV.cjs} +6 -6
  3. package/dist/{chunk-SN7YXLAV.cjs.map → chunk-EHLQWCYV.cjs.map} +1 -1
  4. package/dist/{chunk-ZMNYUGSF.cjs → chunk-FDMRJZKT.cjs} +576 -662
  5. package/dist/chunk-FDMRJZKT.cjs.map +1 -0
  6. package/dist/{chunk-E7OOOYVR.js → chunk-SRQNQ26I.js} +3 -3
  7. package/dist/{chunk-E7OOOYVR.js.map → chunk-SRQNQ26I.js.map} +1 -1
  8. package/dist/{chunk-RYWW6EHB.cjs → chunk-TXJMGQYF.cjs} +3 -5
  9. package/dist/chunk-TXJMGQYF.cjs.map +1 -0
  10. package/dist/{chunk-J5ICAHK2.js → chunk-UPDBDXQB.js} +4 -5
  11. package/dist/chunk-UPDBDXQB.js.map +1 -0
  12. package/dist/{chunk-GNTQJSQX.js → chunk-VYR6V47N.js} +573 -663
  13. package/dist/chunk-VYR6V47N.js.map +1 -0
  14. package/dist/codegen/cli.cjs +1 -1
  15. package/dist/codegen/cli.js +1 -1
  16. package/dist/codegen/index.d.cts +1 -1
  17. package/dist/codegen/index.d.ts +1 -1
  18. package/dist/connect.iife.global.js +28 -28
  19. package/dist/{detect-C_qmYsh9.d.cts → detect-BHYg26_d.d.cts} +1 -1
  20. package/dist/{detect-Dfd0upoz.d.ts → detect-Bf8Q_0dK.d.ts} +1 -1
  21. package/dist/host/index.cjs +14 -14
  22. package/dist/host/index.d.cts +3 -3
  23. package/dist/host/index.d.ts +3 -3
  24. package/dist/host/index.js +2 -2
  25. package/dist/index.cjs +28 -87
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +58 -26
  28. package/dist/index.d.ts +58 -26
  29. package/dist/index.js +3 -81
  30. package/dist/index.js.map +1 -1
  31. package/dist/react/index.cjs +81 -140
  32. package/dist/react/index.cjs.map +1 -1
  33. package/dist/react/index.d.cts +66 -82
  34. package/dist/react/index.d.ts +66 -82
  35. package/dist/react/index.js +82 -136
  36. package/dist/react/index.js.map +1 -1
  37. package/dist/{server-ERRDGEZ7.js → server-5N76YCWC.js} +3 -3
  38. package/dist/{server-ERRDGEZ7.js.map → server-5N76YCWC.js.map} +1 -1
  39. package/dist/{server-P54IYX2G.cjs → server-QIYIJHD5.cjs} +3 -3
  40. package/dist/{server-P54IYX2G.cjs.map → server-QIYIJHD5.cjs.map} +1 -1
  41. package/dist/synapse-runtime.iife.global.js +28 -28
  42. package/dist/synapse-ui.iife.global.js +2 -2
  43. package/dist/{types-BSZasM8q.d.cts → types-uEO4VFJ2.d.cts} +87 -166
  44. package/dist/{types-BSZasM8q.d.ts → types-uEO4VFJ2.d.ts} +87 -166
  45. package/dist/ui/index.cjs +403 -91
  46. package/dist/ui/index.cjs.map +1 -1
  47. package/dist/ui/index.d.cts +115 -3
  48. package/dist/ui/index.d.ts +115 -3
  49. package/dist/ui/index.js +401 -93
  50. package/dist/ui/index.js.map +1 -1
  51. package/dist/vite/index.cjs +1 -1
  52. package/dist/vite/index.cjs.map +1 -1
  53. package/dist/vite/index.js +1 -1
  54. package/dist/vite/index.js.map +1 -1
  55. package/package.json +1 -1
  56. package/dist/chunk-GNTQJSQX.js.map +0 -1
  57. package/dist/chunk-J5ICAHK2.js.map +0 -1
  58. package/dist/chunk-RYWW6EHB.cjs.map +0 -1
  59. package/dist/chunk-ZMNYUGSF.cjs.map +0 -1
package/dist/ui/index.cjs CHANGED
@@ -162,6 +162,81 @@ function Badge({ tone = "neutral", style, children, ...rest }) {
162
162
  }
163
163
  );
164
164
  }
165
+ var TONE2 = {
166
+ default: tokens.fg,
167
+ muted: tokens.fgMuted,
168
+ faint: tokens.fgFaint,
169
+ accent: tokens.accent,
170
+ danger: tokens.danger,
171
+ success: tokens.success
172
+ };
173
+ var WEIGHT = {
174
+ normal: tokens.weightNormal,
175
+ medium: tokens.weightMedium,
176
+ semibold: tokens.weightSemibold,
177
+ bold: tokens.weightBold
178
+ };
179
+ function Text({
180
+ size = "base",
181
+ weight = "normal",
182
+ tone = "default",
183
+ mono = false,
184
+ truncate = false,
185
+ as: As = "span",
186
+ style,
187
+ children,
188
+ ...rest
189
+ }) {
190
+ const truncation = truncate ? {
191
+ display: "block",
192
+ minWidth: 0,
193
+ overflow: "hidden",
194
+ textOverflow: "ellipsis",
195
+ whiteSpace: "nowrap"
196
+ } : {};
197
+ return /* @__PURE__ */ jsxRuntime.jsx(
198
+ As,
199
+ {
200
+ style: {
201
+ margin: 0,
202
+ fontFamily: mono ? tokens.fontMono : tokens.fontSans,
203
+ fontWeight: WEIGHT[weight],
204
+ color: TONE2[tone],
205
+ ...textStyle(size),
206
+ ...truncation,
207
+ ...style
208
+ },
209
+ ...rest,
210
+ children
211
+ }
212
+ );
213
+ }
214
+ function Heading({
215
+ size = "md",
216
+ weight = "semibold",
217
+ tone = "default",
218
+ as: As = "h2",
219
+ style,
220
+ children,
221
+ ...rest
222
+ }) {
223
+ return /* @__PURE__ */ jsxRuntime.jsx(
224
+ As,
225
+ {
226
+ style: {
227
+ margin: 0,
228
+ fontFamily: tokens.fontHeading,
229
+ fontWeight: WEIGHT[weight],
230
+ color: TONE2[tone],
231
+ letterSpacing: "-0.015em",
232
+ ...headingStyle(size),
233
+ ...style
234
+ },
235
+ ...rest,
236
+ children
237
+ }
238
+ );
239
+ }
165
240
  var STYLE_ID = "nb-synapse-button";
166
241
  var RULES = `
167
242
  .nb-btn {
@@ -262,6 +337,41 @@ function TextLink({ tone = "muted", style, className, children, ...rest }) {
262
337
  }
263
338
  );
264
339
  }
340
+ function Breadcrumb({ crumbs, separator = "\u203A", style, ...rest }) {
341
+ return /* @__PURE__ */ jsxRuntime.jsx(
342
+ "nav",
343
+ {
344
+ "aria-label": "Breadcrumb",
345
+ style: {
346
+ display: "flex",
347
+ alignItems: "center",
348
+ gap: "0.4rem",
349
+ flexWrap: "wrap",
350
+ minWidth: 0,
351
+ ...style
352
+ },
353
+ ...rest,
354
+ children: crumbs.map((crumb, i) => {
355
+ const last = i === crumbs.length - 1;
356
+ return (
357
+ // biome-ignore lint/suspicious/noArrayIndexKey: a crumb's POSITION is its identity — "the second level" is what the step means, and two levels can legitimately carry the same label. There is no id to key on, and a trail that changed shape SHOULD remount rather than reconcile a step into a different depth.
358
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
359
+ last || !crumb.onClick ? /* @__PURE__ */ jsxRuntime.jsx(
360
+ Text,
361
+ {
362
+ size: "xs",
363
+ tone: last ? "muted" : "default",
364
+ "aria-current": last ? "page" : void 0,
365
+ children: crumb.label
366
+ }
367
+ ) : /* @__PURE__ */ jsxRuntime.jsx(TextLink, { onClick: crumb.onClick, style: { fontSize: tokens.textXsSize }, children: crumb.label }),
368
+ last ? null : /* @__PURE__ */ jsxRuntime.jsx(Text, { size: "xs", tone: "faint", "aria-hidden": "true", children: separator })
369
+ ] }, `${crumb.label}:${i}`)
370
+ );
371
+ })
372
+ }
373
+ );
374
+ }
265
375
  var STYLE_ID2 = "nb-synapse-card";
266
376
  var RULES2 = `
267
377
  .nb-card--interactive {
@@ -610,75 +720,6 @@ function Divider({ orientation = "horizontal", style, ...rest }) {
610
720
  }
611
721
  );
612
722
  }
613
- var TONE2 = {
614
- default: tokens.fg,
615
- muted: tokens.fgMuted,
616
- faint: tokens.fgFaint,
617
- accent: tokens.accent,
618
- danger: tokens.danger,
619
- success: tokens.success
620
- };
621
- var WEIGHT = {
622
- normal: tokens.weightNormal,
623
- medium: tokens.weightMedium,
624
- semibold: tokens.weightSemibold,
625
- bold: tokens.weightBold
626
- };
627
- function Text({
628
- size = "base",
629
- weight = "normal",
630
- tone = "default",
631
- mono = false,
632
- truncate = false,
633
- as: As = "span",
634
- style,
635
- children,
636
- ...rest
637
- }) {
638
- const truncation = truncate ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : {};
639
- return /* @__PURE__ */ jsxRuntime.jsx(
640
- As,
641
- {
642
- style: {
643
- margin: 0,
644
- fontFamily: mono ? tokens.fontMono : tokens.fontSans,
645
- fontWeight: WEIGHT[weight],
646
- color: TONE2[tone],
647
- ...textStyle(size),
648
- ...truncation,
649
- ...style
650
- },
651
- ...rest,
652
- children
653
- }
654
- );
655
- }
656
- function Heading({
657
- size = "md",
658
- weight = "semibold",
659
- tone = "default",
660
- as: As = "h2",
661
- style,
662
- children,
663
- ...rest
664
- }) {
665
- return /* @__PURE__ */ jsxRuntime.jsx(
666
- As,
667
- {
668
- style: {
669
- margin: 0,
670
- fontFamily: tokens.fontHeading,
671
- fontWeight: WEIGHT[weight],
672
- color: TONE2[tone],
673
- letterSpacing: "-0.015em",
674
- ...headingStyle(size),
675
- ...style
676
- },
677
- ...rest,
678
- children
679
- }
680
- );
681
- }
682
723
  function EmptyState({ icon, title, description, action, style, ...rest }) {
683
724
  return /* @__PURE__ */ jsxRuntime.jsxs(
684
725
  Stack,
@@ -800,6 +841,81 @@ function ListRow({
800
841
  }
801
842
  );
802
843
  }
844
+ var STYLE_ID5 = "nb-synapse-page-header";
845
+ var RULES5 = `
846
+ .nb-page-header__desc {
847
+ display: -webkit-box;
848
+ -webkit-box-orient: vertical;
849
+ -webkit-line-clamp: var(--nb-ph-desc-lines, 2);
850
+ line-clamp: var(--nb-ph-desc-lines, 2);
851
+ overflow: hidden;
852
+ }
853
+ `;
854
+ function PageHeader({
855
+ crumbs,
856
+ title,
857
+ status,
858
+ actions,
859
+ description,
860
+ descriptionLines = 2,
861
+ style,
862
+ ...rest
863
+ }) {
864
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID5, RULES5);
865
+ const descVars = descriptionLines > 0 ? { "--nb-ph-desc-lines": String(descriptionLines) } : {};
866
+ return /* @__PURE__ */ jsxRuntime.jsxs(
867
+ "header",
868
+ {
869
+ style: { display: "flex", flexDirection: "column", gap: "0.5rem", minWidth: 0, ...style },
870
+ ...rest,
871
+ children: [
872
+ crumbs && crumbs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Breadcrumb, { crumbs }) : null,
873
+ /* @__PURE__ */ jsxRuntime.jsxs(
874
+ "div",
875
+ {
876
+ style: {
877
+ display: "flex",
878
+ alignItems: "center",
879
+ justifyContent: "space-between",
880
+ gap: "1rem",
881
+ flexWrap: "wrap",
882
+ minWidth: 0
883
+ },
884
+ children: [
885
+ /* @__PURE__ */ jsxRuntime.jsxs(
886
+ "div",
887
+ {
888
+ style: {
889
+ display: "flex",
890
+ alignItems: "center",
891
+ gap: "0.6rem",
892
+ flexWrap: "wrap",
893
+ minWidth: 0
894
+ },
895
+ children: [
896
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { size: "md", children: title }),
897
+ status
898
+ ]
899
+ }
900
+ ),
901
+ actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", flexShrink: 0 }, children: actions }) : null
902
+ ]
903
+ }
904
+ ),
905
+ description ? /* @__PURE__ */ jsxRuntime.jsx(
906
+ Text,
907
+ {
908
+ size: "sm",
909
+ tone: "muted",
910
+ className: descriptionLines > 0 ? "nb-page-header__desc" : void 0,
911
+ style: { maxWidth: "72ch", ...descVars },
912
+ children: description
913
+ }
914
+ ) : null
915
+ ]
916
+ }
917
+ );
918
+ }
803
919
  function Pagination({ page, pageCount, onChange, style, ...rest }) {
804
920
  const total = Math.max(1, pageCount);
805
921
  const atStart = page <= 1;
@@ -831,8 +947,8 @@ function Pagination({ page, pageCount, onChange, style, ...rest }) {
831
947
  }
832
948
  );
833
949
  }
834
- var STYLE_ID5 = "nb-synapse-prose";
835
- var RULES5 = `
950
+ var STYLE_ID6 = "nb-synapse-prose";
951
+ var RULES6 = `
836
952
  .nb-prose {
837
953
  font-family: ${tokens.fontSans};
838
954
  font-size: ${tokens.textBaseSize};
@@ -857,15 +973,15 @@ var RULES5 = `
857
973
  .nb-prose strong { font-weight: ${tokens.weightSemibold}; }
858
974
  `;
859
975
  function Prose({ html, children, className, ...rest }) {
860
- chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID5, RULES5);
976
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID6, RULES6);
861
977
  const cls = `nb-prose ${className ?? ""}`.trim();
862
978
  if (html !== void 0) {
863
979
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, dangerouslySetInnerHTML: { __html: html }, ...rest });
864
980
  }
865
981
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, ...rest, children });
866
982
  }
867
- var STYLE_ID6 = "nb-synapse-searchfield";
868
- var RULES6 = `
983
+ var STYLE_ID7 = "nb-synapse-searchfield";
984
+ var RULES7 = `
869
985
  .nb-search { font-family: var(--nb-search-font); color: var(--nb-search-fg); outline: none; }
870
986
  .nb-search::placeholder { color: var(--nb-search-placeholder); }
871
987
  .nb-search--underline {
@@ -895,7 +1011,7 @@ function SearchField({
895
1011
  type = "search",
896
1012
  ...rest
897
1013
  }) {
898
- chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID6, RULES6);
1014
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID7, RULES7);
899
1015
  const fieldStyle = {
900
1016
  "--nb-search-font": tokens.fontSans,
901
1017
  "--nb-search-fg": tokens.fg,
@@ -918,8 +1034,8 @@ function SearchField({
918
1034
  }
919
1035
  );
920
1036
  }
921
- var STYLE_ID7 = "nb-synapse-segmented";
922
- var RULES7 = `
1037
+ var STYLE_ID8 = "nb-synapse-segmented";
1038
+ var RULES8 = `
923
1039
  .nb-seg {
924
1040
  display: inline-flex;
925
1041
  gap: 2px;
@@ -954,7 +1070,7 @@ function SegmentedControl({
954
1070
  className,
955
1071
  ...rest
956
1072
  }) {
957
- chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID7, RULES7);
1073
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID8, RULES8);
958
1074
  const trackStyle = {
959
1075
  "--nb-seg-radius": tokens.radiusSm,
960
1076
  "--nb-seg-track": tokens.bgSubtle,
@@ -983,14 +1099,14 @@ function SegmentedControl({
983
1099
  )) })
984
1100
  );
985
1101
  }
986
- var STYLE_ID8 = "nb-synapse-spinner";
987
- var RULES8 = `
1102
+ var STYLE_ID9 = "nb-synapse-spinner";
1103
+ var RULES9 = `
988
1104
  @keyframes nb-spin { to { transform: rotate(360deg); } }
989
1105
  .nb-spinner { animation: nb-spin 0.7s linear infinite; }
990
1106
  @media (prefers-reduced-motion: reduce) { .nb-spinner { animation-duration: 1.6s; } }
991
1107
  `;
992
1108
  function Spinner({ size = 16, color, style, className, ...rest }) {
993
- chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID8, RULES8);
1109
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID9, RULES9);
994
1110
  const spinnerStyle = {
995
1111
  display: "inline-block",
996
1112
  flexShrink: 0,
@@ -1019,7 +1135,7 @@ var COLOR = {
1019
1135
  failed: tokens.danger,
1020
1136
  idle: tokens.fgFaint
1021
1137
  };
1022
- var STYLE_ID9 = "nb-synapse-statusdot";
1138
+ var STYLE_ID10 = "nb-synapse-statusdot";
1023
1139
  var KEYFRAMES = `
1024
1140
  @keyframes nb-statusdot-pulse {
1025
1141
  0% { box-shadow: 0 0 0 0 var(--nb-statusdot-ring); transform: scale(1); }
@@ -1041,7 +1157,7 @@ function StatusDot({
1041
1157
  className,
1042
1158
  ...rest
1043
1159
  }) {
1044
- chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID9, KEYFRAMES);
1160
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID10, KEYFRAMES);
1045
1161
  const dotColor = color ?? COLOR[status];
1046
1162
  const working = status === "working";
1047
1163
  const dotStyle = {
@@ -1064,8 +1180,8 @@ function StatusDot({
1064
1180
  }
1065
1181
  );
1066
1182
  }
1067
- var STYLE_ID10 = "nb-synapse-table";
1068
- var RULES9 = `
1183
+ var STYLE_ID11 = "nb-synapse-table";
1184
+ var RULES10 = `
1069
1185
  .nb-table { width: 100%; border-collapse: collapse; font-family: var(--nb-table-font); }
1070
1186
  .nb-table th, .nb-table td {
1071
1187
  padding: 0.55rem 0.75rem;
@@ -1090,11 +1206,12 @@ function Table({
1090
1206
  rowKey,
1091
1207
  onRowClick,
1092
1208
  empty,
1209
+ minWidth,
1093
1210
  style,
1094
1211
  className,
1095
1212
  ...rest
1096
1213
  }) {
1097
- chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID10, RULES9);
1214
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID11, RULES10);
1098
1215
  const vars = {
1099
1216
  "--nb-table-font": tokens.fontSans,
1100
1217
  "--nb-table-fg": tokens.fg,
@@ -1110,8 +1227,14 @@ function Table({
1110
1227
  if (data.length === 0 && empty !== void 0) {
1111
1228
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: empty });
1112
1229
  }
1113
- const tableStyle = { ...vars, ...style };
1114
- return /* @__PURE__ */ jsxRuntime.jsxs("table", { className: `nb-table ${className ?? ""}`.trim(), style: tableStyle, ...rest, children: [
1230
+ const fixed = columns.some((col) => col.width !== void 0);
1231
+ const tableStyle = {
1232
+ ...vars,
1233
+ ...fixed ? { tableLayout: "fixed" } : {},
1234
+ ...minWidth !== void 0 ? { minWidth } : {},
1235
+ ...style
1236
+ };
1237
+ const table = /* @__PURE__ */ jsxRuntime.jsxs("table", { className: `nb-table ${className ?? ""}`.trim(), style: tableStyle, ...rest, children: [
1115
1238
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
1116
1239
  "th",
1117
1240
  {
@@ -1141,6 +1264,141 @@ function Table({
1141
1264
  );
1142
1265
  }) })
1143
1266
  ] });
1267
+ if (minWidth === void 0) return table;
1268
+ const label = rest["aria-label"];
1269
+ const named = label ? { role: "region", "aria-label": label } : {};
1270
+ return /* @__PURE__ */ jsxRuntime.jsx(
1271
+ "div",
1272
+ {
1273
+ tabIndex: 0,
1274
+ ...named,
1275
+ style: { overflowX: "auto", maxWidth: "100%" },
1276
+ children: table
1277
+ }
1278
+ );
1279
+ }
1280
+ var STYLE_ID12 = "nb-synapse-tabs";
1281
+ var RULES11 = `
1282
+ .nb-tabs {
1283
+ display: flex;
1284
+ align-items: stretch;
1285
+ gap: var(--nb-tabs-gap);
1286
+ border-bottom: var(--nb-tabs-rule);
1287
+ overflow-x: auto;
1288
+ scrollbar-width: none;
1289
+ }
1290
+ .nb-tabs::-webkit-scrollbar { display: none; }
1291
+ .nb-tabs__tab {
1292
+ border: none;
1293
+ background: transparent;
1294
+ color: var(--nb-tabs-fg);
1295
+ font-family: var(--nb-tabs-font);
1296
+ font-weight: var(--nb-tabs-weight);
1297
+ font-size: var(--nb-tabs-size);
1298
+ line-height: var(--nb-tabs-line);
1299
+ padding: 0.4rem 0.15rem 0.55rem;
1300
+ cursor: pointer;
1301
+ white-space: nowrap;
1302
+ position: relative;
1303
+ transition: color 130ms ease;
1304
+ }
1305
+ .nb-tabs__tab::after {
1306
+ content: "";
1307
+ position: absolute;
1308
+ /* bottom 0, not -1px. overflow-x auto makes the computed overflow-y auto as well, and
1309
+ clipping happens at the PADDING box - so a negative offset put 1px of the 2px accent
1310
+ outside it: the underline rendered half-height and left 1px of phantom overflow. */
1311
+ left: 0; right: 0; bottom: 0;
1312
+ height: 2px;
1313
+ border-radius: 2px 2px 0 0;
1314
+ background: transparent;
1315
+ transition: background 130ms ease;
1316
+ }
1317
+ .nb-tabs__tab:hover { color: var(--nb-tabs-fg-strong); }
1318
+ .nb-tabs__tab[aria-selected="true"] {
1319
+ color: var(--nb-tabs-fg-strong);
1320
+ font-weight: var(--nb-tabs-weight-active);
1321
+ }
1322
+ .nb-tabs__tab[aria-selected="true"]::after { background: var(--nb-tabs-accent); }
1323
+ .nb-tabs__tab:focus-visible {
1324
+ outline: 2px solid var(--nb-tabs-ring);
1325
+ outline-offset: -2px;
1326
+ border-radius: var(--nb-tabs-radius);
1327
+ }
1328
+ `;
1329
+ function Tabs({
1330
+ tabs,
1331
+ value,
1332
+ onChange,
1333
+ label,
1334
+ style,
1335
+ className,
1336
+ onKeyDown: callerKeyDown,
1337
+ ...rest
1338
+ }) {
1339
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID12, RULES11);
1340
+ const barRef = react.useRef(null);
1341
+ const barStyle = {
1342
+ "--nb-tabs-gap": "1.25rem",
1343
+ "--nb-tabs-rule": `${tokens.borderWidth} solid ${tokens.border}`,
1344
+ "--nb-tabs-fg": tokens.fgMuted,
1345
+ "--nb-tabs-fg-strong": tokens.fg,
1346
+ "--nb-tabs-accent": tokens.accent,
1347
+ "--nb-tabs-ring": tokens.ring,
1348
+ "--nb-tabs-font": tokens.fontSans,
1349
+ "--nb-tabs-weight": tokens.weightMedium,
1350
+ "--nb-tabs-weight-active": tokens.weightSemibold,
1351
+ "--nb-tabs-size": tokens.textSmSize,
1352
+ "--nb-tabs-line": tokens.textSmLine,
1353
+ "--nb-tabs-radius": tokens.radiusXs,
1354
+ ...style
1355
+ };
1356
+ const onKeyDown = (e) => {
1357
+ callerKeyDown?.(e);
1358
+ if (e.defaultPrevented) return;
1359
+ const at = tabs.findIndex((t) => t.value === value);
1360
+ if (at < 0) return;
1361
+ let to;
1362
+ if (e.key === "ArrowRight") to = (at + 1) % tabs.length;
1363
+ else if (e.key === "ArrowLeft") to = (at - 1 + tabs.length) % tabs.length;
1364
+ else if (e.key === "Home") to = 0;
1365
+ else if (e.key === "End") to = tabs.length - 1;
1366
+ else return;
1367
+ e.preventDefault();
1368
+ if (to !== at) onChange(tabs[to].value);
1369
+ barRef.current?.querySelectorAll('[role="tab"]')[to]?.focus();
1370
+ };
1371
+ return /* @__PURE__ */ jsxRuntime.jsx(
1372
+ "div",
1373
+ {
1374
+ ...rest,
1375
+ ref: barRef,
1376
+ role: "tablist",
1377
+ "aria-label": label,
1378
+ className: `nb-tabs ${className ?? ""}`.trim(),
1379
+ style: barStyle,
1380
+ onKeyDown,
1381
+ children: tabs.map((tab) => {
1382
+ const selected = tab.value === value;
1383
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1384
+ "button",
1385
+ {
1386
+ type: "button",
1387
+ role: "tab",
1388
+ "aria-selected": selected,
1389
+ tabIndex: selected ? 0 : -1,
1390
+ className: "nb-tabs__tab",
1391
+ onClick: () => onChange(tab.value),
1392
+ children: [
1393
+ tab.label,
1394
+ tab.count !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "0.35rem", color: tokens.fgFaint }, children: tab.count }) : null
1395
+ ]
1396
+ },
1397
+ tab.value
1398
+ );
1399
+ })
1400
+ }
1401
+ );
1144
1402
  }
1145
1403
  var READING_MAX = 760;
1146
1404
  var ContentWidthCtx = react.createContext("full");
@@ -1286,14 +1544,64 @@ function Back({ children, style }) {
1286
1544
  return /* @__PURE__ */ jsxRuntime.jsx(TextLink, { onClick: () => onBack?.(), style: { marginBottom: "0.5rem", ...style }, children: children ?? "\u2190 Back" });
1287
1545
  }
1288
1546
  var ListDetailLayout = Object.assign(ListDetailLayoutRoot, { List, Detail, Back });
1547
+ function PageLayout({
1548
+ crumbs,
1549
+ title,
1550
+ status,
1551
+ actions,
1552
+ description,
1553
+ descriptionLines,
1554
+ tabs,
1555
+ toolbar,
1556
+ children,
1557
+ style,
1558
+ ...rest
1559
+ }) {
1560
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1561
+ "div",
1562
+ {
1563
+ style: { display: "flex", flexDirection: "column", gap: "1.1rem", minWidth: 0, ...style },
1564
+ ...rest,
1565
+ children: [
1566
+ /* @__PURE__ */ jsxRuntime.jsx(
1567
+ PageHeader,
1568
+ {
1569
+ crumbs,
1570
+ title,
1571
+ status,
1572
+ actions,
1573
+ description,
1574
+ descriptionLines
1575
+ }
1576
+ ),
1577
+ tabs,
1578
+ toolbar ? /* @__PURE__ */ jsxRuntime.jsx(
1579
+ "div",
1580
+ {
1581
+ style: {
1582
+ display: "flex",
1583
+ alignItems: "center",
1584
+ gap: "0.5rem",
1585
+ flexWrap: "wrap",
1586
+ minWidth: 0,
1587
+ marginBottom: "-0.35rem"
1588
+ },
1589
+ children: toolbar
1590
+ }
1591
+ ) : null,
1592
+ children
1593
+ ]
1594
+ }
1595
+ );
1596
+ }
1289
1597
  var SidebarCtx = react.createContext(null);
1290
1598
  function useSidebar() {
1291
1599
  const ctx = react.useContext(SidebarCtx);
1292
1600
  if (!ctx) throw new Error("useSidebar must be used within <SidebarLayout>");
1293
1601
  return ctx;
1294
1602
  }
1295
- var STYLE_ID11 = "nb-synapse-sidebarlayout";
1296
- var RULES10 = `
1603
+ var STYLE_ID13 = "nb-synapse-sidebarlayout";
1604
+ var RULES12 = `
1297
1605
  .nb-sidebar-drawer { transition: transform 220ms cubic-bezier(0.2, 0, 0, 1); }
1298
1606
  .nb-sidebar-scrim { animation: nb-sidebar-fade 180ms ease; }
1299
1607
  @keyframes nb-sidebar-fade { from { opacity: 0; } to { opacity: 1; } }
@@ -1343,7 +1651,7 @@ function SidebarLayoutRoot({
1343
1651
  }
1344
1652
  function Sidebar({ style, children, ...rest }) {
1345
1653
  const { collapsed, mode, side, width, open, setOpen } = useSidebar();
1346
- chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID11, RULES10);
1654
+ chunkRX6XCLFF_cjs.ensureStyle(STYLE_ID13, RULES12);
1347
1655
  if (!collapsed) {
1348
1656
  const border = `${tokens.borderWidth} solid ${tokens.border}`;
1349
1657
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -1471,6 +1779,7 @@ var SidebarLayout = Object.assign(SidebarLayoutRoot, { Sidebar, Main, Trigger })
1471
1779
  exports.AppFrame = AppFrame;
1472
1780
  exports.Avatar = Avatar;
1473
1781
  exports.Badge = Badge;
1782
+ exports.Breadcrumb = Breadcrumb;
1474
1783
  exports.Button = Button;
1475
1784
  exports.Card = Card;
1476
1785
  exports.Divider = Divider;
@@ -1480,6 +1789,8 @@ exports.Heading = Heading;
1480
1789
  exports.Inline = Inline;
1481
1790
  exports.ListDetailLayout = ListDetailLayout;
1482
1791
  exports.ListRow = ListRow;
1792
+ exports.PageHeader = PageHeader;
1793
+ exports.PageLayout = PageLayout;
1483
1794
  exports.Pagination = Pagination;
1484
1795
  exports.Prose = Prose;
1485
1796
  exports.SearchField = SearchField;
@@ -1490,6 +1801,7 @@ exports.Spinner = Spinner;
1490
1801
  exports.Stack = Stack;
1491
1802
  exports.StatusDot = StatusDot;
1492
1803
  exports.Table = Table;
1804
+ exports.Tabs = Tabs;
1493
1805
  exports.Text = Text;
1494
1806
  exports.TextLink = TextLink;
1495
1807
  exports.headingStyle = headingStyle;