@godxjp/ui 18.4.0 → 18.6.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 (159) hide show
  1. package/README.md +16 -1
  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 +30 -0
  9. package/dist/components/charts/compact-bar-trend.js +74 -0
  10. package/dist/components/charts/index.d.ts +6 -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 +40 -4
  15. package/dist/components/data-display/data-table.js +69 -2
  16. package/dist/components/data-display/empty-state.js +1 -1
  17. package/dist/components/data-display/index.d.ts +4 -2
  18. package/dist/components/data-display/index.js +10 -1
  19. package/dist/components/data-display/list-row.d.ts +41 -4
  20. package/dist/components/data-display/list-row.js +23 -3
  21. package/dist/components/data-display/qr-code.d.ts +1 -1
  22. package/dist/components/data-display/service-launcher-card.d.ts +40 -0
  23. package/dist/components/data-display/service-launcher-card.js +97 -0
  24. package/dist/components/data-display/table.d.ts +47 -2
  25. package/dist/components/data-display/table.js +29 -4
  26. package/dist/components/data-entry/command-palette.d.ts +37 -0
  27. package/dist/components/data-entry/command-palette.js +125 -0
  28. package/dist/components/data-entry/command.d.ts +10 -10
  29. package/dist/components/data-entry/index.d.ts +3 -0
  30. package/dist/components/data-entry/index.js +2 -0
  31. package/dist/components/data-entry/input-otp.d.ts +1 -0
  32. package/dist/components/feedback/dialog.d.ts +16 -1
  33. package/dist/components/feedback/dialog.js +46 -33
  34. package/dist/components/feedback/index.d.ts +5 -2
  35. package/dist/components/feedback/index.js +8 -2
  36. package/dist/components/feedback/sheet.d.ts +26 -4
  37. package/dist/components/feedback/sheet.js +54 -7
  38. package/dist/components/feedback/two-factor-setup.d.ts +31 -0
  39. package/dist/components/feedback/two-factor-setup.js +97 -0
  40. package/dist/components/general/index.d.ts +1 -1
  41. package/dist/components/general/logo.d.ts +49 -9
  42. package/dist/components/general/logo.js +67 -14
  43. package/dist/components/layout/app-shell.d.ts +1 -1
  44. package/dist/components/layout/app-shell.js +55 -30
  45. package/dist/components/layout/auth-account-summary.d.ts +5 -0
  46. package/dist/components/layout/auth-account-summary.js +31 -0
  47. package/dist/components/layout/auth-divider.d.ts +7 -0
  48. package/dist/components/layout/auth-divider.js +21 -0
  49. package/dist/components/layout/auth-footer.d.ts +5 -0
  50. package/dist/components/layout/auth-footer.js +24 -0
  51. package/dist/components/layout/auth-identity.d.ts +5 -0
  52. package/dist/components/layout/auth-identity.js +17 -0
  53. package/dist/components/layout/auth-shell.d.ts +9 -1
  54. package/dist/components/layout/auth-shell.js +25 -6
  55. package/dist/components/layout/auth-stack.d.ts +4 -0
  56. package/dist/components/layout/auth-stack.js +8 -0
  57. package/dist/components/layout/centered-shell.d.ts +5 -2
  58. package/dist/components/layout/centered-shell.js +29 -19
  59. package/dist/components/layout/error-surface.d.ts +35 -0
  60. package/dist/components/layout/error-surface.js +155 -0
  61. package/dist/components/layout/flex.d.ts +1 -1
  62. package/dist/components/layout/flex.js +4 -0
  63. package/dist/components/layout/index.d.ts +23 -3
  64. package/dist/components/layout/index.js +21 -2
  65. package/dist/components/layout/legal-document-shell.d.ts +34 -0
  66. package/dist/components/layout/legal-document-shell.js +186 -0
  67. package/dist/components/layout/master-detail.d.ts +24 -0
  68. package/dist/components/layout/master-detail.js +48 -0
  69. package/dist/components/layout/org-switcher.d.ts +9 -0
  70. package/dist/components/layout/org-switcher.js +218 -0
  71. package/dist/components/layout/page-container.d.ts +1 -1
  72. package/dist/components/layout/page-container.js +7 -2
  73. package/dist/components/layout/sidebar-link.d.ts +43 -0
  74. package/dist/components/layout/sidebar-link.js +13 -0
  75. package/dist/components/layout/sidebar.d.ts +42 -5
  76. package/dist/components/layout/sidebar.js +170 -85
  77. package/dist/components/navigation/app-setting-picker.js +17 -6
  78. package/dist/components/navigation/filter-bar.d.ts +12 -3
  79. package/dist/components/navigation/filter-bar.js +8 -2
  80. package/dist/components/navigation/index.d.ts +2 -2
  81. package/dist/components/navigation/index.js +3 -1
  82. package/dist/components/navigation/steps.js +38 -19
  83. package/dist/components/navigation/tabs-scroll.d.ts +70 -0
  84. package/dist/components/navigation/tabs-scroll.js +76 -0
  85. package/dist/components/navigation/tabs.js +47 -23
  86. package/dist/components/ui/avatar.d.ts +2 -1
  87. package/dist/components/ui/avatar.js +2 -1
  88. package/dist/components/ui/input-otp.d.ts +4 -1
  89. package/dist/components/ui/input-otp.js +2 -1
  90. package/dist/components/ui/toggle-group.d.ts +7 -4
  91. package/dist/components/ui/toggle-group.js +32 -21
  92. package/dist/email/brand-mark.d.ts +108 -0
  93. package/dist/email/brand-mark.js +88 -0
  94. package/dist/email/color.d.ts +53 -0
  95. package/dist/email/color.js +37 -0
  96. package/dist/email/geometry.d.ts +120 -0
  97. package/dist/email/geometry.js +90 -0
  98. package/dist/email/index.d.ts +83 -0
  99. package/dist/email/index.js +82 -0
  100. package/dist/email/inline-style.d.ts +27 -0
  101. package/dist/email/inline-style.js +26 -0
  102. package/dist/email/tokens.generated.d.ts +167 -0
  103. package/dist/email/tokens.generated.js +162 -0
  104. package/dist/email/urgency.d.ts +22 -0
  105. package/dist/email/urgency.js +14 -0
  106. package/dist/i18n/messages/en.json +36 -3
  107. package/dist/i18n/messages/ja.json +36 -3
  108. package/dist/i18n/messages/vi.json +36 -3
  109. package/dist/inertia/index.d.ts +50 -0
  110. package/dist/inertia/index.js +5 -0
  111. package/dist/props/components/app.prop.d.ts +11 -1
  112. package/dist/props/components/charts.prop.d.ts +40 -1
  113. package/dist/props/components/data-display.prop.d.ts +75 -1
  114. package/dist/props/components/data-entry.prop.d.ts +6 -0
  115. package/dist/props/components/feedback.prop.d.ts +10 -0
  116. package/dist/props/components/index.d.ts +3 -3
  117. package/dist/props/components/layout.prop.d.ts +560 -10
  118. package/dist/props/components/layout.prop.js +1 -0
  119. package/dist/props/components/navigation.prop.d.ts +24 -2
  120. package/dist/props/registry.d.ts +433 -8
  121. package/dist/props/registry.js +527 -9
  122. package/dist/props/vocabulary/data.prop.d.ts +27 -0
  123. package/dist/props/vocabulary/index.d.ts +3 -3
  124. package/dist/props/vocabulary/interaction.prop.d.ts +10 -1
  125. package/dist/props/vocabulary/layout.prop.d.ts +49 -0
  126. package/dist/styles/base.css +8 -2
  127. package/dist/styles/card-layout.css +161 -7
  128. package/dist/styles/chart-layout.css +97 -0
  129. package/dist/styles/control.css +76 -2
  130. package/dist/styles/data-display-layout.css +78 -6
  131. package/dist/styles/dialog-layout.css +105 -4
  132. package/dist/styles/fonts.css +15 -4
  133. package/dist/styles/index.css +11 -3
  134. package/dist/styles/layout.css +466 -1
  135. package/dist/styles/logo-layout.css +95 -0
  136. package/dist/styles/navigation-layout.css +152 -0
  137. package/dist/styles/shell-layout.css +790 -40
  138. package/dist/styles/table-layout.css +164 -1
  139. package/dist/theme/dxs.canonical.css +77 -0
  140. package/dist/tokens/axes.css +40 -0
  141. package/dist/tokens/base.css +6 -0
  142. package/dist/tokens/components/card.css +48 -0
  143. package/dist/tokens/components/chart.css +47 -0
  144. package/dist/tokens/components/control.css +16 -0
  145. package/dist/tokens/components/data-display.css +13 -0
  146. package/dist/tokens/components/email.css +93 -0
  147. package/dist/tokens/components/error-surface.css +36 -0
  148. package/dist/tokens/components/feedback.css +17 -0
  149. package/dist/tokens/components/legal-document.css +67 -0
  150. package/dist/tokens/components/list-row.css +43 -2
  151. package/dist/tokens/components/logo.css +74 -0
  152. package/dist/tokens/components/navigation.css +28 -0
  153. package/dist/tokens/components/sheet.css +19 -0
  154. package/dist/tokens/components/shell.css +210 -0
  155. package/dist/tokens/components/sidebar.css +33 -0
  156. package/dist/tokens/components/table.css +45 -0
  157. package/dist/tokens/foundation.css +31 -2
  158. package/dist/tokens/semantic/layout.css +69 -2
  159. package/package.json +22 -5
@@ -1,7 +1,29 @@
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, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, BreakpointProp, GapProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp } from "../vocabulary/index.js";
5
+ import type { EmptyStateToneProp } from "./data-display.prop.js";
6
+ /**
7
+ * Arrangement of the page header's title band and its `extra` slot below the 640px step.
8
+ * `stack` (default) is the historical arrangement — `extra` drops onto its own full-width line
9
+ * under the subtitle. `responsive-inline` keeps `extra` beside the title band at the
10
+ * `--page-header-extra-measure` measure, letting the title/subtitle wrap into what is left.
11
+ */
12
+ export type PageContainerHeaderLayoutProp = "stack" | "responsive-inline";
13
+ /** Whole-page semantic composition owned by PageContainer. */
14
+ export type PageContainerPresetProp = "default" | "admin-collection";
15
+ /**
16
+ * Bounded page MEASURE — the shared inline cap applied to the page header AND body together, so
17
+ * the header `extra` action ends on the same edge as the body surface. Orthogonal to
18
+ * `PageContainerVariantProp` (chrome) and to `PageContainerHeaderLayoutProp`, so a quiet
19
+ * `variant="ghost"` feed can finally have a bounded measure too (gh#245 / gh#247).
20
+ *
21
+ * `default` applies NO cap — the page is fluid exactly as before. `narrow` / `medium` read the
22
+ * `--page-measure-{narrow,medium}` tokens (42rem / 48rem OUTER, i.e. 624px / 720px VISIBLE surface
23
+ * once the package-owned page gutters are subtracted). Both are maxes, so a compact viewport stays
24
+ * fluid at the compact gutter.
25
+ */
26
+ export type PageContainerMeasureProp = "default" | "narrow" | "medium";
5
27
  /** @see PageContainer */
6
28
  export type PageContainerProp = {
7
29
  title: TitleProp;
@@ -21,6 +43,29 @@ export type PageContainerProp = {
21
43
  linkComponent?: React.ElementType;
22
44
  density?: PageDensityProp;
23
45
  variant?: PageContainerVariantProp;
46
+ /**
47
+ * Whole-page composition contract. `admin-collection` sets the header-to-toolbar rhythm,
48
+ * collection search measure, control height and table density once for the entire subtree.
49
+ * Geometry remains token-owned and service-themeable; no child needs a sizing override.
50
+ */
51
+ preset?: PageContainerPresetProp;
52
+ /**
53
+ * How the title band and `extra` share the header row below the 640px step. Defaults to
54
+ * `stack` — the historical arrangement, where `extra` wraps onto its own full-width line under
55
+ * the subtitle. Use `responsive-inline` to keep ONE compact control (a search field, a single
56
+ * primary action) beside the title at 390px, at the token-owned
57
+ * `--page-header-extra-measure`. At >=640px both arrangements are identical.
58
+ */
59
+ headerLayout?: PageContainerHeaderLayoutProp;
60
+ /**
61
+ * Bounded page measure shared by the header and the body. Defaults to `default` — no cap, the
62
+ * historical fluid page. `narrow` (624px surface) / `medium` (720px surface) cap BOTH bands to
63
+ * one token-owned measure (`--page-measure-{narrow,medium}`), so a header action ends flush with
64
+ * the body surface instead of at the page edge. Orthogonal to `variant`, so `variant="ghost"`
65
+ * quiet chrome composes with a bounded measure (gh#245 / gh#247). Unlike `variant="narrow"`,
66
+ * which caps only the body.
67
+ */
68
+ measure?: PageContainerMeasureProp;
24
69
  /** Pin footer to viewport bottom on scroll — pairs well with `variant="narrow"`. */
25
70
  stickyFooter?: boolean;
26
71
  /**
@@ -53,12 +98,72 @@ export type FlexProp = React.HTMLAttributes<HTMLDivElement> & {
53
98
  align?: FlexAlignProp;
54
99
  justify?: FlexJustifyProp;
55
100
  wrap?: boolean;
101
+ /**
102
+ * Drop this region below a breakpoint step (`sm` 40rem · `md` 48rem · `lg` 64rem · `xl` 80rem).
103
+ * The ONE public way to make a layout region responsive without a page-local media query — a
104
+ * public header hides its anchor navigation below the tablet step with `hideBelow="md"` instead
105
+ * of a consumer `@media` rule (gh#252). Omit (the default) and no attribute is emitted, so no
106
+ * rule can match and the Flex is unchanged. The region is removed from the accessibility tree
107
+ * too, so keep its destinations reachable elsewhere at that width (a footer nav).
108
+ */
109
+ hideBelow?: BreakpointProp;
110
+ /**
111
+ * The inverse of `hideBelow` — drop this region FROM a breakpoint step upwards, i.e. keep it
112
+ * only on the narrow side (a compact-only affordance). Omit for no attribute and no rule.
113
+ */
114
+ hideFrom?: BreakpointProp;
56
115
  };
57
116
  export type ResponsiveGridColumnsProp = number | {
58
117
  sm?: number;
59
118
  md?: number;
60
119
  lg?: number;
61
120
  };
121
+ export type MasterDetailRailWidthProp = "compact" | "standard";
122
+ export type MasterDetailRailProp = "master" | "detail";
123
+ /**
124
+ * Bounded viewport preset for the master collection. `auto` (default) never bounds it — the
125
+ * region grows with its content, exactly as before. `compact` / `standard` cap its block size
126
+ * with the `--master-detail-master-viewport-*` tokens and scroll the collection inside the
127
+ * region, so a long list cannot push the detail below the fold once the layout stacks.
128
+ */
129
+ export type MasterDetailMasterViewportProp = "auto" | "compact" | "standard";
130
+ /** @see MasterDetail */
131
+ export type MasterDetailProp = {
132
+ /** Selectable collection; always first in DOM order, so the stacked order stays list-then-detail. */
133
+ master: ReactNode;
134
+ /** Detail surface for the current selection. */
135
+ children: ChildrenProp;
136
+ /**
137
+ * Which region is the fixed-width rail; the other one is fluid. Defaults to `detail` — the
138
+ * canonical fluid-list + fixed-detail-rail composition. Use `master` for a leading
139
+ * category/navigator rail beside a fluid detail surface.
140
+ */
141
+ rail?: MasterDetailRailProp;
142
+ /** Rail track width: `compact` = 300px; `standard` = 320px. */
143
+ railWidth?: MasterDetailRailWidthProp;
144
+ /**
145
+ * Bound the master collection to a scrollable viewport instead of letting it grow with its
146
+ * content. `auto` (default) keeps the unbounded behaviour. `compact` (20rem) / `standard`
147
+ * (28rem) read the `--master-detail-master-viewport-*` tokens, scroll the collection INSIDE the
148
+ * region, and make it a keyboard-reachable scroll container. Pair with `masterLabel` so the
149
+ * scroll region is announced.
150
+ */
151
+ masterViewport?: MasterDetailMasterViewportProp;
152
+ /**
153
+ * Stack the two regions below this breakpoint (`false` never stacks). Omit to inherit the
154
+ * themeable `--master-detail-collapse-below` token (default 40rem / the `sm` step).
155
+ */
156
+ collapseBelow?: BreakpointProp | false;
157
+ /** Accessible name for the master region. */
158
+ masterLabel?: string;
159
+ /** Accessible name for the detail region. */
160
+ detailLabel?: string;
161
+ /**
162
+ * Id of the detail region, so the selection controls inside `master` can point at it with
163
+ * `aria-controls` and the app can move focus to it after a selection.
164
+ */
165
+ detailId?: IdProp;
166
+ };
62
167
  /** @see PageContainer.Inset — full-bleed inset region inside the page padding. */
63
168
  export type PageInsetProp = React.HTMLAttributes<HTMLDivElement> & {
64
169
  children?: ChildrenProp;
@@ -75,6 +180,17 @@ export type AppShellProp = {
75
180
  breadcrumb?: ReactNode;
76
181
  footer?: ReactNode;
77
182
  sidebarCollapsed?: boolean;
183
+ /**
184
+ * Responsive navigation strategy below the canonical 900px shell breakpoint.
185
+ *
186
+ * - `"drawer"` (default) hides the docked sidebar and exposes the accessible mobile Sheet.
187
+ * - `"docked"` keeps the sidebar grid track, footer/account region and active navigation in the
188
+ * shell at narrow widths. The sidebar width remains owned by `--app-shell-sidebar-width`.
189
+ *
190
+ * Use `"docked"` only when the product's approved responsive contract explicitly retains the
191
+ * rail; it intentionally suppresses the redundant mobile drawer trigger.
192
+ */
193
+ responsiveNavigation?: "drawer" | "docked";
78
194
  /**
79
195
  * Navigation shown in the mobile drawer at the DXS 900px breakpoint, where the docked sidebar is
80
196
  * hidden. AppShell OWNS the drawer: it renders a hamburger trigger in the topbar and a focus-
@@ -106,6 +222,97 @@ export type AuthShellProp = {
106
222
  brand?: ReactNode;
107
223
  /** Footer slot pinned to the bottom (legal links, locale switch, support). */
108
224
  footer?: ReactNode;
225
+ /**
226
+ * Visual contract for the auth surface. `"canonical"` applies the shared DXS compact geometry
227
+ * (36px controls, 22.5rem card measure, and responsive page insets) through component tokens.
228
+ * Default `"default"` preserves the existing comfortable shell.
229
+ */
230
+ variant?: "default" | "canonical";
231
+ /**
232
+ * Named flow MEASURE — the page geometry contract for one canonical hosted-identity flow: the
233
+ * auth card's max-width plus the desktop and mobile page gutters, all owned by component tokens
234
+ * (`--auth-shell-{login,device,context,recovery}-*`). Selecting a preset replaces every
235
+ * consumer-side geometry override.
236
+ *
237
+ * - `"default"` (default) — the shell's own measure; nothing changes.
238
+ * - `"login"` — SCR-001's 360px card at x=540/332/15 and y=363/363/353 for the canonical
239
+ * 1440x900, 1024x900 and 390x844 viewports. The identity occupies a package-owned anchor slot,
240
+ * so standalone, one-line requester and wrapped two-line requester states keep the same card
241
+ * position without truncating or inventing requester data. Pass AuthIdentity, Card and
242
+ * AuthFooter as direct children (an anchor may wrap AuthIdentity).
243
+ * - `"device-authorization"` — 380px card measure with a 5px inline page gutter at a 390px
244
+ * viewport (canonical device-grant artboard).
245
+ * - `"context-selection"` — 25rem card measure on desktop/tablet, edge-to-edge on mobile, and a
246
+ * tokenized rhythm between the intro, the card and the trailing "remember" row.
247
+ * - `"account-recovery"` — 27rem/432px panel measure with a 15px inline page gutter at 390px
248
+ * (panel x=15, width=360). One measure for BOTH canonical SCR-008 panels: password recovery
249
+ * (request · sent · new-password · expired) and the sign-in MFA challenge (OTP · recovery-code
250
+ * · passkey-failure), whose title and description sit INSIDE the bordered surface.
251
+ *
252
+ * Orthogonal to `variant`: presets are applied AFTER it, so `variant="canonical"` keeps owning
253
+ * control density and heading size while the preset re-measures/anchors the page.
254
+ */
255
+ preset?: AuthShellPresetProp;
256
+ /**
257
+ * Vertical density scoped to auth-card descendants. The canonical variant defaults to
258
+ * `"compact"`; the default variant defaults to `"comfortable"`.
259
+ */
260
+ density?: "comfortable" | "compact";
261
+ className?: ClassNameProp;
262
+ };
263
+ /** @see AuthDivider */
264
+ export type AuthDividerProp = {
265
+ /** Short localized conjunction rendered between the two separator rules (for example, "or"). */
266
+ label: string;
267
+ className?: ClassNameProp;
268
+ };
269
+ /**
270
+ * @see AuthFooter — the canonical hosted-identity legal line (host · Terms · Privacy · locale).
271
+ * AuthFooter owns ONLY the geometry: the mono type ramp, the wrap behaviour and the `·`
272
+ * separators between the slots that are actually present (tokens `--auth-footer-*`). Every slot
273
+ * is consumer-owned content — real localized links and a real locale control — so the library
274
+ * never invents navigation. Drop it into `AuthShell`'s `footer` slot (which supplies the
275
+ * `contentinfo` landmark); it renders a plain `div`, so it can also sit inside an existing footer.
276
+ */
277
+ export type AuthFooterProp = {
278
+ /** Product / host identity — the operator of the auth surface (e.g. "GoDX ID"). */
279
+ product: ReactNode;
280
+ /** Terms-of-service link or localized text. */
281
+ terms: ReactNode;
282
+ /** Privacy-policy link or localized text. */
283
+ privacy: ReactNode;
284
+ /** Optional consumer-owned locale control (e.g. `<AppSettingPicker kind="locale" compact />`). */
285
+ locale?: ReactNode;
286
+ className?: ClassNameProp;
287
+ };
288
+ /**
289
+ * @see AuthIdentity — the canonical hosted-identity heading block: the brand-green GoDX mark
290
+ * (`Logo mark="godx"`, independent of `--primary`), the `h1` auth heading, and an optional
291
+ * requesting-client line for delegated flows (device grant, OAuth consent). Centred, token-spaced
292
+ * (`--auth-identity-gap` / `--auth-requester-*`) — a consumer never re-centres or re-spaces it.
293
+ */
294
+ export type AuthIdentityProp = {
295
+ /** Primary auth heading, rendered as the page `h1`. */
296
+ title: ReactNode;
297
+ /**
298
+ * Optional real requesting-client context ("Attendance is requesting sign in"). Pass it ONLY
299
+ * when the client identity is authoritative — never a placeholder.
300
+ */
301
+ requester?: ReactNode;
302
+ className?: ClassNameProp;
303
+ };
304
+ /**
305
+ * @see AuthAccountSummary — compact signed-in identity row for hosted authentication surfaces.
306
+ * It owns avatar fallback, bidi-safe email truncation and the keyboard action geometry; the
307
+ * consumer owns the authoritative email, localized action label and navigation handler.
308
+ */
309
+ export type AuthAccountSummaryProp = {
310
+ email: string;
311
+ avatarSrc?: string;
312
+ avatarFallback?: ReactNode;
313
+ actionLabel: ReactNode;
314
+ onAction: () => void;
315
+ disabled?: DisabledProp;
109
316
  className?: ClassNameProp;
110
317
  };
111
318
  /**
@@ -134,6 +341,128 @@ export type CenteredShellProp = {
134
341
  * wider than AuthShell's 24rem auth card. A service retunes each tier via `--centered-shell-width-*`.
135
342
  */
136
343
  width?: CenteredShellWidthProp;
344
+ /**
345
+ * Block alignment of the centred column inside the `100dvh` shell. `"start"` (default) keeps the
346
+ * top-aligned flowing/scrolling page shape. `"center"` centres the column in the viewport — the
347
+ * SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice) whose full-page
348
+ * geometry must stay package-owned instead of a consumer re-implementing `min-h-dvh` + flex
349
+ * centring. Overflowing content still scrolls from the top (auto block offsets collapse to 0), so
350
+ * a long localized message is never clipped.
351
+ */
352
+ align?: CenteredShellAlignProp;
353
+ /**
354
+ * Whole-page shell contract. `"default"` (the default) emits no attribute and keeps the shell's
355
+ * exact box. `"public-landing"` owns the PUBLIC landing geometry — one content measure shared by
356
+ * the header bar, the centred column and the footer, the section rhythm, the flat public-surface
357
+ * card chrome and the hero `h1` tier — from `--centered-shell-landing-*` tokens, so a landing
358
+ * composition (header · hero · sections · legal footer) needs no page-local CSS and no descendant
359
+ * selector against shell internals (gh#252).
360
+ */
361
+ preset?: CenteredShellPresetProp;
362
+ className?: ClassNameProp;
363
+ };
364
+ /**
365
+ * @see ErrorSurface — the optional maintenance / planned-outage timing slot (503, occasionally a
366
+ * planned 500).
367
+ *
368
+ * `start` / `end` are **ISO-8601 instants** and `timeZone` an **IANA** zone id: the surface formats
369
+ * them with `Intl.DateTimeFormat(locale, …).formatRange()` (CLDR), so ja / en / vi each read
370
+ * natively. NEVER pass a pre-formatted string like `"18:00 - 20:00 JST"` — it cannot localize, and
371
+ * the machine-readable value is what lands in `<time dateTime>`.
372
+ *
373
+ * `progress` is server-sent on purpose: deriving "how far through the window are we" from the
374
+ * client clock makes SSR and hydration disagree, and an exception page must be readable before
375
+ * hydration.
376
+ */
377
+ export type ErrorSurfaceMaintenanceProp = {
378
+ /** Window start as an ISO-8601 instant (`2026-08-02T18:00:00Z`). Also the `<time dateTime>` value. */
379
+ start: string;
380
+ /** Window end as an ISO-8601 instant. Omit for an open-ended outage — a single instant is shown. */
381
+ end?: string;
382
+ /**
383
+ * IANA time zone id (`Asia/Tokyo`) the window is presented in. Omit to use the runtime zone —
384
+ * pass it explicitly whenever the page is server-rendered, or SSR and client output diverge.
385
+ */
386
+ timeZone?: string;
387
+ /**
388
+ * Completion of the maintenance window as a **percentage 0–100**, rendered as a labelled
389
+ * `Progress` meter. Server-sent (see above); omit for an outage with no published progress.
390
+ */
391
+ progress?: number;
392
+ };
393
+ /**
394
+ * @see ErrorSurface — the package-owned semantic exception surface for 403 / 404 / 500 / 503.
395
+ *
396
+ * The `mode` is the SHELL CONTRACT, not a skin:
397
+ * - `mode="application"` (403/404) renders the surface as the **body** you put inside the
398
+ * `AppShell` the route already provides (normally within a `PageContainer`). It deliberately does
399
+ * NOT reconstruct navigation chrome: the sidebar, topbar and user menu are consumer-owned data,
400
+ * so the surface preserves the shell it is placed in instead of manufacturing a fake one.
401
+ * - `mode="system"` (500/503) owns the whole page: it renders `CenteredShell align="center"`, so
402
+ * the viewport-centred geometry at 1440 / 1024 / 390 stays package-owned and a consumer never
403
+ * writes `min-h-dvh`, a flex-centring class or a media query.
404
+ *
405
+ * `action` is **exactly one** recovery action, enforced structurally by a single slot (a second
406
+ * element is dropped with a development error). Support contact belongs in `description`, not in a
407
+ * second CTA.
408
+ *
409
+ * All product COPY stays consumer-owned (`title` / `description` / `action` come from the app's own
410
+ * `t()`); the surface owns only its own metadata labels, which it localizes itself.
411
+ */
412
+ export type ErrorSurfaceProp = {
413
+ /** Where the surface lives — `application` = AppShell body (403/404), `system` = own page (500/503). */
414
+ mode: ErrorSurfaceModeProp;
415
+ /** HTTP status presented. Drives the default `icon`, `tone` and the rendered status code. */
416
+ status: ErrorSurfaceStatusProp;
417
+ /** Headline. Consumer-owned copy from the app's `t()` — the library ships no product text. */
418
+ title: TitleProp;
419
+ /** Supporting sentence under the title. Put support-contact guidance here, never in a 2nd CTA. */
420
+ description?: DescriptionProp;
421
+ /**
422
+ * The ONE recovery action (a `Button`, or a `Button asChild` wrapping a router `Link`). A single
423
+ * slot IS the enforcement: pass more than one element and only the first renders, with a
424
+ * development-time error.
425
+ */
426
+ action: ActionProp;
427
+ /** Override the status-derived icon (403 ShieldAlert · 404 SearchX · 500 ServerCrash · 503 Wrench). */
428
+ icon?: IconProp;
429
+ /** Override the status-derived tone (403/503 `warning` · 404 `muted` · 500 `destructive`). */
430
+ tone?: EmptyStateToneProp;
431
+ /**
432
+ * Semantic heading level of `title`. Defaults to `2` in `application` mode (a `PageContainer`
433
+ * `h1` sits above it) and `1` in `system` mode (the surface IS the page). Choose it to keep the
434
+ * outline valid, never for size.
435
+ */
436
+ titleLevel?: HeadingLevelProp;
437
+ /**
438
+ * Support correlation id for the failure, rendered as a monospace/tabular metadata row so it can
439
+ * be read out or copied accurately. Pass the bare id — the localized label is the surface's.
440
+ */
441
+ requestId?: string;
442
+ /**
443
+ * The permission / role the viewer is missing (403). Pass the bare permission name
444
+ * (`reports.view`) — the surface renders the localized "Required permission" label around it.
445
+ */
446
+ permission?: ReactNode;
447
+ /**
448
+ * The organization / tenant the failed request was scoped to. Disambiguates a 403 caused by
449
+ * being in the wrong workspace from one caused by a missing role.
450
+ */
451
+ organization?: ReactNode;
452
+ /** Optional planned-outage timing + progress (503). ISO-8601 + IANA, formatted with `Intl`. */
453
+ maintenance?: ErrorSurfaceMaintenanceProp;
454
+ /**
455
+ * `system` mode only — brand slot above the status code (a `Logo`). Ignored in `application`
456
+ * mode, where the shell already shows the product brand.
457
+ */
458
+ brand?: ReactNode;
459
+ /** `system` mode only — the page footer (contentinfo): copyright, status page, locale switch. */
460
+ footer?: FooterProp;
461
+ /**
462
+ * `system` mode only — measure of the centred column (`CenteredShell` width tier). Default `sm`.
463
+ */
464
+ width?: CenteredShellWidthProp;
465
+ id?: IdProp;
137
466
  className?: ClassNameProp;
138
467
  };
139
468
  /** @see Sidebar */
@@ -146,14 +475,24 @@ export type SidebarProductProp = {
146
475
  export type SidebarItemProp = {
147
476
  id: string;
148
477
  label: string;
478
+ /**
479
+ * Leading 16px glyph — REQUIRED: the collapsed rail is icon-only and the expanded rail aligns
480
+ * every label to the icon column. Untyped/API-driven data that omits it no longer crashes the
481
+ * shell (the row renders an empty `.sb-icon` slot, keeping the 32px row / 10px gap), but the rail
482
+ * reads as a hole. Its colour is themeable separately from the label via
483
+ * `--sidebar-nav-icon-foreground` (see {@link SidebarProp}).
484
+ */
149
485
  icon: ComponentType<SVGProps<SVGSVGElement>>;
150
486
  badge?: ReactNode;
151
487
  disabled?: boolean;
152
488
  /**
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).
489
+ * Destination of the row. It is the SOLE interactive element (no nested `<button>`), so
490
+ * right-click / open-in-new-tab / middle-click all work. Omit for SPA rows that only report
491
+ * selection via `onSelect(id)`.
492
+ *
493
+ * With `Sidebar.linkComponent` this same `href` is what the framework router `<Link>` receives —
494
+ * the LIBRARY still composes the row (icon · label · badge · active · collapsed), so a router
495
+ * link never has to reconstruct row markup (gh#213).
157
496
  */
158
497
  href?: string;
159
498
  /** Nested rows — renders a collapsible submenu group (the parent reads active when any child is). */
@@ -161,11 +500,121 @@ export type SidebarItemProp = {
161
500
  };
162
501
  /** @see Sidebar */
163
502
  export type SidebarItemData = SidebarItemProp;
503
+ /**
504
+ * Props the Sidebar hands to `Sidebar.linkComponent` for one nav row (gh#213).
505
+ *
506
+ * Every field is ANCHOR-SAFE — a router `<Link>` may spread the whole object onto its `<a>` without
507
+ * emitting an unknown-DOM-attribute warning. `children` is the LIBRARY-COMPOSED row content (the
508
+ * `.sb-icon` slot, the `.sb-label`, the `.sb-badge`); render it as-is and never rebuild it, which is
509
+ * what makes icons/badges survive a consumer link (the reported production regression).
510
+ */
511
+ export type SidebarLinkProp = {
512
+ /** `SidebarItemProp.href`. Absent for a disabled row — render an inert `<a>` with no navigation. */
513
+ href?: string;
514
+ /** Library-composed row content: icon slot + label + badge (icon only on the collapsed rail). */
515
+ children: ReactNode;
516
+ /** Canonical row class (`sb-nav-item`, plus `sb-nav-item--sub` for a submenu child). */
517
+ className: string;
518
+ /** Present only on the active row — drives `--sidebar-item-active-*`. */
519
+ "data-active"?: "true";
520
+ /** WAI-ARIA current-page semantics for the active row. */
521
+ "aria-current"?: "page";
522
+ /** Set when the item (or its row) is disabled; the row must not navigate. */
523
+ "aria-disabled"?: true;
524
+ /** Accessible name for the icon-only collapsed rail, where the visible label is hidden. */
525
+ "aria-label"?: string;
526
+ /** `"menuitem"` inside the collapsed rail's portaled flyout menu; absent for ordinary rows. */
527
+ role?: "menuitem";
528
+ /** Reports selection to `Sidebar.onSelect` after the router link runs its own handler. */
529
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
530
+ };
531
+ /**
532
+ * A framework router link component driven by {@link SidebarLinkProp} — Inertia's `<Link href>`,
533
+ * a React Router / TanStack link wrapped by `createSidebarLink(Link, "to")`, or any component that
534
+ * renders a single `<a>`. It must forward its `ref` to that anchor so the collapsed rail's Tooltip
535
+ * can anchor to it.
536
+ */
537
+ export type SidebarLinkComponentProp = ComponentType<SidebarLinkProp>;
538
+ /**
539
+ * Row state supplied to the DEPRECATED `Sidebar.renderItem`.
540
+ *
541
+ * @deprecated Prefer `Sidebar.linkComponent` (or `SidebarItem asChild`), where the library composes
542
+ * the row and the consumer supplies only the element. `renderItem` leaves row CONTENT to the
543
+ * consumer, which is how a `<Link>{item.label}</Link>` silently dropped every icon and badge
544
+ * (gh#213). Spreading `rowProps` — including its `children` — now yields the canonical row.
545
+ */
546
+ export type SidebarRenderItemProp = {
547
+ className: string;
548
+ "data-active"?: "true";
549
+ "aria-current"?: "page";
550
+ "aria-disabled"?: true;
551
+ /**
552
+ * Library-composed row content (icon slot · label · badge). Spread `rowProps` onto your element,
553
+ * or render `rowProps.children` explicitly, to keep the canonical row while adding an affix.
554
+ */
555
+ children?: ReactNode;
556
+ };
164
557
  /** @see Sidebar */
165
558
  export type SidebarSectionProp = {
166
559
  label?: string;
167
560
  items: SidebarItemProp[];
168
561
  };
562
+ /** One selectable organization in the public {@link OrgSwitcher} contract. */
563
+ export type OrgSwitcherOrganization = {
564
+ id: string;
565
+ name: string;
566
+ /** Secondary organization context, for example the member's role or tenant identifier. */
567
+ meta?: ReactNode;
568
+ /** Optional owned mark/avatar. When omitted, OrgSwitcher renders the first name character. */
569
+ avatar?: ReactNode;
570
+ /**
571
+ * Status/plan affordance rendered end-aligned in the expanded trigger and in the menu row
572
+ * (e.g. `<Badge tone="warning">Trial</Badge>`). Hidden in the collapsed rail, which only has room
573
+ * for the mark. Pair a non-textual badge with {@link OrgSwitcherOrganization.badgeLabel}.
574
+ */
575
+ badge?: ReactNode;
576
+ /**
577
+ * Localized screen-reader text for `badge`. Required whenever the badge carries meaning the
578
+ * accessible name would otherwise lose (WCAG 1.1.1 / 1.4.1): the trigger's `aria-label` owns its
579
+ * accessible name, so the badge is announced through `aria-describedby` instead. When omitted, a
580
+ * textual badge is still announced inside the menu row but NOT on the trigger.
581
+ */
582
+ badgeLabel?: string;
583
+ disabled?: boolean;
584
+ };
585
+ /** Localized copy owned by the consuming product, never hard-coded by the component. */
586
+ export type OrgSwitcherLabels = {
587
+ trigger: (organizationName: string) => string;
588
+ title: string;
589
+ search: string;
590
+ empty: string;
591
+ loading: string;
592
+ retry?: string;
593
+ };
594
+ /** @see OrgSwitcher */
595
+ export type OrgSwitcherProp = {
596
+ organizations: readonly OrgSwitcherOrganization[];
597
+ value?: string;
598
+ onValueChange?: (value: string) => void;
599
+ collapsed?: boolean;
600
+ disabled?: boolean;
601
+ loading?: boolean;
602
+ /** Error content replaces the list while preserving the trigger and retry affordance. */
603
+ error?: ReactNode;
604
+ onRetry?: () => void;
605
+ labels: OrgSwitcherLabels;
606
+ /**
607
+ * `"auto"` (default) uses the desktop popover above `--sheet-responsive-breakpoint-width` and a
608
+ * focus-trapped bottom Sheet at/below it — the SAME token that drives `SheetContent
609
+ * responsive="auto"`, resolved through the shared `useSheetResponsiveMode()` hook, so a service
610
+ * moves the drawer line once for every overlay instead of per component.
611
+ * Explicit modes are useful for deterministic embedded surfaces and component tests.
612
+ */
613
+ responsive?: "auto" | "popover" | "sheet";
614
+ open?: boolean;
615
+ onOpenChange?: (open: boolean) => void;
616
+ className?: ClassNameProp;
617
+ };
169
618
  /** @see Sidebar */
170
619
  export type SidebarProp = {
171
620
  /** Accessible navigation landmark name; make it unique when multiple sidebars share a document. */
@@ -179,12 +628,39 @@ export type SidebarProp = {
179
628
  collapsed?: boolean;
180
629
  children?: ChildrenProp;
181
630
  /**
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.
631
+ * THE framework-router contract (gh#213). Supply only the LINK ELEMENT TYPE; the Sidebar still
632
+ * composes the row icon slot, label, badge, `data-active`/`aria-current`, the icon-only
633
+ * collapsed rail and its tooltip name and passes it as {@link SidebarLinkProp} `children`.
634
+ * Used for every row that carries an `href`: top-level leaves, submenu children, collapsed-rail
635
+ * leaves and collapsed flyout entries. A group TRIGGER stays a `<button>` (it owns
636
+ * `aria-expanded` disclosure semantics per WAI-ARIA APG); its children take the link.
637
+ *
638
+ * Rows without an `href` keep the `<button>` + `onSelect(id)` shape — a router link with no
639
+ * destination is not a link.
640
+ *
641
+ * @example
642
+ * ```tsx
643
+ * // Inertia — its <Link href> already matches SidebarLinkProp.
644
+ * import { Link } from "@inertiajs/react";
645
+ * <Sidebar linkComponent={inertiaSidebarLink(Link)} sections={sections} activeId={activeId} />
646
+ *
647
+ * // React Router / TanStack — remap `href` to `to`.
648
+ * import { Link } from "react-router-dom";
649
+ * <Sidebar linkComponent={createSidebarLink(Link, "to")} sections={sections} activeId={activeId} />
650
+ * ```
651
+ */
652
+ linkComponent?: SidebarLinkComponentProp;
653
+ /**
654
+ * @deprecated Use {@link SidebarProp.linkComponent} (or `SidebarItem asChild`) instead — there the
655
+ * LIBRARY composes the row and you supply only the element, so icons/labels/badges cannot be lost.
656
+ *
657
+ * Legacy escape hatch: return a SINGLE interactive element and the Sidebar merges the row styling
658
+ * + active state onto it via Slot. Because row CONTENT stayed consumer-authored, a
659
+ * `<Link>{item.label}</Link>` silently dropped every icon and badge (the gh#213 production
660
+ * regression). `rowProps` now also carries the composed `children`, so spreading it restores the
661
+ * canonical row.
186
662
  */
187
- renderItem?: (item: SidebarItemData) => ReactNode;
663
+ renderItem?: (item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode;
188
664
  footer?: ReactNode;
189
665
  /**
190
666
  * Override the nav landmark's accessible name. Defaults to a localized "Main navigation".
@@ -211,3 +687,77 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
211
687
  /** Escape hatch — render fully custom bar content instead of the three slots. */
212
688
  children?: ReactNode;
213
689
  };
690
+ /**
691
+ * @see LegalDocumentShell — one entry of the table of contents + the matching document section.
692
+ * `id` is the REAL anchor target (`href="#{id}"`, `<section id>`), so it must be unique on the page
693
+ * and URL-safe: it is what a deep link, a hash jump and `aria-current` all key off.
694
+ */
695
+ export type LegalDocumentSectionProp = {
696
+ /** URL-safe anchor id — the `<section id>` AND the contents `href="#…"` target. */
697
+ id: string;
698
+ /** Section heading text — rendered as an `<h2>` AND reused as the contents-list label. */
699
+ title: string;
700
+ /** Section body. Consumer-owned legal copy: paragraphs, lists, tables, nested `<h3>`s. */
701
+ content: ReactNode;
702
+ };
703
+ /**
704
+ * @see LegalDocumentShell — the long-form legal/policy document surface (terms of service, privacy
705
+ * policy, DPA, cookie policy, SLA, EULA) with a table of contents.
706
+ *
707
+ * It owns the parts an app must NOT re-implement: the readable measure + top-aligned document
708
+ * geometry, the sticky contents rail (single-column compact block below 56rem), scroll-spy
709
+ * active-section tracking, hash deep-linking with a token-driven scroll offset, focus handoff to
710
+ * the target `<section>`, and `prefers-reduced-motion`-aware smooth scrolling. All legal TEXT stays
711
+ * owned by the consumer — the shell only receives it through `sections` and the slots.
712
+ *
713
+ * Semantics: `<article>` labelled by the document title · a NAMED `<nav>` for the contents · REAL
714
+ * `<a href="#…">` anchors carrying `aria-current="location"` · one `<section>` per entry, labelled
715
+ * by its `<h2>`.
716
+ */
717
+ export type LegalDocumentShellProp = {
718
+ /** Document title — the `<h1>` that names the `<article>` (e.g. "Terms of Service"). */
719
+ title: TitleProp;
720
+ /**
721
+ * Document version identifier (e.g. `"2.4"`). Rendered as a localized "Version {version}" line —
722
+ * pass the bare identifier, never a pre-localized sentence.
723
+ */
724
+ version?: string;
725
+ /**
726
+ * Effective date as an **ISO 8601** calendar date (`yyyy-MM-dd`) or a full ISO instant. Formatted
727
+ * for display with `Intl.DateTimeFormat` in the active locale and emitted inside a
728
+ * `<time dateTime={effectiveDate}>`, so the machine-readable value is always the ISO input.
729
+ * NEVER pass a pre-formatted string.
730
+ */
731
+ effectiveDate?: string;
732
+ /** Short plain-language summary rendered under the metadata, above the contents. */
733
+ summary?: ReactNode;
734
+ /**
735
+ * Accessible name + visible caption of the contents `<nav>` (e.g. "Contents"). Defaults to a
736
+ * localized "Contents"; override it when two documents render in the same view, so the two `nav`
737
+ * landmarks stay distinguishable (axe `landmark-unique`, WCAG 2.4.1).
738
+ */
739
+ contentsLabel?: string;
740
+ /** The document's sections, in reading order. Drives BOTH the contents list and the body. */
741
+ sections: LegalDocumentSectionProp[];
742
+ /**
743
+ * Controlled active section id (the entry marked `aria-current="location"`). Pair it with
744
+ * `onActiveSectionChange`; omit both for the uncontrolled form.
745
+ */
746
+ activeSection?: string;
747
+ /** Uncontrolled initial active section id. Defaults to the first section. */
748
+ defaultActiveSection?: string;
749
+ /**
750
+ * Fires whenever the active section changes — on a contents-anchor activation, on an initial
751
+ * hash deep link, and continuously from the scroll spy as the reader moves through the document.
752
+ */
753
+ onActiveSectionChange?: (sectionId: string) => void;
754
+ /**
755
+ * Slot above the contents list in the rail — a document switcher across the legal set
756
+ * (Terms · Privacy · Cookies). Rendered as a plain wrapper, so the consumer owns its semantics.
757
+ */
758
+ documentNavigation?: ReactNode;
759
+ /** Slot below the last section — the accept/download/print/contact actions. */
760
+ footerAction?: ReactNode;
761
+ id?: IdProp;
762
+ className?: ClassNameProp;
763
+ };
@@ -0,0 +1 @@
1
+ "use client";