@godxjp/ui 18.3.0 → 18.5.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 (131) hide show
  1. package/README.md +18 -3
  2. package/dist/app/theme-axes.d.ts +11 -3
  3. package/dist/app/theme-axes.js +2 -1
  4. package/dist/components/charts/chart-frame.d.ts +17 -3
  5. package/dist/components/charts/chart-frame.js +7 -1
  6. package/dist/components/charts/chart-summary.d.ts +21 -0
  7. package/dist/components/charts/chart-summary.js +32 -5
  8. package/dist/components/charts/compact-bar-trend.d.ts +29 -0
  9. package/dist/components/charts/compact-bar-trend.js +74 -0
  10. package/dist/components/charts/index.d.ts +3 -0
  11. package/dist/components/charts/index.js +2 -0
  12. package/dist/components/data-display/badge.d.ts +8 -0
  13. package/dist/components/data-display/badge.js +7 -1
  14. package/dist/components/data-display/data-table.d.ts +16 -1
  15. package/dist/components/data-display/data-table.js +56 -0
  16. package/dist/components/data-display/index.d.ts +3 -1
  17. package/dist/components/data-display/index.js +10 -1
  18. package/dist/components/data-display/list-row.d.ts +22 -4
  19. package/dist/components/data-display/list-row.js +21 -3
  20. package/dist/components/data-display/service-launcher-card.d.ts +40 -0
  21. package/dist/components/data-display/service-launcher-card.js +97 -0
  22. package/dist/components/data-entry/command-palette.d.ts +37 -0
  23. package/dist/components/data-entry/command-palette.js +125 -0
  24. package/dist/components/data-entry/index.d.ts +2 -0
  25. package/dist/components/data-entry/index.js +2 -0
  26. package/dist/components/feedback/dialog.d.ts +2 -0
  27. package/dist/components/feedback/dialog.js +42 -33
  28. package/dist/components/feedback/index.d.ts +4 -1
  29. package/dist/components/feedback/index.js +6 -2
  30. package/dist/components/feedback/sheet.d.ts +26 -4
  31. package/dist/components/feedback/sheet.js +54 -7
  32. package/dist/components/feedback/two-factor-setup.d.ts +31 -0
  33. package/dist/components/feedback/two-factor-setup.js +97 -0
  34. package/dist/components/general/index.d.ts +1 -1
  35. package/dist/components/general/logo.d.ts +42 -8
  36. package/dist/components/general/logo.js +67 -14
  37. package/dist/components/layout/app-shell.js +22 -6
  38. package/dist/components/layout/auth-divider.d.ts +7 -0
  39. package/dist/components/layout/auth-divider.js +21 -0
  40. package/dist/components/layout/auth-footer.d.ts +5 -0
  41. package/dist/components/layout/auth-footer.js +24 -0
  42. package/dist/components/layout/auth-identity.d.ts +5 -0
  43. package/dist/components/layout/auth-identity.js +17 -0
  44. package/dist/components/layout/auth-shell.d.ts +8 -1
  45. package/dist/components/layout/auth-shell.js +25 -6
  46. package/dist/components/layout/auth-stack.d.ts +4 -0
  47. package/dist/components/layout/auth-stack.js +8 -0
  48. package/dist/components/layout/centered-shell.d.ts +5 -2
  49. package/dist/components/layout/centered-shell.js +2 -1
  50. package/dist/components/layout/index.d.ts +18 -3
  51. package/dist/components/layout/index.js +17 -2
  52. package/dist/components/layout/legal-document-shell.d.ts +34 -0
  53. package/dist/components/layout/legal-document-shell.js +186 -0
  54. package/dist/components/layout/master-detail.d.ts +24 -0
  55. package/dist/components/layout/master-detail.js +48 -0
  56. package/dist/components/layout/org-switcher.d.ts +9 -0
  57. package/dist/components/layout/org-switcher.js +218 -0
  58. package/dist/components/layout/page-container.d.ts +1 -1
  59. package/dist/components/layout/page-container.js +3 -2
  60. package/dist/components/layout/sidebar-link.d.ts +43 -0
  61. package/dist/components/layout/sidebar-link.js +13 -0
  62. package/dist/components/layout/sidebar.d.ts +42 -5
  63. package/dist/components/layout/sidebar.js +170 -85
  64. package/dist/components/navigation/app-setting-picker.js +17 -6
  65. package/dist/components/navigation/filter-bar.d.ts +12 -3
  66. package/dist/components/navigation/filter-bar.js +8 -2
  67. package/dist/components/navigation/index.d.ts +2 -2
  68. package/dist/components/navigation/index.js +3 -1
  69. package/dist/components/navigation/tabs-scroll.d.ts +70 -0
  70. package/dist/components/navigation/tabs-scroll.js +76 -0
  71. package/dist/components/navigation/tabs.js +35 -20
  72. package/dist/email/brand-mark.d.ts +92 -0
  73. package/dist/email/brand-mark.js +83 -0
  74. package/dist/email/color.d.ts +52 -0
  75. package/dist/email/color.js +37 -0
  76. package/dist/email/geometry.d.ts +112 -0
  77. package/dist/email/geometry.js +89 -0
  78. package/dist/email/index.d.ts +78 -0
  79. package/dist/email/index.js +76 -0
  80. package/dist/email/inline-style.d.ts +25 -0
  81. package/dist/email/inline-style.js +21 -0
  82. package/dist/email/tokens.generated.d.ts +147 -0
  83. package/dist/email/tokens.generated.js +142 -0
  84. package/dist/i18n/messages/en.json +21 -2
  85. package/dist/i18n/messages/ja.json +21 -2
  86. package/dist/i18n/messages/vi.json +21 -2
  87. package/dist/inertia/index.d.ts +50 -0
  88. package/dist/inertia/index.js +5 -0
  89. package/dist/props/components/app.prop.d.ts +11 -1
  90. package/dist/props/components/charts.prop.d.ts +40 -1
  91. package/dist/props/components/data-display.prop.d.ts +13 -1
  92. package/dist/props/components/feedback.prop.d.ts +10 -0
  93. package/dist/props/components/index.d.ts +2 -2
  94. package/dist/props/components/layout.prop.d.ts +374 -11
  95. package/dist/props/components/layout.prop.js +1 -0
  96. package/dist/props/components/navigation.prop.d.ts +19 -1
  97. package/dist/props/registry.d.ts +291 -7
  98. package/dist/props/registry.js +341 -7
  99. package/dist/props/vocabulary/index.d.ts +1 -1
  100. package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
  101. package/dist/props/vocabulary/layout.prop.d.ts +18 -0
  102. package/dist/styles/base.css +9 -3
  103. package/dist/styles/card-layout.css +162 -8
  104. package/dist/styles/chart-layout.css +97 -0
  105. package/dist/styles/control.css +48 -2
  106. package/dist/styles/data-display-layout.css +52 -6
  107. package/dist/styles/dialog-layout.css +105 -4
  108. package/dist/styles/fonts.css +29 -20
  109. package/dist/styles/index.css +11 -3
  110. package/dist/styles/layout.css +368 -2
  111. package/dist/styles/logo-layout.css +62 -0
  112. package/dist/styles/navigation-layout.css +43 -0
  113. package/dist/styles/shell-layout.css +505 -62
  114. package/dist/theme/dxs.canonical.css +76 -0
  115. package/dist/tokens/axes.css +39 -0
  116. package/dist/tokens/base.css +5 -0
  117. package/dist/tokens/components/card.css +52 -5
  118. package/dist/tokens/components/chart.css +47 -0
  119. package/dist/tokens/components/control.css +16 -0
  120. package/dist/tokens/components/email.css +66 -0
  121. package/dist/tokens/components/feedback.css +17 -0
  122. package/dist/tokens/components/legal-document.css +67 -0
  123. package/dist/tokens/components/list-row.css +23 -2
  124. package/dist/tokens/components/logo.css +32 -0
  125. package/dist/tokens/components/navigation.css +13 -0
  126. package/dist/tokens/components/sheet.css +19 -0
  127. package/dist/tokens/components/shell.css +149 -1
  128. package/dist/tokens/components/sidebar.css +33 -0
  129. package/dist/tokens/foundation.css +5 -2
  130. package/dist/tokens/semantic/layout.css +44 -2
  131. package/package.json +17 -6
@@ -1,7 +1,14 @@
1
1
  /** Layout component prop types — @see docs/COMPONENTS.md#layout */
2
2
  import type * as React from "react";
3
3
  import type { ComponentType, ReactNode, SVGProps } from "react";
4
- import type { BreadcrumbProp, TitleProp, SubtitleProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, GapProp, ClassNameProp, ChildrenProp } from "../vocabulary/index.js";
4
+ import type { BreadcrumbProp, TitleProp, SubtitleProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, AuthShellPresetProp, BreakpointProp, GapProp, ClassNameProp, ChildrenProp, IdProp } from "../vocabulary/index.js";
5
+ /**
6
+ * Arrangement of the page header's title band and its `extra` slot below the 640px step.
7
+ * `stack` (default) is the historical arrangement — `extra` drops onto its own full-width line
8
+ * under the subtitle. `responsive-inline` keeps `extra` beside the title band at the
9
+ * `--page-header-extra-measure` measure, letting the title/subtitle wrap into what is left.
10
+ */
11
+ export type PageContainerHeaderLayoutProp = "stack" | "responsive-inline";
5
12
  /** @see PageContainer */
6
13
  export type PageContainerProp = {
7
14
  title: TitleProp;
@@ -21,6 +28,14 @@ export type PageContainerProp = {
21
28
  linkComponent?: React.ElementType;
22
29
  density?: PageDensityProp;
23
30
  variant?: PageContainerVariantProp;
31
+ /**
32
+ * How the title band and `extra` share the header row below the 640px step. Defaults to
33
+ * `stack` — the historical arrangement, where `extra` wraps onto its own full-width line under
34
+ * the subtitle. Use `responsive-inline` to keep ONE compact control (a search field, a single
35
+ * primary action) beside the title at 390px, at the token-owned
36
+ * `--page-header-extra-measure`. At >=640px both arrangements are identical.
37
+ */
38
+ headerLayout?: PageContainerHeaderLayoutProp;
24
39
  /** Pin footer to viewport bottom on scroll — pairs well with `variant="narrow"`. */
25
40
  stickyFooter?: boolean;
26
41
  /**
@@ -59,6 +74,52 @@ export type ResponsiveGridColumnsProp = number | {
59
74
  md?: number;
60
75
  lg?: number;
61
76
  };
77
+ export type MasterDetailRailWidthProp = "compact" | "standard";
78
+ export type MasterDetailRailProp = "master" | "detail";
79
+ /**
80
+ * Bounded viewport preset for the master collection. `auto` (default) never bounds it — the
81
+ * region grows with its content, exactly as before. `compact` / `standard` cap its block size
82
+ * with the `--master-detail-master-viewport-*` tokens and scroll the collection inside the
83
+ * region, so a long list cannot push the detail below the fold once the layout stacks.
84
+ */
85
+ export type MasterDetailMasterViewportProp = "auto" | "compact" | "standard";
86
+ /** @see MasterDetail */
87
+ export type MasterDetailProp = {
88
+ /** Selectable collection; always first in DOM order, so the stacked order stays list-then-detail. */
89
+ master: ReactNode;
90
+ /** Detail surface for the current selection. */
91
+ children: ChildrenProp;
92
+ /**
93
+ * Which region is the fixed-width rail; the other one is fluid. Defaults to `detail` — the
94
+ * canonical fluid-list + fixed-detail-rail composition. Use `master` for a leading
95
+ * category/navigator rail beside a fluid detail surface.
96
+ */
97
+ rail?: MasterDetailRailProp;
98
+ /** Rail track width: `compact` = 300px; `standard` = 320px. */
99
+ railWidth?: MasterDetailRailWidthProp;
100
+ /**
101
+ * Bound the master collection to a scrollable viewport instead of letting it grow with its
102
+ * content. `auto` (default) keeps the unbounded behaviour. `compact` (20rem) / `standard`
103
+ * (28rem) read the `--master-detail-master-viewport-*` tokens, scroll the collection INSIDE the
104
+ * region, and make it a keyboard-reachable scroll container. Pair with `masterLabel` so the
105
+ * scroll region is announced.
106
+ */
107
+ masterViewport?: MasterDetailMasterViewportProp;
108
+ /**
109
+ * Stack the two regions below this breakpoint (`false` never stacks). Omit to inherit the
110
+ * themeable `--master-detail-collapse-below` token (default 40rem / the `sm` step).
111
+ */
112
+ collapseBelow?: BreakpointProp | false;
113
+ /** Accessible name for the master region. */
114
+ masterLabel?: string;
115
+ /** Accessible name for the detail region. */
116
+ detailLabel?: string;
117
+ /**
118
+ * Id of the detail region, so the selection controls inside `master` can point at it with
119
+ * `aria-controls` and the app can move focus to it after a selection.
120
+ */
121
+ detailId?: IdProp;
122
+ };
62
123
  /** @see PageContainer.Inset — full-bleed inset region inside the page padding. */
63
124
  export type PageInsetProp = React.HTMLAttributes<HTMLDivElement> & {
64
125
  children?: ChildrenProp;
@@ -76,7 +137,7 @@ export type AppShellProp = {
76
137
  footer?: ReactNode;
77
138
  sidebarCollapsed?: boolean;
78
139
  /**
79
- * Navigation shown in the mobile drawer below the `lg` breakpoint, where the docked sidebar is
140
+ * Navigation shown in the mobile drawer at the DXS 900px breakpoint, where the docked sidebar is
80
141
  * hidden. AppShell OWNS the drawer: it renders a hamburger trigger in the topbar and a focus-
81
142
  * trapped Sheet (Esc + overlay close, focus returns to the trigger) — hiding the sidebar without
82
143
  * a reachable alternative is invalid (gh#165). Defaults to `sidebar`, so the same nav is
@@ -106,6 +167,78 @@ export type AuthShellProp = {
106
167
  brand?: ReactNode;
107
168
  /** Footer slot pinned to the bottom (legal links, locale switch, support). */
108
169
  footer?: ReactNode;
170
+ /**
171
+ * Visual contract for the auth surface. `"canonical"` applies the shared DXS compact geometry
172
+ * (36px controls, 22.5rem card measure, and responsive page insets) through component tokens.
173
+ * Default `"default"` preserves the existing comfortable shell.
174
+ */
175
+ variant?: "default" | "canonical";
176
+ /**
177
+ * Named flow MEASURE — the page geometry contract for one canonical hosted-identity flow: the
178
+ * auth card's max-width plus the desktop and mobile page gutters, all owned by component tokens
179
+ * (`--auth-shell-{device,context,recovery}-*`). Selecting a preset replaces every consumer-side
180
+ * `--auth-shell-card-max-width` override.
181
+ *
182
+ * - `"default"` (default) — the shell's own measure; nothing changes.
183
+ * - `"device-authorization"` — 380px card measure with a 5px inline page gutter at a 390px
184
+ * viewport (canonical device-grant artboard).
185
+ * - `"context-selection"` — 25rem card measure on desktop/tablet, edge-to-edge on mobile, and a
186
+ * tokenized rhythm between the intro, the card and the trailing "remember" row.
187
+ * - `"account-recovery"` — 27rem/432px panel measure with a 15px inline page gutter at 390px
188
+ * (panel x=15, width=360). One measure for BOTH canonical SCR-008 panels: password recovery
189
+ * (request · sent · new-password · expired) and the sign-in MFA challenge (OTP · recovery-code
190
+ * · passkey-failure), whose title and description sit INSIDE the bordered surface.
191
+ *
192
+ * Orthogonal to `variant`: presets are applied AFTER it, so `variant="canonical"` keeps owning
193
+ * control density and heading size while the preset re-measures the page.
194
+ */
195
+ preset?: AuthShellPresetProp;
196
+ /**
197
+ * Vertical density scoped to auth-card descendants. The canonical variant defaults to
198
+ * `"compact"`; the default variant defaults to `"comfortable"`.
199
+ */
200
+ density?: "comfortable" | "compact";
201
+ className?: ClassNameProp;
202
+ };
203
+ /** @see AuthDivider */
204
+ export type AuthDividerProp = {
205
+ /** Short localized conjunction rendered between the two separator rules (for example, "or"). */
206
+ label: string;
207
+ className?: ClassNameProp;
208
+ };
209
+ /**
210
+ * @see AuthFooter — the canonical hosted-identity legal line (host · Terms · Privacy · locale).
211
+ * AuthFooter owns ONLY the geometry: the mono type ramp, the wrap behaviour and the `·`
212
+ * separators between the slots that are actually present (tokens `--auth-footer-*`). Every slot
213
+ * is consumer-owned content — real localized links and a real locale control — so the library
214
+ * never invents navigation. Drop it into `AuthShell`'s `footer` slot (which supplies the
215
+ * `contentinfo` landmark); it renders a plain `div`, so it can also sit inside an existing footer.
216
+ */
217
+ export type AuthFooterProp = {
218
+ /** Product / host identity — the operator of the auth surface (e.g. "GoDX ID"). */
219
+ product: ReactNode;
220
+ /** Terms-of-service link or localized text. */
221
+ terms: ReactNode;
222
+ /** Privacy-policy link or localized text. */
223
+ privacy: ReactNode;
224
+ /** Optional consumer-owned locale control (e.g. `<AppSettingPicker kind="locale" compact />`). */
225
+ locale?: ReactNode;
226
+ className?: ClassNameProp;
227
+ };
228
+ /**
229
+ * @see AuthIdentity — the canonical hosted-identity heading block: the brand-green GoDX mark
230
+ * (`Logo mark="godx"`, independent of `--primary`), the `h1` auth heading, and an optional
231
+ * requesting-client line for delegated flows (device grant, OAuth consent). Centred, token-spaced
232
+ * (`--auth-identity-gap` / `--auth-requester-*`) — a consumer never re-centres or re-spaces it.
233
+ */
234
+ export type AuthIdentityProp = {
235
+ /** Primary auth heading, rendered as the page `h1`. */
236
+ title: ReactNode;
237
+ /**
238
+ * Optional real requesting-client context ("Attendance is requesting sign in"). Pass it ONLY
239
+ * when the client identity is authoritative — never a placeholder.
240
+ */
241
+ requester?: ReactNode;
109
242
  className?: ClassNameProp;
110
243
  };
111
244
  /**
@@ -134,6 +267,15 @@ export type CenteredShellProp = {
134
267
  * wider than AuthShell's 24rem auth card. A service retunes each tier via `--centered-shell-width-*`.
135
268
  */
136
269
  width?: CenteredShellWidthProp;
270
+ /**
271
+ * Block alignment of the centred column inside the `100dvh` shell. `"start"` (default) keeps the
272
+ * top-aligned flowing/scrolling page shape. `"center"` centres the column in the viewport — the
273
+ * SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice) whose full-page
274
+ * geometry must stay package-owned instead of a consumer re-implementing `min-h-dvh` + flex
275
+ * centring. Overflowing content still scrolls from the top (auto block offsets collapse to 0), so
276
+ * a long localized message is never clipped.
277
+ */
278
+ align?: CenteredShellAlignProp;
137
279
  className?: ClassNameProp;
138
280
  };
139
281
  /** @see Sidebar */
@@ -146,14 +288,24 @@ export type SidebarProductProp = {
146
288
  export type SidebarItemProp = {
147
289
  id: string;
148
290
  label: string;
291
+ /**
292
+ * Leading 16px glyph — REQUIRED: the collapsed rail is icon-only and the expanded rail aligns
293
+ * every label to the icon column. Untyped/API-driven data that omits it no longer crashes the
294
+ * shell (the row renders an empty `.sb-icon` slot, keeping the 32px row / 10px gap), but the rail
295
+ * reads as a hole. Its colour is themeable separately from the label via
296
+ * `--sidebar-nav-icon-foreground` (see {@link SidebarProp}).
297
+ */
149
298
  icon: ComponentType<SVGProps<SVGSVGElement>>;
150
299
  badge?: ReactNode;
151
300
  disabled?: boolean;
152
301
  /**
153
- * Render the row as a real anchor (`<a href>`) — the link is the SOLE interactive element (no
154
- * nested `<button>`). Use for MPA links / right-click-open-in-new-tab. Omit for SPA rows that
155
- * report selection via `onSelect(id)`. For a framework router `<Link>`, use `renderItem` instead
156
- * (its returned element is merged as the row via Slot, so there is still no nested interactive).
302
+ * Destination of the row. It is the SOLE interactive element (no nested `<button>`), so
303
+ * right-click / open-in-new-tab / middle-click all work. Omit for SPA rows that only report
304
+ * selection via `onSelect(id)`.
305
+ *
306
+ * With `Sidebar.linkComponent` this same `href` is what the framework router `<Link>` receives —
307
+ * the LIBRARY still composes the row (icon · label · badge · active · collapsed), so a router
308
+ * link never has to reconstruct row markup (gh#213).
157
309
  */
158
310
  href?: string;
159
311
  /** Nested rows — renders a collapsible submenu group (the parent reads active when any child is). */
@@ -161,11 +313,121 @@ export type SidebarItemProp = {
161
313
  };
162
314
  /** @see Sidebar */
163
315
  export type SidebarItemData = SidebarItemProp;
316
+ /**
317
+ * Props the Sidebar hands to `Sidebar.linkComponent` for one nav row (gh#213).
318
+ *
319
+ * Every field is ANCHOR-SAFE — a router `<Link>` may spread the whole object onto its `<a>` without
320
+ * emitting an unknown-DOM-attribute warning. `children` is the LIBRARY-COMPOSED row content (the
321
+ * `.sb-icon` slot, the `.sb-label`, the `.sb-badge`); render it as-is and never rebuild it, which is
322
+ * what makes icons/badges survive a consumer link (the reported production regression).
323
+ */
324
+ export type SidebarLinkProp = {
325
+ /** `SidebarItemProp.href`. Absent for a disabled row — render an inert `<a>` with no navigation. */
326
+ href?: string;
327
+ /** Library-composed row content: icon slot + label + badge (icon only on the collapsed rail). */
328
+ children: ReactNode;
329
+ /** Canonical row class (`sb-nav-item`, plus `sb-nav-item--sub` for a submenu child). */
330
+ className: string;
331
+ /** Present only on the active row — drives `--sidebar-item-active-*`. */
332
+ "data-active"?: "true";
333
+ /** WAI-ARIA current-page semantics for the active row. */
334
+ "aria-current"?: "page";
335
+ /** Set when the item (or its row) is disabled; the row must not navigate. */
336
+ "aria-disabled"?: true;
337
+ /** Accessible name for the icon-only collapsed rail, where the visible label is hidden. */
338
+ "aria-label"?: string;
339
+ /** `"menuitem"` inside the collapsed rail's portaled flyout menu; absent for ordinary rows. */
340
+ role?: "menuitem";
341
+ /** Reports selection to `Sidebar.onSelect` after the router link runs its own handler. */
342
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
343
+ };
344
+ /**
345
+ * A framework router link component driven by {@link SidebarLinkProp} — Inertia's `<Link href>`,
346
+ * a React Router / TanStack link wrapped by `createSidebarLink(Link, "to")`, or any component that
347
+ * renders a single `<a>`. It must forward its `ref` to that anchor so the collapsed rail's Tooltip
348
+ * can anchor to it.
349
+ */
350
+ export type SidebarLinkComponentProp = ComponentType<SidebarLinkProp>;
351
+ /**
352
+ * Row state supplied to the DEPRECATED `Sidebar.renderItem`.
353
+ *
354
+ * @deprecated Prefer `Sidebar.linkComponent` (or `SidebarItem asChild`), where the library composes
355
+ * the row and the consumer supplies only the element. `renderItem` leaves row CONTENT to the
356
+ * consumer, which is how a `<Link>{item.label}</Link>` silently dropped every icon and badge
357
+ * (gh#213). Spreading `rowProps` — including its `children` — now yields the canonical row.
358
+ */
359
+ export type SidebarRenderItemProp = {
360
+ className: string;
361
+ "data-active"?: "true";
362
+ "aria-current"?: "page";
363
+ "aria-disabled"?: true;
364
+ /**
365
+ * Library-composed row content (icon slot · label · badge). Spread `rowProps` onto your element,
366
+ * or render `rowProps.children` explicitly, to keep the canonical row while adding an affix.
367
+ */
368
+ children?: ReactNode;
369
+ };
164
370
  /** @see Sidebar */
165
371
  export type SidebarSectionProp = {
166
372
  label?: string;
167
373
  items: SidebarItemProp[];
168
374
  };
375
+ /** One selectable organization in the public {@link OrgSwitcher} contract. */
376
+ export type OrgSwitcherOrganization = {
377
+ id: string;
378
+ name: string;
379
+ /** Secondary organization context, for example the member's role or tenant identifier. */
380
+ meta?: ReactNode;
381
+ /** Optional owned mark/avatar. When omitted, OrgSwitcher renders the first name character. */
382
+ avatar?: ReactNode;
383
+ /**
384
+ * Status/plan affordance rendered end-aligned in the expanded trigger and in the menu row
385
+ * (e.g. `<Badge tone="warning">Trial</Badge>`). Hidden in the collapsed rail, which only has room
386
+ * for the mark. Pair a non-textual badge with {@link OrgSwitcherOrganization.badgeLabel}.
387
+ */
388
+ badge?: ReactNode;
389
+ /**
390
+ * Localized screen-reader text for `badge`. Required whenever the badge carries meaning the
391
+ * accessible name would otherwise lose (WCAG 1.1.1 / 1.4.1): the trigger's `aria-label` owns its
392
+ * accessible name, so the badge is announced through `aria-describedby` instead. When omitted, a
393
+ * textual badge is still announced inside the menu row but NOT on the trigger.
394
+ */
395
+ badgeLabel?: string;
396
+ disabled?: boolean;
397
+ };
398
+ /** Localized copy owned by the consuming product, never hard-coded by the component. */
399
+ export type OrgSwitcherLabels = {
400
+ trigger: (organizationName: string) => string;
401
+ title: string;
402
+ search: string;
403
+ empty: string;
404
+ loading: string;
405
+ retry?: string;
406
+ };
407
+ /** @see OrgSwitcher */
408
+ export type OrgSwitcherProp = {
409
+ organizations: readonly OrgSwitcherOrganization[];
410
+ value?: string;
411
+ onValueChange?: (value: string) => void;
412
+ collapsed?: boolean;
413
+ disabled?: boolean;
414
+ loading?: boolean;
415
+ /** Error content replaces the list while preserving the trigger and retry affordance. */
416
+ error?: ReactNode;
417
+ onRetry?: () => void;
418
+ labels: OrgSwitcherLabels;
419
+ /**
420
+ * `"auto"` (default) uses the desktop popover above `--sheet-responsive-breakpoint-width` and a
421
+ * focus-trapped bottom Sheet at/below it — the SAME token that drives `SheetContent
422
+ * responsive="auto"`, resolved through the shared `useSheetResponsiveMode()` hook, so a service
423
+ * moves the drawer line once for every overlay instead of per component.
424
+ * Explicit modes are useful for deterministic embedded surfaces and component tests.
425
+ */
426
+ responsive?: "auto" | "popover" | "sheet";
427
+ open?: boolean;
428
+ onOpenChange?: (open: boolean) => void;
429
+ className?: ClassNameProp;
430
+ };
169
431
  /** @see Sidebar */
170
432
  export type SidebarProp = {
171
433
  /** Accessible navigation landmark name; make it unique when multiple sidebars share a document. */
@@ -179,12 +441,39 @@ export type SidebarProp = {
179
441
  collapsed?: boolean;
180
442
  children?: ChildrenProp;
181
443
  /**
182
- * Escape hatch to render a leaf row as a custom element typically a framework router `<Link>`.
183
- * Return a SINGLE interactive element; the Sidebar merges the row styling + active state onto it
184
- * via Slot (so it is the row and the sole interactive element no nested `<button>`). Any
185
- * secondary affix (a star, a count) must be a non-interactive descendant of that element.
444
+ * THE framework-router contract (gh#213). Supply only the LINK ELEMENT TYPE; the Sidebar still
445
+ * composes the row icon slot, label, badge, `data-active`/`aria-current`, the icon-only
446
+ * collapsed rail and its tooltip name and passes it as {@link SidebarLinkProp} `children`.
447
+ * Used for every row that carries an `href`: top-level leaves, submenu children, collapsed-rail
448
+ * leaves and collapsed flyout entries. A group TRIGGER stays a `<button>` (it owns
449
+ * `aria-expanded` disclosure semantics per WAI-ARIA APG); its children take the link.
450
+ *
451
+ * Rows without an `href` keep the `<button>` + `onSelect(id)` shape — a router link with no
452
+ * destination is not a link.
453
+ *
454
+ * @example
455
+ * ```tsx
456
+ * // Inertia — its <Link href> already matches SidebarLinkProp.
457
+ * import { Link } from "@inertiajs/react";
458
+ * <Sidebar linkComponent={inertiaSidebarLink(Link)} sections={sections} activeId={activeId} />
459
+ *
460
+ * // React Router / TanStack — remap `href` to `to`.
461
+ * import { Link } from "react-router-dom";
462
+ * <Sidebar linkComponent={createSidebarLink(Link, "to")} sections={sections} activeId={activeId} />
463
+ * ```
186
464
  */
187
- renderItem?: (item: SidebarItemData) => ReactNode;
465
+ linkComponent?: SidebarLinkComponentProp;
466
+ /**
467
+ * @deprecated Use {@link SidebarProp.linkComponent} (or `SidebarItem asChild`) instead — there the
468
+ * LIBRARY composes the row and you supply only the element, so icons/labels/badges cannot be lost.
469
+ *
470
+ * Legacy escape hatch: return a SINGLE interactive element and the Sidebar merges the row styling
471
+ * + active state onto it via Slot. Because row CONTENT stayed consumer-authored, a
472
+ * `<Link>{item.label}</Link>` silently dropped every icon and badge (the gh#213 production
473
+ * regression). `rowProps` now also carries the composed `children`, so spreading it restores the
474
+ * canonical row.
475
+ */
476
+ renderItem?: (item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode;
188
477
  footer?: ReactNode;
189
478
  /**
190
479
  * Override the nav landmark's accessible name. Defaults to a localized "Main navigation".
@@ -211,3 +500,77 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
211
500
  /** Escape hatch — render fully custom bar content instead of the three slots. */
212
501
  children?: ReactNode;
213
502
  };
503
+ /**
504
+ * @see LegalDocumentShell — one entry of the table of contents + the matching document section.
505
+ * `id` is the REAL anchor target (`href="#{id}"`, `<section id>`), so it must be unique on the page
506
+ * and URL-safe: it is what a deep link, a hash jump and `aria-current` all key off.
507
+ */
508
+ export type LegalDocumentSectionProp = {
509
+ /** URL-safe anchor id — the `<section id>` AND the contents `href="#…"` target. */
510
+ id: string;
511
+ /** Section heading text — rendered as an `<h2>` AND reused as the contents-list label. */
512
+ title: string;
513
+ /** Section body. Consumer-owned legal copy: paragraphs, lists, tables, nested `<h3>`s. */
514
+ content: ReactNode;
515
+ };
516
+ /**
517
+ * @see LegalDocumentShell — the long-form legal/policy document surface (terms of service, privacy
518
+ * policy, DPA, cookie policy, SLA, EULA) with a table of contents.
519
+ *
520
+ * It owns the parts an app must NOT re-implement: the readable measure + top-aligned document
521
+ * geometry, the sticky contents rail (single-column compact block below 56rem), scroll-spy
522
+ * active-section tracking, hash deep-linking with a token-driven scroll offset, focus handoff to
523
+ * the target `<section>`, and `prefers-reduced-motion`-aware smooth scrolling. All legal TEXT stays
524
+ * owned by the consumer — the shell only receives it through `sections` and the slots.
525
+ *
526
+ * Semantics: `<article>` labelled by the document title · a NAMED `<nav>` for the contents · REAL
527
+ * `<a href="#…">` anchors carrying `aria-current="location"` · one `<section>` per entry, labelled
528
+ * by its `<h2>`.
529
+ */
530
+ export type LegalDocumentShellProp = {
531
+ /** Document title — the `<h1>` that names the `<article>` (e.g. "Terms of Service"). */
532
+ title: TitleProp;
533
+ /**
534
+ * Document version identifier (e.g. `"2.4"`). Rendered as a localized "Version {version}" line —
535
+ * pass the bare identifier, never a pre-localized sentence.
536
+ */
537
+ version?: string;
538
+ /**
539
+ * Effective date as an **ISO 8601** calendar date (`yyyy-MM-dd`) or a full ISO instant. Formatted
540
+ * for display with `Intl.DateTimeFormat` in the active locale and emitted inside a
541
+ * `<time dateTime={effectiveDate}>`, so the machine-readable value is always the ISO input.
542
+ * NEVER pass a pre-formatted string.
543
+ */
544
+ effectiveDate?: string;
545
+ /** Short plain-language summary rendered under the metadata, above the contents. */
546
+ summary?: ReactNode;
547
+ /**
548
+ * Accessible name + visible caption of the contents `<nav>` (e.g. "Contents"). Defaults to a
549
+ * localized "Contents"; override it when two documents render in the same view, so the two `nav`
550
+ * landmarks stay distinguishable (axe `landmark-unique`, WCAG 2.4.1).
551
+ */
552
+ contentsLabel?: string;
553
+ /** The document's sections, in reading order. Drives BOTH the contents list and the body. */
554
+ sections: LegalDocumentSectionProp[];
555
+ /**
556
+ * Controlled active section id (the entry marked `aria-current="location"`). Pair it with
557
+ * `onActiveSectionChange`; omit both for the uncontrolled form.
558
+ */
559
+ activeSection?: string;
560
+ /** Uncontrolled initial active section id. Defaults to the first section. */
561
+ defaultActiveSection?: string;
562
+ /**
563
+ * Fires whenever the active section changes — on a contents-anchor activation, on an initial
564
+ * hash deep link, and continuously from the scroll spy as the reader moves through the document.
565
+ */
566
+ onActiveSectionChange?: (sectionId: string) => void;
567
+ /**
568
+ * Slot above the contents list in the rail — a document switcher across the legal set
569
+ * (Terms · Privacy · Cookies). Rendered as a plain wrapper, so the consumer owns its semantics.
570
+ */
571
+ documentNavigation?: ReactNode;
572
+ /** Slot below the last section — the accept/download/print/contact actions. */
573
+ footerAction?: ReactNode;
574
+ id?: IdProp;
575
+ className?: ClassNameProp;
576
+ };
@@ -0,0 +1 @@
1
+ "use client";
@@ -1,6 +1,12 @@
1
1
  /** Navigation component prop types — @see docs/COMPONENTS.md#navigation */
2
2
  import type * as React from "react";
3
- import type { ChildrenProp, ClassNameProp, DisabledProp, HasActiveFiltersProp, LabelProp, OnClearFiltersProp, StickyProp } from "../vocabulary/index.js";
3
+ import type { ChildrenProp, ClassNameProp, DisabledProp, HasActiveFiltersProp, IdProp, LabelProp, OnClearFiltersProp, StickyProp } from "../vocabulary/index.js";
4
+ /**
5
+ * How a {@link ToolbarProp} strip resolves more filters than fit one row (#216).
6
+ * `wrap` (default) stacks on narrow viewports then wraps onto extra rows; `scroll` keeps ONE
7
+ * bounded row that scrolls inline, so a wide filter set never pushes the list below the fold.
8
+ */
9
+ export type FilterBarOverflowProp = "wrap" | "scroll";
4
10
  /** @see Toolbar */
5
11
  export type ToolbarProp = {
6
12
  onClear?: OnClearFiltersProp;
@@ -12,12 +18,24 @@ export type ToolbarProp = {
12
18
  * `--filter-bar-sticky-background` theme knobs.
13
19
  */
14
20
  sticky?: StickyProp;
21
+ /**
22
+ * Responsive overflow strategy (#216). Default `wrap`. Use `scroll` for list pages with many
23
+ * filters (long JA/EN/VI labels) where a wrapped 3-row strip would push the table off screen.
24
+ * Tune the scrollbar gutter with the `--filter-bar-scroll-padding-y` theme knob.
25
+ */
26
+ overflow?: FilterBarOverflowProp;
15
27
  className?: ClassNameProp;
16
28
  children: ChildrenProp;
17
29
  };
18
30
  /** @see ToolbarGroup */
19
31
  export type ToolbarGroupProp = {
20
32
  label: LabelProp;
33
+ /**
34
+ * `id` of the single control this group labels (#216). When set, the visible group label is
35
+ * rendered as that control's real `<label htmlFor>`, so the filter is named by the text the
36
+ * user sees (WCAG 2.5.3 / 1.3.1) — otherwise the control needs its own `aria-label`.
37
+ */
38
+ controlId?: IdProp;
21
39
  className?: ClassNameProp;
22
40
  children: ChildrenProp;
23
41
  };