@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.136.1 → 1.0.0-beta.138.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/components/ai/context.d.ts +4 -4
  2. package/dist/components/composed/AppShellSimple/AppShellSimple.cjs +2 -0
  3. package/dist/components/composed/AppShellSimple/AppShellSimple.cjs.map +1 -0
  4. package/dist/components/composed/AppShellSimple/AppShellSimple.d.ts +49 -0
  5. package/dist/components/composed/AppShellSimple/AppShellSimple.js +204 -0
  6. package/dist/components/composed/AppShellSimple/AppShellSimple.js.map +1 -0
  7. package/dist/components/composed/AppShellSimple/index.d.ts +2 -0
  8. package/dist/components/composed/DataAppShell/DataAppShell.cjs +1 -1
  9. package/dist/components/composed/DataAppShell/DataAppShell.cjs.map +1 -1
  10. package/dist/components/composed/DataAppShell/DataAppShell.js +86 -93
  11. package/dist/components/composed/DataAppShell/DataAppShell.js.map +1 -1
  12. package/dist/components/composed/DataAppShell/PrimaryNav.cjs +1 -1
  13. package/dist/components/composed/DataAppShell/PrimaryNav.cjs.map +1 -1
  14. package/dist/components/composed/DataAppShell/PrimaryNav.js +106 -92
  15. package/dist/components/composed/DataAppShell/PrimaryNav.js.map +1 -1
  16. package/dist/components/composed/PageHeader/PageHeader.cjs +2 -0
  17. package/dist/components/composed/PageHeader/PageHeader.cjs.map +1 -0
  18. package/dist/components/composed/PageHeader/PageHeader.d.ts +106 -0
  19. package/dist/components/composed/PageHeader/PageHeader.js +38 -0
  20. package/dist/components/composed/PageHeader/PageHeader.js.map +1 -0
  21. package/dist/components/composed/PageHeader/index.d.ts +1 -0
  22. package/dist/components/ui/badge.d.ts +1 -1
  23. package/dist/components/ui/button.d.ts +2 -2
  24. package/dist/components/ui/input-group.d.ts +1 -1
  25. package/dist/components/ui/item.d.ts +2 -2
  26. package/dist/components/ui/tabs.d.ts +1 -1
  27. package/dist/composed/AppShellSimple.cjs +2 -0
  28. package/dist/composed/AppShellSimple.cjs.map +1 -0
  29. package/dist/composed/AppShellSimple.js +5 -0
  30. package/dist/composed/AppShellSimple.js.map +1 -0
  31. package/dist/composed/PageHeader.cjs +2 -0
  32. package/dist/composed/PageHeader.cjs.map +1 -0
  33. package/dist/composed/PageHeader.js +5 -0
  34. package/dist/composed/PageHeader.js.map +1 -0
  35. package/dist/index.cjs +1 -1
  36. package/dist/index.css +1 -1
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +714 -710
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.tailwind.css +1 -1
  41. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
- import { jsx as a, jsxs as s, Fragment as V } from "react/jsx-runtime";
2
- import { cva as o } from "class-variance-authority";
3
- import * as y from "react";
4
- import { Badge as b } from "../../../ui/badge.js";
1
+ import { jsx as a, jsxs as l, Fragment as V } from "react/jsx-runtime";
2
+ import { cva as s } from "class-variance-authority";
3
+ import * as g from "react";
4
+ import { Badge as v } from "../../../ui/badge.js";
5
5
  import { TooltipProvider as k, Tooltip as A, TooltipTrigger as C, TooltipContent as T } from "../../../ui/tooltip.js";
6
6
  import { cn as t } from "../../../lib/utils.js";
7
- const j = o("flex min-w-0", {
7
+ const j = s("flex min-w-0", {
8
8
  variants: {
9
9
  variant: {
10
10
  /** Vertical icon-only rail — tooltips on hover, user slot at the bottom */
@@ -16,28 +16,30 @@ const j = o("flex min-w-0", {
16
16
  }
17
17
  },
18
18
  defaultVariants: { variant: "rail" }
19
- }), P = o("shrink-0 flex", {
19
+ }), I = s("shrink-0 flex", {
20
20
  variants: {
21
21
  variant: {
22
22
  rail: "justify-center py-2",
23
- sidebar: "px-3 py-2.5 border-b border-sidebar-border",
23
+ // pl-0 so a header logo can sit in the same 48px icon gutter as the nav
24
+ // items below it (SW-2578); keep the right inset for a collapse control.
25
+ sidebar: "pr-3 py-2.5 border-b border-sidebar-border",
24
26
  top: "items-center"
25
27
  }
26
28
  },
27
29
  defaultVariants: { variant: "rail" }
28
- }), S = o("flex min-h-0", {
30
+ }), P = s("flex min-h-0", {
29
31
  variants: {
30
32
  variant: {
31
33
  rail: "flex-1 flex-col items-center gap-1 px-2 pt-2 overflow-y-auto",
32
- // px-1 the inset previously lived on each row as `mx-1` alongside
33
- // `w-full`, which made every row 8px wider than its container and got
34
- // clipped by this scroll area (SW-2118 sidebar row cut-off).
35
- sidebar: "flex-1 flex-col px-1 py-2 overflow-y-auto",
34
+ // px-0 so the active/hover row highlight runs edge-to-edge as a full-width
35
+ // bar (SW-2578). Rows are `w-full` with no negative margins, so nothing is
36
+ // clipped by this scroll area (the earlier SW-2118 mx-1 cut-off is gone).
37
+ sidebar: "flex-1 flex-col px-0 py-2 overflow-y-auto",
36
38
  top: "flex-1 flex-row items-center gap-1 min-w-0 overflow-x-auto"
37
39
  }
38
40
  },
39
41
  defaultVariants: { variant: "rail" }
40
- }), F = o("flex", {
42
+ }), S = s("flex", {
41
43
  variants: {
42
44
  variant: {
43
45
  rail: "flex-col items-center gap-3 w-full",
@@ -46,7 +48,7 @@ const j = o("flex min-w-0", {
46
48
  }
47
49
  },
48
50
  defaultVariants: { variant: "rail" }
49
- }), I = o("border-sidebar-border", {
51
+ }), F = s("border-sidebar-border", {
50
52
  variants: {
51
53
  variant: {
52
54
  rail: "border-t w-8 my-2",
@@ -55,7 +57,7 @@ const j = o("flex min-w-0", {
55
57
  }
56
58
  },
57
59
  defaultVariants: { variant: "rail" }
58
- }), B = o("shrink-0", {
60
+ }), z = s("shrink-0", {
59
61
  variants: {
60
62
  variant: {
61
63
  rail: "flex flex-col items-center py-2",
@@ -64,13 +66,15 @@ const j = o("flex min-w-0", {
64
66
  }
65
67
  },
66
68
  defaultVariants: { variant: "rail" }
67
- }), v = o(
69
+ }), b = s(
68
70
  "cursor-pointer bg-transparent border-none transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
69
71
  {
70
72
  variants: {
71
73
  variant: {
72
74
  rail: "flex flex-col items-center p-0 w-full",
73
- sidebar: "flex items-center gap-3 w-full px-3 py-2 text-sm text-left rounded-md",
75
+ // Icon sits in a fixed 48px gutter (see NavItem); the row highlight is a
76
+ // full-width square bar, so no px / rounded here (SW-2578).
77
+ sidebar: "flex items-center w-full py-2 pr-3 text-sm text-left",
74
78
  // ring-inset — the items row is an overflow-x-auto scroll container,
75
79
  // which would clip an outset ring at the row's edges
76
80
  top: "flex items-center gap-2 h-7 px-2.5 text-sm rounded-md whitespace-nowrap focus-visible:ring-inset"
@@ -84,7 +88,7 @@ const j = o("flex min-w-0", {
84
88
  {
85
89
  variant: "sidebar",
86
90
  active: !0,
87
- class: "bg-sidebar-accent text-sidebar-foreground font-medium"
91
+ class: "bg-sidebar-accent text-primary font-medium"
88
92
  },
89
93
  {
90
94
  variant: "sidebar",
@@ -104,7 +108,7 @@ const j = o("flex min-w-0", {
104
108
  ],
105
109
  defaultVariants: { variant: "rail", active: !1 }
106
110
  }
107
- ), R = o(
111
+ ), B = s(
108
112
  "relative flex items-center justify-center rounded-lg transition-colors duration-150",
109
113
  {
110
114
  variants: {
@@ -113,36 +117,46 @@ const j = o("flex min-w-0", {
113
117
  false: "bg-transparent"
114
118
  },
115
119
  variant: {
116
- rail: "w-[30px] h-[30px] hover:bg-accent",
117
- sidebar: "w-8 h-8",
120
+ // 32px box everywhere so the rail, sidebar and top-bar toggle boxes all
121
+ // match; sidebar-accent hover to match the rest of the sidebar surface.
122
+ rail: "size-8 hover:bg-sidebar-accent/60",
123
+ sidebar: "size-8",
118
124
  top: ""
119
125
  }
120
126
  },
121
127
  defaultVariants: { active: !1, variant: "rail" }
122
128
  }
123
129
  );
124
- function $({ page: r, variant: l, active: e, onSelect: f }) {
125
- const n = r.icon, d = () => {
126
- r.onClick?.(), f?.(r.id, r);
130
+ function R({
131
+ page: r,
132
+ active: i,
133
+ onClick: e
134
+ }) {
135
+ const o = r.icon;
136
+ return /* @__PURE__ */ l(
137
+ "button",
138
+ {
139
+ type: "button",
140
+ "data-slot": "data-app-shell-primary-nav-item",
141
+ "aria-current": i ? "page" : void 0,
142
+ className: t(b({ variant: "top", active: i })),
143
+ onClick: e,
144
+ children: [
145
+ o && /* @__PURE__ */ a(o, { className: "w-4 h-4" }),
146
+ /* @__PURE__ */ a("span", { children: r.label }),
147
+ r.badge != null && /* @__PURE__ */ a(v, { variant: "secondary", className: "h-4 px-1.5 text-[10px]", children: r.badge })
148
+ ]
149
+ }
150
+ );
151
+ }
152
+ function $({ page: r, variant: i, active: e, onSelect: o }) {
153
+ const d = r.icon, c = () => {
154
+ r.onClick?.(), o?.(r.id, r);
127
155
  };
128
- if (l === "top")
129
- return /* @__PURE__ */ s(
130
- "button",
131
- {
132
- type: "button",
133
- "data-slot": "data-app-shell-primary-nav-item",
134
- "aria-current": e ? "page" : void 0,
135
- className: t(v({ variant: l, active: e })),
136
- onClick: d,
137
- children: [
138
- n && /* @__PURE__ */ a(n, { className: "w-4 h-4" }),
139
- /* @__PURE__ */ a("span", { children: r.label }),
140
- r.badge != null && /* @__PURE__ */ a(b, { variant: "secondary", className: "h-4 px-1.5 text-[10px]", children: r.badge })
141
- ]
142
- }
143
- );
144
- const p = n ? /* @__PURE__ */ a(
145
- n,
156
+ if (i === "top")
157
+ return /* @__PURE__ */ a(R, { page: r, active: e, onClick: c });
158
+ const u = d ? /* @__PURE__ */ a(
159
+ d,
146
160
  {
147
161
  className: t("w-4 h-4", e ? "text-primary" : "text-muted-foreground")
148
162
  }
@@ -154,11 +168,11 @@ function $({ page: r, variant: l, active: e, onSelect: f }) {
154
168
  e ? "bg-primary" : "bg-muted-foreground/40"
155
169
  )
156
170
  }
157
- ), m = /* @__PURE__ */ s("div", { className: t(R({ active: e, variant: l })), children: [
158
- p,
159
- l === "rail" && r.badge != null && /* @__PURE__ */ a(b, { className: "absolute -top-1 -right-1 h-3.5 min-w-3.5 px-1 text-[9px] leading-none", children: r.badge })
171
+ ), p = /* @__PURE__ */ l("div", { className: t(B({ active: e, variant: i })), children: [
172
+ u,
173
+ i === "rail" && r.badge != null && /* @__PURE__ */ a(v, { className: "absolute -top-1 -right-1 h-3.5 min-w-3.5 px-1 text-[9px] leading-none", children: r.badge })
160
174
  ] });
161
- return l === "rail" ? /* @__PURE__ */ s(A, { children: [
175
+ return i === "rail" ? /* @__PURE__ */ l(A, { children: [
162
176
  /* @__PURE__ */ a(C, { asChild: !0, children: /* @__PURE__ */ a(
163
177
  "button",
164
178
  {
@@ -166,92 +180,92 @@ function $({ page: r, variant: l, active: e, onSelect: f }) {
166
180
  "data-slot": "data-app-shell-primary-nav-item",
167
181
  "aria-label": r.label,
168
182
  "aria-current": e ? "page" : void 0,
169
- className: t(v({ variant: l, active: e })),
170
- onClick: d,
171
- children: m
183
+ className: t(b({ variant: i, active: e })),
184
+ onClick: c,
185
+ children: p
172
186
  }
173
187
  ) }),
174
188
  /* @__PURE__ */ a(T, { side: "right", children: r.label })
175
- ] }) : /* @__PURE__ */ s(
189
+ ] }) : /* @__PURE__ */ l(
176
190
  "button",
177
191
  {
178
192
  type: "button",
179
193
  "data-slot": "data-app-shell-primary-nav-item",
180
194
  "aria-current": e ? "page" : void 0,
181
- className: t(v({ variant: l, active: e })),
182
- onClick: d,
195
+ className: t(b({ variant: i, active: e })),
196
+ onClick: c,
183
197
  children: [
184
- m,
198
+ /* @__PURE__ */ a("span", { className: "flex w-12 shrink-0 justify-center", children: p }),
185
199
  /* @__PURE__ */ a("span", { className: "truncate", children: r.label }),
186
- r.badge != null && /* @__PURE__ */ a(b, { variant: "secondary", className: "ml-auto h-4 px-1.5 text-[10px]", children: r.badge })
200
+ r.badge != null && /* @__PURE__ */ a(v, { variant: "secondary", className: "ml-auto h-4 px-1.5 text-[10px]", children: r.badge })
187
201
  ]
188
202
  }
189
203
  );
190
204
  }
191
- function H({
205
+ function J({
192
206
  variant: r,
193
- navGroups: l,
207
+ navGroups: i,
194
208
  activeKey: e,
195
- onSelect: f,
196
- header: n,
197
- user: d,
198
- actions: p,
199
- renderItem: m,
200
- className: w,
201
- ...N
209
+ onSelect: o,
210
+ header: d,
211
+ user: c,
212
+ actions: u,
213
+ renderItem: p,
214
+ className: N,
215
+ ...w
202
216
  }) {
203
- const i = r ?? "rail", x = i === "rail";
217
+ const n = r ?? "rail", x = n === "rail";
204
218
  return /* @__PURE__ */ a(
205
219
  "nav",
206
220
  {
207
221
  "data-slot": "data-app-shell-primary-nav",
208
- "data-variant": i,
209
- className: t(j({ variant: i }), w),
210
- ...N,
211
- children: /* @__PURE__ */ s(k, { children: [
212
- n != null && /* @__PURE__ */ a(
222
+ "data-variant": n,
223
+ className: t(j({ variant: n }), N),
224
+ ...w,
225
+ children: /* @__PURE__ */ l(k, { children: [
226
+ d != null && /* @__PURE__ */ a(
213
227
  "div",
214
228
  {
215
229
  "data-slot": "data-app-shell-primary-nav-header",
216
- className: t(P({ variant: i })),
217
- children: n
230
+ className: t(I({ variant: n })),
231
+ children: d
218
232
  }
219
233
  ),
220
- x && n != null && /* @__PURE__ */ a("div", { className: "shrink-0 mx-auto w-8 border-t border-sidebar-border" }),
234
+ x && d != null && /* @__PURE__ */ a("div", { className: "shrink-0 mx-auto w-8 border-t border-sidebar-border" }),
221
235
  /* @__PURE__ */ a(
222
236
  "div",
223
237
  {
224
238
  "data-slot": "data-app-shell-primary-nav-items",
225
- className: t(S({ variant: i })),
226
- children: l.map((u, h) => /* @__PURE__ */ s(y.Fragment, { children: [
227
- h > 0 && /* @__PURE__ */ a("div", { className: t(I({ variant: i })) }),
228
- i === "sidebar" && u.label && /* @__PURE__ */ a("span", { className: "px-3 py-1 text-[10px] uppercase tracking-widest text-muted-foreground/60 font-semibold", children: u.label }),
229
- /* @__PURE__ */ a("div", { className: t(F({ variant: i })), children: u.pages.map((c) => {
230
- const g = e == null ? c.isActive ?? !1 : c.id === e;
231
- return m ? /* @__PURE__ */ a(y.Fragment, { children: m(c, { active: g, variant: i }) }, c.id) : /* @__PURE__ */ a(
239
+ className: t(P({ variant: n })),
240
+ children: i.map((f, h) => /* @__PURE__ */ l(g.Fragment, { children: [
241
+ h > 0 && /* @__PURE__ */ a("div", { className: t(F({ variant: n })) }),
242
+ n === "sidebar" && f.label && /* @__PURE__ */ a("span", { className: "pl-12 pr-3 py-1 text-[10px] uppercase tracking-widest text-muted-foreground/60 font-semibold", children: f.label }),
243
+ /* @__PURE__ */ a("div", { className: t(S({ variant: n })), children: f.pages.map((m) => {
244
+ const y = e == null ? m.isActive ?? !1 : m.id === e;
245
+ return p ? /* @__PURE__ */ a(g.Fragment, { children: p(m, { active: y, variant: n }) }, m.id) : /* @__PURE__ */ a(
232
246
  $,
233
247
  {
234
- page: c,
235
- variant: i,
236
- active: g,
237
- onSelect: f
248
+ page: m,
249
+ variant: n,
250
+ active: y,
251
+ onSelect: o
238
252
  },
239
- c.id
253
+ m.id
240
254
  );
241
255
  }) })
242
- ] }, `${h}-${u.label ?? ""}`))
256
+ ] }, `${h}-${f.label ?? ""}`))
243
257
  }
244
258
  ),
245
- (d != null || p != null) && /* @__PURE__ */ s(V, { children: [
259
+ (c != null || u != null) && /* @__PURE__ */ l(V, { children: [
246
260
  x && /* @__PURE__ */ a("div", { className: "shrink-0 mx-auto w-8 border-t border-sidebar-border" }),
247
- /* @__PURE__ */ s(
261
+ /* @__PURE__ */ l(
248
262
  "div",
249
263
  {
250
264
  "data-slot": "data-app-shell-primary-nav-user",
251
- className: t(B({ variant: i })),
265
+ className: t(z({ variant: n })),
252
266
  children: [
253
- p,
254
- d
267
+ u,
268
+ c
255
269
  ]
256
270
  }
257
271
  )
@@ -261,8 +275,8 @@ function H({
261
275
  );
262
276
  }
263
277
  export {
264
- H as DataAppShellPrimaryNav,
265
- v as dataAppShellPrimaryNavItemVariants,
278
+ J as DataAppShellPrimaryNav,
279
+ b as dataAppShellPrimaryNavItemVariants,
266
280
  j as dataAppShellPrimaryNavVariants
267
281
  };
268
282
  //# sourceMappingURL=PrimaryNav.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PrimaryNav.js","sources":["../../../../src/components/composed/DataAppShell/PrimaryNav.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { type LucideIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface NavPage {\n /** Unique identifier */\n id: string;\n /** Display label */\n label: string;\n /** Lucide icon or custom React SVG component */\n icon?: LucideIcon | React.FC<React.SVGProps<SVGSVGElement>>;\n /** Optional count/status badge — overlaid on the icon in `rail`, inline in `sidebar`/`top` */\n badge?: React.ReactNode;\n /** Whether this page is currently active (ignored when `activeKey` is set on the nav) */\n isActive?: boolean;\n /** Click handler */\n onClick?: () => void;\n}\n\nexport interface NavGroup {\n /** Optional group label shown as a section header in the `sidebar` variant */\n label?: string;\n /** Page entries in this group */\n pages: NavPage[];\n}\n\nexport type DataAppShellPrimaryNavVariant = \"rail\" | \"sidebar\" | \"top\";\n\n// =============================================================================\n// CVA variants — one placement per `variant`\n// =============================================================================\n\nconst dataAppShellPrimaryNavVariants = cva(\"flex min-w-0\", {\n variants: {\n variant: {\n /** Vertical icon-only rail — tooltips on hover, user slot at the bottom */\n rail: \"flex-col\",\n /** Vertical icon + label panel — group labels, user slot at the bottom */\n sidebar: \"flex-col\",\n /** Horizontal bar — labels inline, user/actions slot on the right */\n top: \"flex-row items-center w-full gap-2\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst headerAreaVariants = cva(\"shrink-0 flex\", {\n variants: {\n variant: {\n rail: \"justify-center py-2\",\n sidebar: \"px-3 py-2.5 border-b border-sidebar-border\",\n top: \"items-center\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst itemsAreaVariants = cva(\"flex min-h-0\", {\n variants: {\n variant: {\n rail: \"flex-1 flex-col items-center gap-1 px-2 pt-2 overflow-y-auto\",\n // px-1 — the inset previously lived on each row as `mx-1` alongside\n // `w-full`, which made every row 8px wider than its container and got\n // clipped by this scroll area (SW-2118 sidebar row cut-off).\n sidebar: \"flex-1 flex-col px-1 py-2 overflow-y-auto\",\n top: \"flex-1 flex-row items-center gap-1 min-w-0 overflow-x-auto\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst groupItemsVariants = cva(\"flex\", {\n variants: {\n variant: {\n rail: \"flex-col items-center gap-3 w-full\",\n sidebar: \"flex-col gap-0.5\",\n top: \"flex-row items-center gap-1\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst groupSeparatorVariants = cva(\"border-sidebar-border\", {\n variants: {\n variant: {\n rail: \"border-t w-8 my-2\",\n sidebar: \"border-t mx-3 my-1\",\n top: \"border-l h-5 mx-1 self-center\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst userAreaVariants = cva(\"shrink-0\", {\n variants: {\n variant: {\n rail: \"flex flex-col items-center py-2\",\n sidebar: \"p-2 border-t border-sidebar-border\",\n top: \"flex items-center gap-2\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst dataAppShellPrimaryNavItemVariants = cva(\n \"cursor-pointer bg-transparent border-none transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n {\n variants: {\n variant: {\n rail: \"flex flex-col items-center p-0 w-full\",\n sidebar: \"flex items-center gap-3 w-full px-3 py-2 text-sm text-left rounded-md\",\n // ring-inset — the items row is an overflow-x-auto scroll container,\n // which would clip an outset ring at the row's edges\n top: \"flex items-center gap-2 h-7 px-2.5 text-sm rounded-md whitespace-nowrap focus-visible:ring-inset\",\n },\n active: {\n true: \"\",\n false: \"\",\n },\n },\n compoundVariants: [\n {\n variant: \"sidebar\",\n active: true,\n class: \"bg-sidebar-accent text-sidebar-foreground font-medium\",\n },\n {\n variant: \"sidebar\",\n active: false,\n class: \"text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground\",\n },\n {\n variant: \"top\",\n active: true,\n class: \"bg-primary/10 text-primary font-medium\",\n },\n {\n variant: \"top\",\n active: false,\n class: \"text-muted-foreground hover:bg-accent hover:text-foreground\",\n },\n ],\n defaultVariants: { variant: \"rail\", active: false },\n }\n);\n\nconst pageIconVariants = cva(\n \"relative flex items-center justify-center rounded-lg transition-colors duration-150\",\n {\n variants: {\n active: {\n true: \"bg-primary/10\",\n false: \"bg-transparent\",\n },\n variant: {\n rail: \"w-[30px] h-[30px] hover:bg-accent\",\n sidebar: \"w-8 h-8\",\n top: \"\",\n },\n },\n defaultVariants: { active: false, variant: \"rail\" },\n }\n);\n\n// =============================================================================\n// Props\n// =============================================================================\n\nexport interface DataAppShellPrimaryNavProps\n extends Omit<React.ComponentProps<\"nav\">, \"children\" | \"onSelect\">,\n VariantProps<typeof dataAppShellPrimaryNavVariants> {\n /** Navigation groups; each group contains one or more pages */\n navGroups: NavGroup[];\n /** Controlled active page id — takes precedence over per-page `isActive` */\n activeKey?: string;\n /** Called with the selected page id after the page's own `onClick` */\n onSelect?: (key: string, page: NavPage) => void;\n /** Slot above the items in `rail`/`sidebar`, left of the items in `top` (e.g. app logo) */\n header?: React.ReactNode;\n /** User slot (e.g. `<UserMenu/>`) — bottom in `rail`/`sidebar`, right in `top` */\n user?: React.ReactNode;\n /** Action slot rendered alongside `user` */\n actions?: React.ReactNode;\n /** Escape hatch — full control over an item's rendering */\n renderItem?: (\n page: NavPage,\n state: { active: boolean; variant: DataAppShellPrimaryNavVariant }\n ) => React.ReactNode;\n}\n\n// =============================================================================\n// NavItem\n// =============================================================================\n\ninterface NavItemProps {\n page: NavPage;\n variant: DataAppShellPrimaryNavVariant;\n active: boolean;\n onSelect?: (key: string, page: NavPage) => void;\n}\n\nfunction NavItem({ page, variant, active, onSelect }: NavItemProps) {\n const Icon = page.icon;\n\n const handleClick = () => {\n page.onClick?.();\n onSelect?.(page.id, page);\n };\n\n if (variant === \"top\") {\n return (\n <button\n type=\"button\"\n data-slot=\"data-app-shell-primary-nav-item\"\n aria-current={active ? \"page\" : undefined}\n className={cn(dataAppShellPrimaryNavItemVariants({ variant, active }))}\n onClick={handleClick}\n >\n {Icon && <Icon className=\"w-4 h-4\" />}\n <span>{page.label}</span>\n {page.badge != null && (\n <Badge variant=\"secondary\" className=\"h-4 px-1.5 text-[10px]\">\n {page.badge}\n </Badge>\n )}\n </button>\n );\n }\n\n const iconEl = Icon ? (\n <Icon\n className={cn(\"w-4 h-4\", active ? \"text-primary\" : \"text-muted-foreground\")}\n />\n ) : (\n <div\n className={cn(\n \"w-2 h-2 rounded-full\",\n active ? \"bg-primary\" : \"bg-muted-foreground/40\"\n )}\n />\n );\n\n const iconBox = (\n <div className={cn(pageIconVariants({ active, variant }))}>\n {iconEl}\n {variant === \"rail\" && page.badge != null && (\n <Badge className=\"absolute -top-1 -right-1 h-3.5 min-w-3.5 px-1 text-[9px] leading-none\">\n {page.badge}\n </Badge>\n )}\n </div>\n );\n\n if (variant === \"rail\") {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"data-app-shell-primary-nav-item\"\n aria-label={page.label}\n aria-current={active ? \"page\" : undefined}\n className={cn(dataAppShellPrimaryNavItemVariants({ variant, active }))}\n onClick={handleClick}\n >\n {iconBox}\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"right\">{page.label}</TooltipContent>\n </Tooltip>\n );\n }\n\n // sidebar — icon + label row\n return (\n <button\n type=\"button\"\n data-slot=\"data-app-shell-primary-nav-item\"\n aria-current={active ? \"page\" : undefined}\n className={cn(dataAppShellPrimaryNavItemVariants({ variant, active }))}\n onClick={handleClick}\n >\n {iconBox}\n <span className=\"truncate\">{page.label}</span>\n {page.badge != null && (\n <Badge variant=\"secondary\" className=\"ml-auto h-4 px-1.5 text-[10px]\">\n {page.badge}\n </Badge>\n )}\n </button>\n );\n}\n\n// =============================================================================\n// DataAppShellPrimaryNav\n// =============================================================================\n\n/**\n * The Data App Shell's nav engine — one `navGroups` model rendered in three\n * placements via `variant`:\n *\n * - **rail** — vertical icon-only (48px rail); labels via tooltip, user slot at the bottom\n * - **sidebar** — vertical icon + label with group headers, user slot at the bottom\n * - **top** — horizontal bar; user/actions slot on the right\n *\n * Active state is either controlled via `activeKey` or per-page `isActive`.\n * Width/background chrome is owned by the container (the shell's rail wrapper,\n * a Sheet, or a TopBar) — this component is layout-neutral.\n */\nfunction DataAppShellPrimaryNav({\n variant,\n navGroups,\n activeKey,\n onSelect,\n header,\n user,\n actions,\n renderItem,\n className,\n ...props\n}: DataAppShellPrimaryNavProps) {\n const resolvedVariant: DataAppShellPrimaryNavVariant = variant ?? \"rail\";\n const isRail = resolvedVariant === \"rail\";\n\n return (\n <nav\n data-slot=\"data-app-shell-primary-nav\"\n data-variant={resolvedVariant}\n className={cn(dataAppShellPrimaryNavVariants({ variant: resolvedVariant }), className)}\n {...props}\n >\n <TooltipProvider>\n {header != null && (\n <div\n data-slot=\"data-app-shell-primary-nav-header\"\n className={cn(headerAreaVariants({ variant: resolvedVariant }))}\n >\n {header}\n </div>\n )}\n {/* Rail: short centered divider under the header, aligned with the icon column */}\n {isRail && header != null && (\n <div className=\"shrink-0 mx-auto w-8 border-t border-sidebar-border\" />\n )}\n\n <div\n data-slot=\"data-app-shell-primary-nav-items\"\n className={cn(itemsAreaVariants({ variant: resolvedVariant }))}\n >\n {navGroups.map((group, groupIndex) => (\n <React.Fragment key={`${groupIndex}-${group.label ?? \"\"}`}>\n {groupIndex > 0 && (\n <div className={cn(groupSeparatorVariants({ variant: resolvedVariant }))} />\n )}\n {resolvedVariant === \"sidebar\" && group.label && (\n <span className=\"px-3 py-1 text-[10px] uppercase tracking-widest text-muted-foreground/60 font-semibold\">\n {group.label}\n </span>\n )}\n <div className={cn(groupItemsVariants({ variant: resolvedVariant }))}>\n {group.pages.map((page) => {\n const active =\n activeKey == null ? (page.isActive ?? false) : page.id === activeKey;\n if (renderItem) {\n return (\n <React.Fragment key={page.id}>\n {renderItem(page, { active, variant: resolvedVariant })}\n </React.Fragment>\n );\n }\n return (\n <NavItem\n key={page.id}\n page={page}\n variant={resolvedVariant}\n active={active}\n onSelect={onSelect}\n />\n );\n })}\n </div>\n </React.Fragment>\n ))}\n </div>\n\n {(user != null || actions != null) && (\n <>\n {/* Rail: short centered divider above the user slot */}\n {isRail && (\n <div className=\"shrink-0 mx-auto w-8 border-t border-sidebar-border\" />\n )}\n <div\n data-slot=\"data-app-shell-primary-nav-user\"\n className={cn(userAreaVariants({ variant: resolvedVariant }))}\n >\n {actions}\n {user}\n </div>\n </>\n )}\n </TooltipProvider>\n </nav>\n );\n}\n\nexport {\n DataAppShellPrimaryNav,\n dataAppShellPrimaryNavVariants,\n dataAppShellPrimaryNavItemVariants,\n};\n"],"names":["dataAppShellPrimaryNavVariants","cva","headerAreaVariants","itemsAreaVariants","groupItemsVariants","groupSeparatorVariants","userAreaVariants","dataAppShellPrimaryNavItemVariants","pageIconVariants","NavItem","page","variant","active","onSelect","Icon","handleClick","jsxs","cn","jsx","Badge","iconEl","iconBox","Tooltip","TooltipTrigger","TooltipContent","DataAppShellPrimaryNav","navGroups","activeKey","header","user","actions","renderItem","className","props","resolvedVariant","isRail","TooltipProvider","group","groupIndex","React","Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"PrimaryNav.js","sources":["../../../../src/components/composed/DataAppShell/PrimaryNav.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport { type LucideIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface NavPage {\n /** Unique identifier */\n id: string;\n /** Display label */\n label: string;\n /** Lucide icon or custom React SVG component */\n icon?: LucideIcon | React.FC<React.SVGProps<SVGSVGElement>>;\n /** Optional count/status badge — overlaid on the icon in `rail`, inline in `sidebar`/`top` */\n badge?: React.ReactNode;\n /** Whether this page is currently active (ignored when `activeKey` is set on the nav) */\n isActive?: boolean;\n /** Click handler */\n onClick?: () => void;\n}\n\nexport interface NavGroup {\n /** Optional group label shown as a section header in the `sidebar` variant */\n label?: string;\n /** Page entries in this group */\n pages: NavPage[];\n}\n\nexport type DataAppShellPrimaryNavVariant = \"rail\" | \"sidebar\" | \"top\";\n\n// =============================================================================\n// CVA variants — one placement per `variant`\n// =============================================================================\n\nconst dataAppShellPrimaryNavVariants = cva(\"flex min-w-0\", {\n variants: {\n variant: {\n /** Vertical icon-only rail — tooltips on hover, user slot at the bottom */\n rail: \"flex-col\",\n /** Vertical icon + label panel — group labels, user slot at the bottom */\n sidebar: \"flex-col\",\n /** Horizontal bar — labels inline, user/actions slot on the right */\n top: \"flex-row items-center w-full gap-2\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst headerAreaVariants = cva(\"shrink-0 flex\", {\n variants: {\n variant: {\n rail: \"justify-center py-2\",\n // pl-0 so a header logo can sit in the same 48px icon gutter as the nav\n // items below it (SW-2578); keep the right inset for a collapse control.\n sidebar: \"pr-3 py-2.5 border-b border-sidebar-border\",\n top: \"items-center\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst itemsAreaVariants = cva(\"flex min-h-0\", {\n variants: {\n variant: {\n rail: \"flex-1 flex-col items-center gap-1 px-2 pt-2 overflow-y-auto\",\n // px-0 so the active/hover row highlight runs edge-to-edge as a full-width\n // bar (SW-2578). Rows are `w-full` with no negative margins, so nothing is\n // clipped by this scroll area (the earlier SW-2118 mx-1 cut-off is gone).\n sidebar: \"flex-1 flex-col px-0 py-2 overflow-y-auto\",\n top: \"flex-1 flex-row items-center gap-1 min-w-0 overflow-x-auto\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst groupItemsVariants = cva(\"flex\", {\n variants: {\n variant: {\n rail: \"flex-col items-center gap-3 w-full\",\n sidebar: \"flex-col gap-0.5\",\n top: \"flex-row items-center gap-1\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst groupSeparatorVariants = cva(\"border-sidebar-border\", {\n variants: {\n variant: {\n rail: \"border-t w-8 my-2\",\n sidebar: \"border-t mx-3 my-1\",\n top: \"border-l h-5 mx-1 self-center\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst userAreaVariants = cva(\"shrink-0\", {\n variants: {\n variant: {\n rail: \"flex flex-col items-center py-2\",\n sidebar: \"p-2 border-t border-sidebar-border\",\n top: \"flex items-center gap-2\",\n },\n },\n defaultVariants: { variant: \"rail\" },\n});\n\nconst dataAppShellPrimaryNavItemVariants = cva(\n \"cursor-pointer bg-transparent border-none transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n {\n variants: {\n variant: {\n rail: \"flex flex-col items-center p-0 w-full\",\n // Icon sits in a fixed 48px gutter (see NavItem); the row highlight is a\n // full-width square bar, so no px / rounded here (SW-2578).\n sidebar: \"flex items-center w-full py-2 pr-3 text-sm text-left\",\n // ring-inset — the items row is an overflow-x-auto scroll container,\n // which would clip an outset ring at the row's edges\n top: \"flex items-center gap-2 h-7 px-2.5 text-sm rounded-md whitespace-nowrap focus-visible:ring-inset\",\n },\n active: {\n true: \"\",\n false: \"\",\n },\n },\n compoundVariants: [\n {\n variant: \"sidebar\",\n active: true,\n class: \"bg-sidebar-accent text-primary font-medium\",\n },\n {\n variant: \"sidebar\",\n active: false,\n class: \"text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground\",\n },\n {\n variant: \"top\",\n active: true,\n class: \"bg-primary/10 text-primary font-medium\",\n },\n {\n variant: \"top\",\n active: false,\n class: \"text-muted-foreground hover:bg-accent hover:text-foreground\",\n },\n ],\n defaultVariants: { variant: \"rail\", active: false },\n }\n);\n\nconst pageIconVariants = cva(\n \"relative flex items-center justify-center rounded-lg transition-colors duration-150\",\n {\n variants: {\n active: {\n true: \"bg-primary/10\",\n false: \"bg-transparent\",\n },\n variant: {\n // 32px box everywhere so the rail, sidebar and top-bar toggle boxes all\n // match; sidebar-accent hover to match the rest of the sidebar surface.\n rail: \"size-8 hover:bg-sidebar-accent/60\",\n sidebar: \"size-8\",\n top: \"\",\n },\n },\n defaultVariants: { active: false, variant: \"rail\" },\n }\n);\n\n// =============================================================================\n// Props\n// =============================================================================\n\nexport interface DataAppShellPrimaryNavProps\n extends Omit<React.ComponentProps<\"nav\">, \"children\" | \"onSelect\">,\n VariantProps<typeof dataAppShellPrimaryNavVariants> {\n /** Navigation groups; each group contains one or more pages */\n navGroups: NavGroup[];\n /** Controlled active page id — takes precedence over per-page `isActive` */\n activeKey?: string;\n /** Called with the selected page id after the page's own `onClick` */\n onSelect?: (key: string, page: NavPage) => void;\n /** Slot above the items in `rail`/`sidebar`, left of the items in `top` (e.g. app logo) */\n header?: React.ReactNode;\n /** User slot (e.g. `<UserMenu/>`) — bottom in `rail`/`sidebar`, right in `top` */\n user?: React.ReactNode;\n /** Action slot rendered alongside `user` */\n actions?: React.ReactNode;\n /** Escape hatch — full control over an item's rendering */\n renderItem?: (\n page: NavPage,\n state: { active: boolean; variant: DataAppShellPrimaryNavVariant }\n ) => React.ReactNode;\n}\n\n// =============================================================================\n// NavItem\n// =============================================================================\n\ninterface NavItemProps {\n page: NavPage;\n variant: DataAppShellPrimaryNavVariant;\n active: boolean;\n onSelect?: (key: string, page: NavPage) => void;\n}\n\n// The horizontal `top` variant is self-contained (no icon box / gutter styling),\n// so it lives in its own component to keep `NavItem` focused on rail/sidebar.\nfunction TopNavItem({\n page,\n active,\n onClick,\n}: {\n page: NavPage;\n active: boolean;\n onClick: () => void;\n}) {\n const Icon = page.icon;\n return (\n <button\n type=\"button\"\n data-slot=\"data-app-shell-primary-nav-item\"\n aria-current={active ? \"page\" : undefined}\n className={cn(dataAppShellPrimaryNavItemVariants({ variant: \"top\", active }))}\n onClick={onClick}\n >\n {Icon && <Icon className=\"w-4 h-4\" />}\n <span>{page.label}</span>\n {page.badge != null && (\n <Badge variant=\"secondary\" className=\"h-4 px-1.5 text-[10px]\">\n {page.badge}\n </Badge>\n )}\n </button>\n );\n}\n\nfunction NavItem({ page, variant, active, onSelect }: NavItemProps) {\n const Icon = page.icon;\n\n const handleClick = () => {\n page.onClick?.();\n onSelect?.(page.id, page);\n };\n\n if (variant === \"top\") {\n return <TopNavItem page={page} active={active} onClick={handleClick} />;\n }\n\n const iconEl = Icon ? (\n <Icon\n className={cn(\"w-4 h-4\", active ? \"text-primary\" : \"text-muted-foreground\")}\n />\n ) : (\n <div\n className={cn(\n \"w-2 h-2 rounded-full\",\n active ? \"bg-primary\" : \"bg-muted-foreground/40\"\n )}\n />\n );\n\n const iconBox = (\n <div className={cn(pageIconVariants({ active, variant }))}>\n {iconEl}\n {variant === \"rail\" && page.badge != null && (\n <Badge className=\"absolute -top-1 -right-1 h-3.5 min-w-3.5 px-1 text-[9px] leading-none\">\n {page.badge}\n </Badge>\n )}\n </div>\n );\n\n if (variant === \"rail\") {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"data-app-shell-primary-nav-item\"\n aria-label={page.label}\n aria-current={active ? \"page\" : undefined}\n className={cn(dataAppShellPrimaryNavItemVariants({ variant, active }))}\n onClick={handleClick}\n >\n {iconBox}\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"right\">{page.label}</TooltipContent>\n </Tooltip>\n );\n }\n\n // sidebar — icon (in a 48px gutter) + label row\n return (\n <button\n type=\"button\"\n data-slot=\"data-app-shell-primary-nav-item\"\n aria-current={active ? \"page\" : undefined}\n className={cn(dataAppShellPrimaryNavItemVariants({ variant, active }))}\n onClick={handleClick}\n >\n <span className=\"flex w-12 shrink-0 justify-center\">{iconBox}</span>\n <span className=\"truncate\">{page.label}</span>\n {page.badge != null && (\n <Badge variant=\"secondary\" className=\"ml-auto h-4 px-1.5 text-[10px]\">\n {page.badge}\n </Badge>\n )}\n </button>\n );\n}\n\n// =============================================================================\n// DataAppShellPrimaryNav\n// =============================================================================\n\n/**\n * The Data App Shell's nav engine — one `navGroups` model rendered in three\n * placements via `variant`:\n *\n * - **rail** — vertical icon-only (48px rail); labels via tooltip, user slot at the bottom\n * - **sidebar** — vertical icon + label with group headers, user slot at the bottom\n * - **top** — horizontal bar; user/actions slot on the right\n *\n * Active state is either controlled via `activeKey` or per-page `isActive`.\n * Width/background chrome is owned by the container (the shell's rail wrapper,\n * a Sheet, or a TopBar) — this component is layout-neutral.\n */\nfunction DataAppShellPrimaryNav({\n variant,\n navGroups,\n activeKey,\n onSelect,\n header,\n user,\n actions,\n renderItem,\n className,\n ...props\n}: DataAppShellPrimaryNavProps) {\n const resolvedVariant: DataAppShellPrimaryNavVariant = variant ?? \"rail\";\n const isRail = resolvedVariant === \"rail\";\n\n return (\n <nav\n data-slot=\"data-app-shell-primary-nav\"\n data-variant={resolvedVariant}\n className={cn(dataAppShellPrimaryNavVariants({ variant: resolvedVariant }), className)}\n {...props}\n >\n <TooltipProvider>\n {header != null && (\n <div\n data-slot=\"data-app-shell-primary-nav-header\"\n className={cn(headerAreaVariants({ variant: resolvedVariant }))}\n >\n {header}\n </div>\n )}\n {/* Rail: short centered divider under the header, aligned with the icon column */}\n {isRail && header != null && (\n <div className=\"shrink-0 mx-auto w-8 border-t border-sidebar-border\" />\n )}\n\n <div\n data-slot=\"data-app-shell-primary-nav-items\"\n className={cn(itemsAreaVariants({ variant: resolvedVariant }))}\n >\n {navGroups.map((group, groupIndex) => (\n <React.Fragment key={`${groupIndex}-${group.label ?? \"\"}`}>\n {groupIndex > 0 && (\n <div className={cn(groupSeparatorVariants({ variant: resolvedVariant }))} />\n )}\n {resolvedVariant === \"sidebar\" && group.label && (\n <span className=\"pl-12 pr-3 py-1 text-[10px] uppercase tracking-widest text-muted-foreground/60 font-semibold\">\n {group.label}\n </span>\n )}\n <div className={cn(groupItemsVariants({ variant: resolvedVariant }))}>\n {group.pages.map((page) => {\n const active =\n activeKey == null ? (page.isActive ?? false) : page.id === activeKey;\n if (renderItem) {\n return (\n <React.Fragment key={page.id}>\n {renderItem(page, { active, variant: resolvedVariant })}\n </React.Fragment>\n );\n }\n return (\n <NavItem\n key={page.id}\n page={page}\n variant={resolvedVariant}\n active={active}\n onSelect={onSelect}\n />\n );\n })}\n </div>\n </React.Fragment>\n ))}\n </div>\n\n {(user != null || actions != null) && (\n <>\n {/* Rail: short centered divider above the user slot */}\n {isRail && (\n <div className=\"shrink-0 mx-auto w-8 border-t border-sidebar-border\" />\n )}\n <div\n data-slot=\"data-app-shell-primary-nav-user\"\n className={cn(userAreaVariants({ variant: resolvedVariant }))}\n >\n {actions}\n {user}\n </div>\n </>\n )}\n </TooltipProvider>\n </nav>\n );\n}\n\nexport {\n DataAppShellPrimaryNav,\n dataAppShellPrimaryNavVariants,\n dataAppShellPrimaryNavItemVariants,\n};\n"],"names":["dataAppShellPrimaryNavVariants","cva","headerAreaVariants","itemsAreaVariants","groupItemsVariants","groupSeparatorVariants","userAreaVariants","dataAppShellPrimaryNavItemVariants","pageIconVariants","TopNavItem","page","active","onClick","Icon","jsxs","cn","jsx","Badge","NavItem","variant","onSelect","handleClick","iconEl","iconBox","Tooltip","TooltipTrigger","TooltipContent","DataAppShellPrimaryNav","navGroups","activeKey","header","user","actions","renderItem","className","props","resolvedVariant","isRail","TooltipProvider","group","groupIndex","React","Fragment"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),s=require("../../../ui/text.cjs"),g=require("../../../lib/utils.cjs");function h({title:l,subtitle:a,trailing:t,icon:r,as:i="h1",variant:d="title-lg",truncate:n=!0,className:c,subtitleClassName:o,...u}){return e.jsxs("div",{"data-slot":"page-header",className:g.cn("space-y-0.5",c),...u,children:[e.jsxs("div",{"data-slot":"page-header-row",className:"flex items-baseline gap-3",children:[e.jsx(s.Text,{as:i,variant:d,icon:r,truncate:n,className:"min-w-0","data-slot":"page-header-title",children:l}),t?e.jsx("div",{"data-slot":"page-header-trailing",className:"ml-auto flex shrink-0 items-baseline gap-2",children:t}):null]}),a?e.jsx(s.Text,{as:"p",variant:"body",state:"muted",className:o,"data-slot":"page-header-subtitle",children:a}):null]})}exports.PageHeader=h;
2
+ //# sourceMappingURL=PageHeader.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageHeader.cjs","sources":["../../../../src/components/composed/PageHeader/PageHeader.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { Text, type TextVariant } from \"@/components/ui/text\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Heading levels `PageHeader` is allowed to render its title as.\n *\n * Deliberately narrower than `Text`'s `as`: a page header's title *is* a\n * heading, and restricting the type is what makes \"heading level is\n * caller-controlled\" enforceable rather than advisory. If you want a title-\n * scaled node that stays out of the document outline, that is a `Text`, not a\n * `PageHeader`.\n */\ntype PageHeaderHeading = \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\";\n\n/**\n * Visual scales that make sense for a page title. A page header is never\n * `body`/`caption`/`overline` sized — those variants exist on `Text` for copy,\n * not for titles.\n */\ntype PageHeaderVariant = Extract<TextVariant, \"display\" | \"title-lg\" | \"title\" | \"title-sm\" | \"title-xs\">;\n\n/**\n * Props are based on `HTMLAttributes<HTMLElement>` for the same reason `Text`'s\n * are (see the comment in `src/components/ui/text.tsx`): handlers and `ref` on a\n * concrete tag are parameterised by that one element type, which cannot hold\n * across a polymorphic `as`. No `ref` is forwarded; the kit is migrating off\n * `forwardRef`.\n */\ninterface PageHeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, \"title\"> {\n /**\n * The page title. Renders inside the heading element, so its text *is* the\n * heading's accessible name.\n *\n * Non-interactive trailing content that *should* join the accessible name\n * (a status `Badge`, a count) goes here, inline with the text. Anything\n * interactive — or anything that must stay out of the name — goes in\n * `trailing` instead.\n */\n title: React.ReactNode;\n /**\n * Secondary line beneath the title. Always renders as a `p` at\n * `body`/`muted`, never a heading tag: a subtitle inside an `h3` pollutes the\n * document outline and reads to a screen reader as a section that does not\n * exist.\n */\n subtitle?: React.ReactNode;\n /**\n * Actions, status, or an overflow menu, pinned to the right of the title.\n *\n * Renders as a **sibling** of the heading element, never a descendant — a\n * button or menu trigger nested inside an `h2` is announced as part of the\n * heading. This is the whole reason `PageHeader` exists as a component rather\n * than as more props on `Text`; the structure below is load-bearing, not\n * incidental.\n */\n trailing?: React.ReactNode;\n /**\n * Decorative leading glyph on the title, forwarded to `Text` — sized in `em`\n * so it scales with `variant`, and `aria-hidden` so it stays out of the\n * heading's accessible name.\n */\n icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;\n /**\n * Heading level for the title. Defaults to `h1` — a page header is usually\n * the page's one `h1` — and is **never** inferred from `variant`. Set it to\n * `h2` when the page already owns its `h1` (a shell breadcrumb, for\n * instance).\n */\n as?: PageHeaderHeading;\n /** Visual scale of the title. Independent of `as`. */\n variant?: PageHeaderVariant;\n /**\n * Truncate a long title to one line so `trailing` is never pushed out of the\n * row. On by default: the pinned-right trailing contract only holds if the\n * title is the thing that gives up space. Set `false` for a title that should\n * wrap instead — `trailing` then aligns to the first line's baseline.\n */\n truncate?: boolean;\n /** Extra classes for the subtitle, for the rare page that needs a wider measure. */\n subtitleClassName?: string;\n}\n\n/**\n * Page title, optional subtitle, and an optional trailing action slot.\n *\n * The structure is the point. The subtitle renders *outside* the title row, so\n * the row's `items-baseline` aligns `trailing` against the **title's** baseline\n * rather than centring it against the whole title+subtitle block — which is\n * what happens if you put all three in one flex container, and is the most\n * common way this layout is got wrong.\n *\n * ```tsx\n * <PageHeader\n * title=\"Peptide mapping\"\n * subtitle=\"14 samples across 3 plates · last run 12 minutes ago\"\n * trailing={<Button size=\"sm\">Configure</Button>}\n * />\n * ```\n *\n * The root is a `div`, not a `header`: a `header` that happens to be a direct\n * child of `body` becomes a `banner` landmark, and a page can only have one of\n * those — the shell's top bar already claims it.\n *\n * Not for component-internal titles. Card headers, shell chrome, and empty-state\n * copy own their own scale; replacing it with `PageHeader` gives the kit two\n * sources of truth for one set of pixels.\n */\nfunction PageHeader({\n title,\n subtitle,\n trailing,\n icon,\n as = \"h1\",\n variant = \"title-lg\",\n truncate = true,\n className,\n subtitleClassName,\n ...props\n}: PageHeaderProps) {\n return (\n <div data-slot=\"page-header\" className={cn(\"space-y-0.5\", className)} {...props}>\n {/* The title row. `trailing` is a sibling of the heading here — moving it\n inside the `Text` below would fold a button into the heading's\n accessible name. */}\n <div data-slot=\"page-header-row\" className=\"flex items-baseline gap-3\">\n <Text\n as={as}\n variant={variant}\n icon={icon}\n truncate={truncate}\n // `min-w-0` is what lets the title shrink below its content width so\n // `truncate` actually engages; a flex item defaults to\n // `min-width: auto` and would otherwise push `trailing` out.\n className=\"min-w-0\"\n data-slot=\"page-header-title\"\n >\n {title}\n </Text>\n {trailing ? (\n <div data-slot=\"page-header-trailing\" className=\"ml-auto flex shrink-0 items-baseline gap-2\">\n {trailing}\n </div>\n ) : null}\n </div>\n\n {subtitle ? (\n <Text as=\"p\" variant=\"body\" state=\"muted\" className={subtitleClassName} data-slot=\"page-header-subtitle\">\n {subtitle}\n </Text>\n ) : null}\n </div>\n );\n}\n\nexport { PageHeader };\nexport type { PageHeaderProps, PageHeaderHeading, PageHeaderVariant };\n"],"names":["PageHeader","title","subtitle","trailing","icon","as","variant","truncate","className","subtitleClassName","props","jsxs","cn","jsx","Text"],"mappings":"2LA6GA,SAASA,EAAW,CAClB,MAAAC,EACA,SAAAC,EACA,SAAAC,EACA,KAAAC,EACA,GAAAC,EAAK,KACL,QAAAC,EAAU,WACV,SAAAC,EAAW,GACX,UAAAC,EACA,kBAAAC,EACA,GAAGC,CACL,EAAoB,CAClB,OACEC,OAAC,MAAA,CAAI,YAAU,cAAc,UAAWC,EAAAA,GAAG,cAAeJ,CAAS,EAAI,GAAGE,EAIxE,SAAA,CAAAC,EAAAA,KAAC,MAAA,CAAI,YAAU,kBAAkB,UAAU,4BACzC,SAAA,CAAAE,EAAAA,IAACC,EAAAA,KAAA,CACC,GAAAT,EACA,QAAAC,EACA,KAAAF,EACA,SAAAG,EAIA,UAAU,UACV,YAAU,oBAET,SAAAN,CAAA,CAAA,EAEFE,QACE,MAAA,CAAI,YAAU,uBAAuB,UAAU,6CAC7C,WACH,EACE,IAAA,EACN,EAECD,EACCW,EAAAA,IAACC,OAAA,CAAK,GAAG,IAAI,QAAQ,OAAO,MAAM,QAAQ,UAAWL,EAAmB,YAAU,uBAC/E,WACH,EACE,IAAA,EACN,CAEJ"}
@@ -0,0 +1,106 @@
1
+ import { TextVariant } from '../../ui/text';
2
+ import * as React from "react";
3
+ /**
4
+ * Heading levels `PageHeader` is allowed to render its title as.
5
+ *
6
+ * Deliberately narrower than `Text`'s `as`: a page header's title *is* a
7
+ * heading, and restricting the type is what makes "heading level is
8
+ * caller-controlled" enforceable rather than advisory. If you want a title-
9
+ * scaled node that stays out of the document outline, that is a `Text`, not a
10
+ * `PageHeader`.
11
+ */
12
+ type PageHeaderHeading = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
13
+ /**
14
+ * Visual scales that make sense for a page title. A page header is never
15
+ * `body`/`caption`/`overline` sized — those variants exist on `Text` for copy,
16
+ * not for titles.
17
+ */
18
+ type PageHeaderVariant = Extract<TextVariant, "display" | "title-lg" | "title" | "title-sm" | "title-xs">;
19
+ /**
20
+ * Props are based on `HTMLAttributes<HTMLElement>` for the same reason `Text`'s
21
+ * are (see the comment in `src/components/ui/text.tsx`): handlers and `ref` on a
22
+ * concrete tag are parameterised by that one element type, which cannot hold
23
+ * across a polymorphic `as`. No `ref` is forwarded; the kit is migrating off
24
+ * `forwardRef`.
25
+ */
26
+ interface PageHeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
27
+ /**
28
+ * The page title. Renders inside the heading element, so its text *is* the
29
+ * heading's accessible name.
30
+ *
31
+ * Non-interactive trailing content that *should* join the accessible name
32
+ * (a status `Badge`, a count) goes here, inline with the text. Anything
33
+ * interactive — or anything that must stay out of the name — goes in
34
+ * `trailing` instead.
35
+ */
36
+ title: React.ReactNode;
37
+ /**
38
+ * Secondary line beneath the title. Always renders as a `p` at
39
+ * `body`/`muted`, never a heading tag: a subtitle inside an `h3` pollutes the
40
+ * document outline and reads to a screen reader as a section that does not
41
+ * exist.
42
+ */
43
+ subtitle?: React.ReactNode;
44
+ /**
45
+ * Actions, status, or an overflow menu, pinned to the right of the title.
46
+ *
47
+ * Renders as a **sibling** of the heading element, never a descendant — a
48
+ * button or menu trigger nested inside an `h2` is announced as part of the
49
+ * heading. This is the whole reason `PageHeader` exists as a component rather
50
+ * than as more props on `Text`; the structure below is load-bearing, not
51
+ * incidental.
52
+ */
53
+ trailing?: React.ReactNode;
54
+ /**
55
+ * Decorative leading glyph on the title, forwarded to `Text` — sized in `em`
56
+ * so it scales with `variant`, and `aria-hidden` so it stays out of the
57
+ * heading's accessible name.
58
+ */
59
+ icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
60
+ /**
61
+ * Heading level for the title. Defaults to `h1` — a page header is usually
62
+ * the page's one `h1` — and is **never** inferred from `variant`. Set it to
63
+ * `h2` when the page already owns its `h1` (a shell breadcrumb, for
64
+ * instance).
65
+ */
66
+ as?: PageHeaderHeading;
67
+ /** Visual scale of the title. Independent of `as`. */
68
+ variant?: PageHeaderVariant;
69
+ /**
70
+ * Truncate a long title to one line so `trailing` is never pushed out of the
71
+ * row. On by default: the pinned-right trailing contract only holds if the
72
+ * title is the thing that gives up space. Set `false` for a title that should
73
+ * wrap instead — `trailing` then aligns to the first line's baseline.
74
+ */
75
+ truncate?: boolean;
76
+ /** Extra classes for the subtitle, for the rare page that needs a wider measure. */
77
+ subtitleClassName?: string;
78
+ }
79
+ /**
80
+ * Page title, optional subtitle, and an optional trailing action slot.
81
+ *
82
+ * The structure is the point. The subtitle renders *outside* the title row, so
83
+ * the row's `items-baseline` aligns `trailing` against the **title's** baseline
84
+ * rather than centring it against the whole title+subtitle block — which is
85
+ * what happens if you put all three in one flex container, and is the most
86
+ * common way this layout is got wrong.
87
+ *
88
+ * ```tsx
89
+ * <PageHeader
90
+ * title="Peptide mapping"
91
+ * subtitle="14 samples across 3 plates · last run 12 minutes ago"
92
+ * trailing={<Button size="sm">Configure</Button>}
93
+ * />
94
+ * ```
95
+ *
96
+ * The root is a `div`, not a `header`: a `header` that happens to be a direct
97
+ * child of `body` becomes a `banner` landmark, and a page can only have one of
98
+ * those — the shell's top bar already claims it.
99
+ *
100
+ * Not for component-internal titles. Card headers, shell chrome, and empty-state
101
+ * copy own their own scale; replacing it with `PageHeader` gives the kit two
102
+ * sources of truth for one set of pixels.
103
+ */
104
+ declare function PageHeader({ title, subtitle, trailing, icon, as, variant, truncate, className, subtitleClassName, ...props }: PageHeaderProps): import("react/jsx-runtime").JSX.Element;
105
+ export { PageHeader };
106
+ export type { PageHeaderProps, PageHeaderHeading, PageHeaderVariant };
@@ -0,0 +1,38 @@
1
+ import { jsxs as l, jsx as e } from "react/jsx-runtime";
2
+ import { Text as s } from "../../../ui/text.js";
3
+ import { cn as h } from "../../../lib/utils.js";
4
+ function x({
5
+ title: r,
6
+ subtitle: a,
7
+ trailing: t,
8
+ icon: d,
9
+ as: i = "h1",
10
+ variant: m = "title-lg",
11
+ truncate: n = !0,
12
+ className: o,
13
+ subtitleClassName: c,
14
+ ...p
15
+ }) {
16
+ return /* @__PURE__ */ l("div", { "data-slot": "page-header", className: h("space-y-0.5", o), ...p, children: [
17
+ /* @__PURE__ */ l("div", { "data-slot": "page-header-row", className: "flex items-baseline gap-3", children: [
18
+ /* @__PURE__ */ e(
19
+ s,
20
+ {
21
+ as: i,
22
+ variant: m,
23
+ icon: d,
24
+ truncate: n,
25
+ className: "min-w-0",
26
+ "data-slot": "page-header-title",
27
+ children: r
28
+ }
29
+ ),
30
+ t ? /* @__PURE__ */ e("div", { "data-slot": "page-header-trailing", className: "ml-auto flex shrink-0 items-baseline gap-2", children: t }) : null
31
+ ] }),
32
+ a ? /* @__PURE__ */ e(s, { as: "p", variant: "body", state: "muted", className: c, "data-slot": "page-header-subtitle", children: a }) : null
33
+ ] });
34
+ }
35
+ export {
36
+ x as PageHeader
37
+ };
38
+ //# sourceMappingURL=PageHeader.js.map