@godxjp/ui 20.0.0 → 20.2.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 (273) hide show
  1. package/README.md +8 -2
  2. package/dist/app/app-provider.js +37 -21
  3. package/dist/app/index.d.ts +2 -0
  4. package/dist/app/index.js +2 -0
  5. package/dist/app/storage.d.ts +17 -0
  6. package/dist/app/storage.js +28 -0
  7. package/dist/app/theme-axes.d.ts +2 -0
  8. package/dist/app/theme-axes.js +45 -0
  9. package/dist/components/charts/chart-cartesian.d.ts +2 -1
  10. package/dist/components/charts/chart-cartesian.js +52 -3
  11. package/dist/components/charts/chart-category-axis.d.ts +55 -0
  12. package/dist/components/charts/chart-category-axis.js +93 -0
  13. package/dist/components/charts/chart-frame.d.ts +10 -1
  14. package/dist/components/charts/chart-frame.js +19 -3
  15. package/dist/components/charts/compact-bar-trend.d.ts +1 -1
  16. package/dist/components/charts/compact-bar-trend.js +2 -0
  17. package/dist/components/charts/pie-chart.d.ts +1 -1
  18. package/dist/components/charts/pie-chart.js +12 -1
  19. package/dist/components/charts/recharts-peer.d.ts +49 -0
  20. package/dist/components/charts/recharts-peer.js +45 -0
  21. package/dist/components/data-display/card.d.ts +12 -1
  22. package/dist/components/data-display/card.js +22 -4
  23. package/dist/components/data-display/code-block.js +6 -2
  24. package/dist/components/data-display/data-table.d.ts +39 -4
  25. package/dist/components/data-display/data-table.js +802 -270
  26. package/dist/components/data-display/descriptions.d.ts +27 -6
  27. package/dist/components/data-display/descriptions.js +58 -17
  28. package/dist/components/data-display/index.d.ts +2 -0
  29. package/dist/components/data-display/index.js +2 -0
  30. package/dist/components/data-display/list-row.d.ts +10 -1
  31. package/dist/components/data-display/list-row.js +7 -1
  32. package/dist/components/data-display/popover.d.ts +9 -3
  33. package/dist/components/data-display/popover.js +65 -11
  34. package/dist/components/data-display/range-timeline.d.ts +38 -0
  35. package/dist/components/data-display/range-timeline.js +161 -0
  36. package/dist/components/data-display/scroll-area.js +13 -2
  37. package/dist/components/data-display/service-launcher-card.js +14 -12
  38. package/dist/components/data-display/table.d.ts +71 -3
  39. package/dist/components/data-display/table.js +56 -18
  40. package/dist/components/data-display/tree-list.js +4 -9
  41. package/dist/components/data-entry/calendar.d.ts +2 -2
  42. package/dist/components/data-entry/calendar.js +23 -3
  43. package/dist/components/data-entry/cascader.d.ts +1 -1
  44. package/dist/components/data-entry/cascader.js +188 -24
  45. package/dist/components/data-entry/color-picker.d.ts +1 -1
  46. package/dist/components/data-entry/color-picker.js +17 -5
  47. package/dist/components/data-entry/control-appearance.d.ts +64 -0
  48. package/dist/components/data-entry/control-appearance.js +39 -0
  49. package/dist/components/data-entry/control-surface.d.ts +61 -0
  50. package/dist/components/data-entry/control-surface.js +39 -0
  51. package/dist/components/data-entry/date-picker.d.ts +1 -1
  52. package/dist/components/data-entry/date-picker.js +344 -113
  53. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  54. package/dist/components/data-entry/date-range-picker.js +278 -140
  55. package/dist/components/data-entry/form-field.d.ts +1 -1
  56. package/dist/components/data-entry/form-field.js +39 -4
  57. package/dist/components/data-entry/form.d.ts +4 -0
  58. package/dist/components/data-entry/form.js +4 -2
  59. package/dist/components/data-entry/index.d.ts +7 -3
  60. package/dist/components/data-entry/index.js +10 -1
  61. package/dist/components/data-entry/input-otp.d.ts +1 -1
  62. package/dist/components/data-entry/input.d.ts +12 -34
  63. package/dist/components/data-entry/input.js +92 -24
  64. package/dist/components/data-entry/month-picker.d.ts +1 -1
  65. package/dist/components/data-entry/month-picker.js +47 -10
  66. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  67. package/dist/components/data-entry/month-range-picker.js +51 -11
  68. package/dist/components/data-entry/number-input.d.ts +7 -0
  69. package/dist/components/data-entry/number-input.js +147 -98
  70. package/dist/components/data-entry/password-input.d.ts +1 -1
  71. package/dist/components/data-entry/radio.d.ts +1 -1
  72. package/dist/components/data-entry/radio.js +61 -13
  73. package/dist/components/data-entry/search-input.d.ts +1 -1
  74. package/dist/components/data-entry/search-input.js +16 -2
  75. package/dist/components/data-entry/search-select.d.ts +2 -2
  76. package/dist/components/data-entry/search-select.js +209 -78
  77. package/dist/components/data-entry/select.d.ts +22 -4
  78. package/dist/components/data-entry/select.js +215 -163
  79. package/dist/components/data-entry/slider.d.ts +9 -1
  80. package/dist/components/data-entry/slider.js +87 -9
  81. package/dist/components/data-entry/switch.d.ts +3 -0
  82. package/dist/components/data-entry/switch.js +29 -3
  83. package/dist/components/data-entry/textarea.d.ts +14 -56
  84. package/dist/components/data-entry/textarea.js +80 -33
  85. package/dist/components/data-entry/time-picker.d.ts +2 -2
  86. package/dist/components/data-entry/time-picker.js +333 -109
  87. package/dist/components/data-entry/time-range-picker.d.ts +5 -0
  88. package/dist/components/data-entry/time-range-picker.js +89 -0
  89. package/dist/components/data-entry/transfer.d.ts +1 -1
  90. package/dist/components/data-entry/transfer.js +86 -28
  91. package/dist/components/data-entry/tree-select.d.ts +1 -1
  92. package/dist/components/data-entry/tree-select.js +201 -113
  93. package/dist/components/data-entry/tree-utils.js +7 -14
  94. package/dist/components/data-entry/upload-files.d.ts +2 -0
  95. package/dist/components/data-entry/upload-files.js +31 -0
  96. package/dist/components/data-entry/upload-request.d.ts +4 -0
  97. package/dist/components/data-entry/upload-request.js +53 -0
  98. package/dist/components/data-entry/upload-types.d.ts +28 -0
  99. package/dist/components/data-entry/upload-types.js +2 -0
  100. package/dist/components/data-entry/upload.d.ts +2 -2
  101. package/dist/components/data-entry/upload.js +475 -115
  102. package/dist/components/feedback/dialog.js +19 -19
  103. package/dist/components/feedback/sheet.js +3 -0
  104. package/dist/components/feedback/tooltip.js +3 -0
  105. package/dist/components/general/button.d.ts +2 -1
  106. package/dist/components/general/button.js +7 -1
  107. package/dist/components/general/index.d.ts +1 -0
  108. package/dist/components/general/index.js +2 -0
  109. package/dist/components/general/typography.d.ts +3 -0
  110. package/dist/components/general/typography.js +15 -1
  111. package/dist/components/general/visually-hidden.d.ts +3 -0
  112. package/dist/components/general/visually-hidden.js +9 -0
  113. package/dist/components/layout/app-launcher.d.ts +34 -0
  114. package/dist/components/layout/app-launcher.js +261 -0
  115. package/dist/components/layout/app-shell.d.ts +3 -1
  116. package/dist/components/layout/app-shell.js +34 -14
  117. package/dist/components/layout/auth-shell.d.ts +1 -1
  118. package/dist/components/layout/auth-shell.js +2 -0
  119. package/dist/components/layout/breadcrumb.d.ts +14 -2
  120. package/dist/components/layout/breadcrumb.js +46 -4
  121. package/dist/components/layout/flex.d.ts +1 -1
  122. package/dist/components/layout/flex.js +23 -2
  123. package/dist/components/layout/index.d.ts +3 -0
  124. package/dist/components/layout/index.js +5 -1
  125. package/dist/components/layout/org-switcher.js +20 -1
  126. package/dist/components/layout/page-container.d.ts +1 -1
  127. package/dist/components/layout/page-container.js +5 -3
  128. package/dist/components/layout/responsive-grid.d.ts +16 -2
  129. package/dist/components/layout/responsive-grid.js +29 -2
  130. package/dist/components/layout/sidebar.js +2 -1
  131. package/dist/components/layout/topbar-item.d.ts +3 -0
  132. package/dist/components/layout/topbar-item.js +18 -3
  133. package/dist/components/layout/topbar.d.ts +1 -1
  134. package/dist/components/layout/topbar.js +27 -6
  135. package/dist/components/navigation/dropdown-menu.d.ts +30 -2
  136. package/dist/components/navigation/dropdown-menu.js +73 -11
  137. package/dist/components/navigation/pagination.d.ts +2 -2
  138. package/dist/components/navigation/pagination.js +155 -83
  139. package/dist/components/navigation/steps.d.ts +2 -2
  140. package/dist/components/navigation/steps.js +29 -4
  141. package/dist/components/navigation/tabs.d.ts +5 -33
  142. package/dist/components/navigation/tabs.js +232 -64
  143. package/dist/components/ui/hover-card.js +3 -0
  144. package/dist/components/ui/input-otp.d.ts +26 -28
  145. package/dist/components/ui/input-otp.js +50 -7
  146. package/dist/components/ui/password-input.d.ts +36 -2
  147. package/dist/components/ui/password-input.js +27 -7
  148. package/dist/components/ui/rating.d.ts +25 -0
  149. package/dist/components/ui/rating.js +67 -27
  150. package/dist/components/ui/segmented.d.ts +9 -0
  151. package/dist/components/ui/segmented.js +17 -2
  152. package/dist/components/ui/separator.d.ts +4 -0
  153. package/dist/components/ui/separator.js +13 -0
  154. package/dist/components/ui/tag-input.d.ts +45 -0
  155. package/dist/components/ui/tag-input.js +109 -27
  156. package/dist/form/form-context.d.ts +29 -0
  157. package/dist/form/form-context.js +44 -4
  158. package/dist/form/form-field-array.d.ts +22 -0
  159. package/dist/form/form-field-array.js +50 -0
  160. package/dist/form/form-field-control.d.ts +2 -1
  161. package/dist/form/form-field-control.js +116 -40
  162. package/dist/form/form-root.d.ts +2 -1
  163. package/dist/form/form-root.js +142 -23
  164. package/dist/form/index.d.ts +3 -1
  165. package/dist/form/index.js +12 -1
  166. package/dist/i18n/index.d.ts +1 -1
  167. package/dist/i18n/index.js +3 -1
  168. package/dist/i18n/messages/en.json +59 -6
  169. package/dist/i18n/messages/ja.json +59 -6
  170. package/dist/i18n/messages/vi.json +59 -6
  171. package/dist/i18n/translate.d.ts +19 -0
  172. package/dist/i18n/translate.js +24 -0
  173. package/dist/inertia/index.d.ts +20 -0
  174. package/dist/inertia/index.js +60 -1
  175. package/dist/lib/control-styles.d.ts +25 -3
  176. package/dist/lib/control-styles.js +9 -3
  177. package/dist/lib/datetime/picker-format.d.ts +8 -0
  178. package/dist/lib/datetime/picker-format.js +54 -0
  179. package/dist/lib/overlay-portal.d.ts +18 -0
  180. package/dist/lib/overlay-portal.js +17 -0
  181. package/dist/props/components/app.prop.d.ts +13 -2
  182. package/dist/props/components/charts.prop.d.ts +21 -0
  183. package/dist/props/components/data-display.prop.d.ts +55 -4
  184. package/dist/props/components/data-entry.prop.d.ts +687 -43
  185. package/dist/props/components/form.prop.d.ts +142 -4
  186. package/dist/props/components/general.prop.d.ts +30 -1
  187. package/dist/props/components/index.d.ts +2 -2
  188. package/dist/props/components/layout.prop.d.ts +255 -8
  189. package/dist/props/components/navigation.prop.d.ts +113 -3
  190. package/dist/props/registry.d.ts +376 -10
  191. package/dist/props/registry.js +493 -9
  192. package/dist/props/vocabulary/data.prop.d.ts +179 -1
  193. package/dist/props/vocabulary/index.d.ts +4 -4
  194. package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
  195. package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
  196. package/dist/props/vocabulary/shared.prop.d.ts +33 -0
  197. package/dist/styles/badge-layout.css +2 -1
  198. package/dist/styles/card-layout.css +36 -5
  199. package/dist/styles/chart-layout.css +17 -1
  200. package/dist/styles/control.css +520 -14
  201. package/dist/styles/data-display-layout.css +248 -7
  202. package/dist/styles/data-entry-layout.css +13 -0
  203. package/dist/styles/layout.css +221 -4
  204. package/dist/styles/navigation-layout.css +230 -1
  205. package/dist/styles/shell-layout.css +690 -21
  206. package/dist/styles/table-layout.css +95 -6
  207. package/dist/styles/text-layout.css +5 -0
  208. package/dist/tokens/base.css +1 -0
  209. package/dist/tokens/components/badge.css +1 -0
  210. package/dist/tokens/components/chart.css +6 -0
  211. package/dist/tokens/components/control.css +81 -3
  212. package/dist/tokens/components/data-display.css +9 -0
  213. package/dist/tokens/components/descriptions.css +11 -0
  214. package/dist/tokens/components/flex.css +8 -0
  215. package/dist/tokens/components/navigation.css +55 -0
  216. package/dist/tokens/components/shell.css +100 -5
  217. package/dist/tokens/components/table.css +14 -0
  218. package/dist/tokens/foundation.css +2 -0
  219. package/dist/tokens/semantic/layout.css +9 -0
  220. package/docs/CONSUMER-RULES.md +13 -0
  221. package/docs/DESIGN-AUTHORITY.md +43 -6
  222. package/docs/DEVELOPMENT.md +4 -3
  223. package/docs/FORMS.md +151 -6
  224. package/docs/FRAME-COVERAGE-REPORT.md +29 -16
  225. package/docs/README.md +1 -1
  226. package/docs/STANDARDS-vocabulary-tokens.md +1 -1
  227. package/docs/TESTING.md +15 -6
  228. package/docs/charts/cjk-category-axis.tsx +81 -0
  229. package/docs/data-display/card/index.tsx +22 -0
  230. package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
  231. package/docs/data-display/data-table/index.tsx +23 -0
  232. package/docs/data-display/descriptions.tsx +41 -0
  233. package/docs/data-display/list-row.tsx +6 -6
  234. package/docs/data-display/scroll-area.tsx +31 -25
  235. package/docs/data-display/table.tsx +104 -45
  236. package/docs/data-display/timeline.tsx +41 -0
  237. package/docs/data-entry/date-picker.tsx +85 -0
  238. package/docs/data-entry/date-range-picker.tsx +26 -0
  239. package/docs/data-entry/form-dynamic-fields.tsx +199 -0
  240. package/docs/data-entry/form.tsx +74 -4
  241. package/docs/data-entry/input-otp.tsx +24 -0
  242. package/docs/data-entry/input.tsx +46 -1
  243. package/docs/data-entry/month-range-picker.tsx +1 -1
  244. package/docs/data-entry/select.tsx +27 -0
  245. package/docs/data-entry/switch.tsx +41 -0
  246. package/docs/data-entry/textarea.tsx +38 -0
  247. package/docs/data-entry/time-picker.tsx +85 -1
  248. package/docs/data-entry/time-range-picker.tsx +55 -0
  249. package/docs/data-entry/transfer.tsx +17 -0
  250. package/docs/data-entry/upload.tsx +56 -12
  251. package/docs/feedback/tooltip.tsx +1 -1
  252. package/docs/general/activity.tsx +2 -2
  253. package/docs/general/button/index.tsx +14 -1
  254. package/docs/general/typography.tsx +14 -1
  255. package/docs/layout/app-launcher.tsx +195 -0
  256. package/docs/layout/app-shell-arrangements.tsx +5 -5
  257. package/docs/layout/app-shell.tsx +11 -0
  258. package/docs/layout/flex.tsx +50 -0
  259. package/docs/layout/responsive-grid.tsx +21 -1
  260. package/docs/layout/topbar.tsx +5 -9
  261. package/docs/navigation/app-setting-picker.tsx +11 -0
  262. package/docs/navigation/breadcrumb.tsx +48 -0
  263. package/docs/navigation/dropdown-menu.tsx +21 -0
  264. package/docs/navigation/pagination.tsx +52 -0
  265. package/docs/navigation/steps.tsx +37 -0
  266. package/docs/navigation/tabs.tsx +97 -1
  267. package/docs/query/button-refetch.tsx +1 -0
  268. package/docs/showcase/settings-security-mfa.tsx +3 -3
  269. package/package.json +24 -4
  270. package/scripts/_agent-setup.mjs +76 -9
  271. package/scripts/init-guinea-pig.mjs +26 -4
  272. package/scripts/ui-audit.mjs +243 -30
  273. package/scripts/visual-audit.mjs +45 -7
@@ -1,8 +1,8 @@
1
1
  /** Form module prop types — react-hook-form + Zod 4 only. */
2
2
  import type * as React from "react";
3
- import type { FieldPath, FieldValues, UseFormProps, UseFormReturn } from "react-hook-form";
3
+ import type { FieldArray, FieldArrayPath, FieldErrors, FieldPath, FieldValues, UseFormProps, UseFormReturn } from "react-hook-form";
4
4
  import type { z } from "zod";
5
- import type { ErrorProp, HelperProp, IdProp, LabelProp, RequiredProp } from "../vocabulary/index.js";
5
+ import type { BreakpointProp, DensityProp, DisabledProp, ErrorBagProp, ErrorProp, FormLayoutProp, HelperProp, IdProp, LabelProp, RequiredProp, WidthProp } from "../vocabulary/index.js";
6
6
  /** Schema passed to useZodForm — must be Zod 4 object schema. */
7
7
  export type ZodSchemaProp<T extends z.ZodType = z.ZodType> = T;
8
8
  /** Options for useZodForm (resolver injected automatically). */
@@ -27,6 +27,11 @@ export interface FormStateAdapter {
27
27
  /** Optional blur handler (e.g. touch-tracking); called with the field name. */
28
28
  onBlur?(name: string): void;
29
29
  getValues?(): unknown;
30
+ /**
31
+ * Restore the store's initial values (e.g. Inertia's `form.reset()`). `FormRoot` calls it for a
32
+ * native `<Button type="reset">`, so the reset button behaves identically on both paths.
33
+ */
34
+ reset?(): void;
30
35
  }
31
36
  /** @see FormRoot */
32
37
  export type FormRootProp<TFieldValues extends FieldValues> = {
@@ -39,26 +44,159 @@ export type FormRootProp<TFieldValues extends FieldValues> = {
39
44
  * Framework-agnostic form-state adapter (e.g. `inertiaAdapter(form)` from `@godxjp/ui/inertia`).
40
45
  */
41
46
  adapter?: FormStateAdapter;
42
- /** Submit handler. */
47
+ /** Submit handler — runs only after validation passes (antd `onFinish`). */
43
48
  onSubmit: (values: TFieldValues) => void | Promise<void>;
49
+ /**
50
+ * Validation FAILED (antd `onFinishFailed`) — receives react-hook-form's error tree. Use it to
51
+ * announce a summary; the first invalid field is focused (and scrolled to) regardless.
52
+ * react-hook-form path only: on the adapter path the server owns validation.
53
+ */
54
+ onSubmitFailed?: (errors: FieldErrors<TFieldValues>) => void;
55
+ /**
56
+ * `onSubmit` REJECTED (the network call failed). Without this the rejection escapes as an
57
+ * unhandled promise rejection, which is what an async `onSubmit` does today — pass a handler to
58
+ * render a form-level error instead.
59
+ */
60
+ onSubmitError?: (error: unknown) => void;
61
+ /**
62
+ * A native `<Button type="reset">` inside the form restores `defaultValues` (adapter path:
63
+ * `adapter.reset()`); this runs afterwards for any extra app state (a status banner, a step).
64
+ */
65
+ onReset?: () => void;
66
+ /**
67
+ * Scroll the first invalid field into view after a failed submit (antd `scrollToFirstError`).
68
+ * Honours `prefers-reduced-motion`. Default `true`.
69
+ */
70
+ scrollToFirstError?: boolean;
71
+ /**
72
+ * Disable EVERY field under this form (antd Form `disabled`) — read by `FormFieldControl`
73
+ * (which hands `disabled` to its render prop) and by `useFormDisabled()` for the action buttons.
74
+ * Values are NOT dropped from the submitted payload.
75
+ */
76
+ disabled?: DisabledProp;
77
+ /**
78
+ * Field layout for every `FormFieldControl` below (antd `layout`). Setting ANY layout prop makes
79
+ * `FormRoot` render the `Form` layout shell, whose `--form-block-gap` spacing replaces the plain
80
+ * `ui-stack-md` stack used by a bare `FormRoot`.
81
+ */
82
+ layout?: FormLayoutProp;
83
+ /** Label column width in the `horizontal` layout (antd `labelCol`). */
84
+ labelWidth?: WidthProp;
85
+ /** Control column width (antd `wrapperCol`). */
86
+ controlWidth?: WidthProp;
87
+ /** Label text alignment in the `horizontal` layout (antd `labelAlign`). */
88
+ labelAlign?: "start" | "end";
89
+ /** Viewport below which a `horizontal`/`inline` layout stacks; `false` never stacks. */
90
+ collapseBelow?: BreakpointProp | false;
91
+ /** Control density for the fields below (antd Form `size`). */
92
+ density?: DensityProp;
93
+ columns?: import("./data-entry.prop.js").FormProp["columns"];
94
+ /** How required/optional fields are marked in their labels (antd `requiredMark`). */
95
+ requiredMark?: boolean | "optional";
96
+ /**
97
+ * Server validation error bag. Fields claim their own key and `<FormErrors />` renders the
98
+ * unclaimed remainder — the same mechanism as `Form errors`, without a second `Form` wrapper.
99
+ */
100
+ errors?: ErrorBagProp;
44
101
  children: React.ReactNode;
45
102
  className?: string;
46
103
  id?: IdProp;
47
104
  };
48
105
  /** @see FormFieldControl */
49
- export type FormFieldControlProp<TFieldValues extends FieldValues> = {
106
+ export type FormFieldControlProp<TFieldValues extends FieldValues> = Pick<import("./data-entry.prop.js").FormFieldProp, "id" | "field" | "labelAddon" | "layout" | "labelWidth" | "controlWidth" | "colSpan"> & {
50
107
  name: FieldPath<TFieldValues>;
51
108
  label: LabelProp;
52
109
  required?: RequiredProp;
53
110
  helper?: HelperProp;
111
+ /**
112
+ * Disable this field only. Defaults to the surrounding `FormRoot disabled`. The resolved state
113
+ * reaches the control through the render prop's `disabled`; the field's value is still submitted
114
+ * (antd semantics), unlike react-hook-form's own `disabled`.
115
+ */
116
+ disabled?: DisabledProp;
117
+ /**
118
+ * Re-validate this field whenever one of these fields changes (antd `dependencies`) — the
119
+ * confirm-password / date-range case, where the schema's cross-field `.superRefine()` reports on
120
+ * THIS field but only re-runs when the OTHER one is edited. Runs only once the form has been
121
+ * submitted, so a dependency edit never flags a field the user has not reached yet.
122
+ */
123
+ dependencies?: FieldPath<TFieldValues>[];
124
+ /**
125
+ * Read the value out of the control's change payload (antd `getValueFromEvent`). Replaces the
126
+ * built-in DOM-event/raw-value detection — needed by a control that reports `(value, option)`.
127
+ */
128
+ getValueFromEvent?: (...args: unknown[]) => unknown;
129
+ /**
130
+ * Transform the extracted value before it is stored (antd `normalize`) — trim, upper-case,
131
+ * digits-only. Receives the previous value so a normaliser can reject a change.
132
+ */
133
+ normalize?: (value: unknown, previousValue: unknown) => unknown;
134
+ /**
135
+ * Message shown in the error slot INSTEAD of the resolved validation error (antd `help`) — for a
136
+ * server error the schema cannot know about.
137
+ */
138
+ help?: ErrorProp;
139
+ /**
140
+ * Force the validation state shown by the field (antd `validateStatus`) — `"validating"` for a
141
+ * remote check in flight, `"success"` for a confirmed-unique value. A real validation error
142
+ * always wins; mirrors `FormField validateStatus`.
143
+ */
144
+ validateStatus?: "success" | "warning" | "error" | "validating";
145
+ /** Render the status icon + localized status text for `validateStatus` (antd `hasFeedback`). */
146
+ hasFeedback?: boolean;
147
+ /** Replace the default feedback text next to the `hasFeedback` icon. */
148
+ feedback?: React.ReactNode;
149
+ /**
150
+ * Keep the value in the form state after the field unmounts (antd `preserve`, default `true`).
151
+ * `false` unregisters it — a conditional branch that must not submit a stale value.
152
+ */
153
+ preserve?: boolean;
54
154
  className?: string;
55
155
  children: (field: {
56
156
  id: string;
57
157
  name: string;
58
158
  value: unknown;
59
159
  onChange: (...args: unknown[]) => void;
160
+ onValueChange: (...args: unknown[]) => void;
60
161
  onBlur: () => void;
61
162
  ref: React.Ref<HTMLInputElement>;
163
+ /** Present (and `true`) only when the field is disabled, so `{...field}` never re-enables a control. */
164
+ disabled?: DisabledProp;
165
+ }) => React.ReactNode;
166
+ };
167
+ /**
168
+ * @see FormFieldArray — dynamic repeating fields (antd `Form.List`) on react-hook-form's
169
+ * `useFieldArray`. `name` is the array field; each row hands back the dotted path prefix to build
170
+ * the child `FormFieldControl name`.
171
+ */
172
+ export type FormFieldArrayProp<TFieldValues extends FieldValues, TName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>> = {
173
+ /** Path of the array in the schema (`"contacts"`, `"invoice.lines"`). */
174
+ name: TName;
175
+ children: (list: {
176
+ /** One entry per row, in order. `key` is react-hook-form's stable row id — use it as the React key. */
177
+ fields: Array<{
178
+ key: string;
179
+ name: `${TName}.${number}`;
180
+ index: number;
181
+ }>;
182
+ /** Append one row (or several) at the end. */
183
+ append: (value: FieldArray<TFieldValues, TName> | FieldArray<TFieldValues, TName>[]) => void;
184
+ /** Insert one row (or several) at the front. */
185
+ prepend: (value: FieldArray<TFieldValues, TName> | FieldArray<TFieldValues, TName>[]) => void;
186
+ /** Insert at `index`. */
187
+ insert: (index: number, value: FieldArray<TFieldValues, TName> | FieldArray<TFieldValues, TName>[]) => void;
188
+ /** Remove `index` (or several); omit to clear every row. */
189
+ remove: (index?: number | number[]) => void;
190
+ /** Move a row, keeping its state (drag-reorder). */
191
+ move: (from: number, to: number) => void;
192
+ /** Exchange two rows. */
193
+ swap: (indexA: number, indexB: number) => void;
194
+ /** Replace every row at once. */
195
+ replace: (values: FieldArray<TFieldValues, TName>[]) => void;
196
+ /** Array-LEVEL validation message (a `.min(1)` on the array itself), not a row's. */
197
+ error?: string;
198
+ /** Resolved `FormRoot disabled` — gate the add/remove buttons on it. */
199
+ disabled: DisabledProp;
62
200
  }) => React.ReactNode;
63
201
  };
64
202
  /** Mapped field error from RHF — displayed via FormField error slot. */
@@ -1,6 +1,6 @@
1
1
  /** Foundation component prop types — @see docs/COMPONENTS.md#foundation */
2
2
  import type * as React from "react";
3
- import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DisabledProp, FontWeightProp, HeadingLevelProp, LabelProp, OnClickProp, PendingProp, RevealDelayProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp } from "../vocabulary/index.js";
3
+ import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DisabledProp, FontWeightProp, HeadingLevelProp, LabelProp, OnClickProp, PendingProp, RevealDelayProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp, TextWhitespaceProp } from "../vocabulary/index.js";
4
4
  /** @see Text — typographic primitive; replaces hand-rolled `<span className="text-[13px] …">`. */
5
5
  export type TextProp = Omit<React.HTMLAttributes<HTMLElement>, "color"> & {
6
6
  /** Render element. Default `span`. */
@@ -37,8 +37,24 @@ export type TextProp = Omit<React.HTMLAttributes<HTMLElement>, "color"> & {
37
37
  * line-clamp styling (never write the `line-clamp-N` utility page-side).
38
38
  */
39
39
  clamp?: number;
40
+ /**
41
+ * Whitespace handling. Default `normal` (CSS's own: newlines and space runs collapse).
42
+ *
43
+ * `pre-wrap` is for text a PERSON typed — a plain-text note, an issue description, a pasted log
44
+ * — where the line breaks and the indentation are CONTENT, not formatting. It preserves both and
45
+ * still wraps long lines at the container edge, and it breaks an over-long unbroken token (a URL,
46
+ * an id) rather than letting it overflow.
47
+ *
48
+ * Precedence is explicit and resolved in the component, not by CSS ordering: `truncate` is a
49
+ * single-line contract and WINS (dev builds warn, and `data-whitespace` is not emitted), while
50
+ * `clamp` composes with it — a clamped pre-wrap block shows its first N real lines.
51
+ */
52
+ whitespace?: TextWhitespaceProp;
40
53
  /** Tabular figures for aligned numbers. */
41
54
  tabular?: boolean;
55
+ decoration?: "none" | "underline" | "line-through";
56
+ /** Inline code chip; use with as="code". */
57
+ chip?: boolean;
42
58
  /** Monospace family (codes, ids). */
43
59
  mono?: boolean;
44
60
  htmlFor?: string;
@@ -73,6 +89,19 @@ export type ButtonProp = React.ButtonHTMLAttributes<HTMLButtonElement> & {
73
89
  /** Corner shape — `default` (control radius) · `pill` (fully rounded) · `sharp` (square). */
74
90
  shape?: ShapeProp;
75
91
  fullWidth?: boolean;
92
+ /**
93
+ * Take the space the siblings leave, and let a long label ELLIPSE instead of widening the row —
94
+ * the same axis `Flex` calls `fill`, for the same reason.
95
+ *
96
+ * Button ships `flex-shrink: 0`, right almost everywhere and wrong in a constrained bar: an
97
+ * account menu holding an avatar plus a person's name keeps its full width while the cluster
98
+ * clips it, so a keyboard user tabs to a control they cannot see (SC 2.4.7). Until this axis
99
+ * existed the only move was `className="min-w-0 flex-1"`, which ui-audit blocks — and which this
100
+ * package's own Topbar guidance recommended, so the docs prescribed the utility the audit forbids.
101
+ *
102
+ * Sets `flex: 1 1 auto` and `min-inline-size: 0`; pair it with a `<Text truncate>` label.
103
+ */
104
+ fill?: boolean;
76
105
  /** Allow a text button to grow vertically for multi-line labels. */
77
106
  wrap?: boolean;
78
107
  /** Logical content alignment, especially for full-width collection actions. */
@@ -1,10 +1,10 @@
1
- export type { AppShellProp, AuthFooterProp, AuthIdentityProp, OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, PageContainerProp, PageInsetProp, SidebarItemProp, SidebarProductProp, SidebarProp, SidebarSectionProp, TopbarProp, } from "./layout.prop.js";
1
+ export type { AppLauncherApp, AppLauncherGroup, AppLauncherLabels, AppLauncherProp, AppShellProp, AuthFooterProp, AuthIdentityProp, OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, PageContainerProp, PageInsetProp, SidebarItemProp, SidebarProductProp, SidebarProp, SidebarSectionProp, TopbarProp, } from "./layout.prop.js";
2
2
  export type { ButtonProp } from "./general.prop.js";
3
3
  export type { InputProp, TextareaProp, FormFieldProp, SearchInputProp, CheckboxProp, CheckboxGroupProp, ChoiceOptionProp, RadioProp, RadioGroupProp, SwitchProp, SliderProp, CalendarProp, DatePickerProp, DateRangePickerProp, TimePickerProp, ColorPickerProp, UploadProp, UploadFileItemProp, UploadVariantProp, TreeOptionProp, TreeFieldNamesProp, CascaderProp, TreeSelectProp, ShowCheckedStrategyProp, TransferProp, TransferItemProp, } from "./data-entry.prop.js";
4
4
  export type { AvatarProp, EmptyStateProp, DescriptionsProp, DescriptionsItemProp, BadgeProp, DataTableProp, QrCodeProp, } from "./data-display.prop.js";
5
5
  export type { ChartDatum, ChartSeriesProp, LineChartProp, BarChartProp, AreaChartProp, PieChartProp, } from "./charts.prop.js";
6
6
  export type { AlertQueryErrorProp, AlertProp, AlertTitleProp, AlertContentProp, AlertDescriptionProp, AlertActionsProp, SheetResponsiveProp, SkeletonRowsProp, } from "./feedback.prop.js";
7
7
  export type { DataStateProp, InfiniteQueryStateProp, InfiniteQueryHelpers, PrefetchLinkProp, } from "./query.prop.js";
8
- export type { PaginationProp, StepsProp, StepItemProp, StepStatusProp, TabsProp, TabItemProp, } from "./navigation.prop.js";
8
+ export type { DropdownMenuPlacementProp, PaginationProp, PaginationSizeProp, PaginationAlignProp, StepsProp, StepItemProp, StepStatusProp, StepsTypeProp, TabsProp, TabItemProp, TabsVariantProp, TabsPlacementProp, TabsExtraProp, TabsOnEditProp, } from "./navigation.prop.js";
9
9
  export type { AppProviderProp, AppContextValue, AppSettingKind, AppSettingPickerProp, AppSettingToggleKind, AppSettingToggleProp, } from "./app.prop.js";
10
10
  export type { ZodSchemaProp, UseZodFormOptionsProp, UseZodFormReturnProp, FormRootProp, FormFieldControlProp, FieldErrorMessageProp, } from "./form.prop.js";
@@ -24,6 +24,8 @@ export type PageContainerMeasureProp = "default" | "narrow" | "medium";
24
24
  export type PageContainerHeaderScaleProp = "document" | "chrome";
25
25
  /** @see PageContainer */
26
26
  export type PageContainerProp = {
27
+ toolbarPad?: PadProp;
28
+ footerPad?: PadProp;
27
29
  title: TitleProp;
28
30
  subtitle?: SubtitleProp;
29
31
  /**
@@ -101,7 +103,7 @@ export type FlexDirectionProp = "row" | "col";
101
103
  export type FlexAlignProp = "start" | "center" | "end" | "stretch" | "baseline";
102
104
  export type FlexJustifyProp = "start" | "center" | "end" | "between" | "around" | "evenly";
103
105
  /** @see Flex */
104
- export type FlexProp = React.HTMLAttributes<HTMLDivElement> & {
106
+ export type FlexProp = React.HTMLAttributes<HTMLElement> & {
105
107
  /**
106
108
  * Render element — `div` (default) or `span` when the Flex sits in a PHRASING context and a
107
109
  * `<div>` would be invalid HTML: inside a `TabsTrigger`/`PopoverTrigger`/`Button` (all of which
@@ -109,8 +111,16 @@ export type FlexProp = React.HTMLAttributes<HTMLDivElement> & {
109
111
  * inside a `<p>`. Same closed shape as `ListRow`'s `as` — it swaps the TAG, nothing else: the
110
112
  * `.ui-flex` rules carry `display: flex`, so the box is identical either way.
111
113
  */
112
- as?: "div" | "span";
113
- direction?: FlexDirectionProp;
114
+ as?: "div" | "span" | "ul" | "ol" | "li";
115
+ /** Lightweight surfaces for rows and notices; no Card elevation by default. */
116
+ surface?: "muted" | "popover" | "warning";
117
+ /** Negative inline inset, using the same spacing scale as pad. */
118
+ bleed?: GapProp;
119
+ /** Floating row actions: show on parent hover, focus-within, and touch. */
120
+ reveal?: "hover";
121
+ direction?: FlexDirectionProp | Partial<Record<"base" | BreakpointProp, FlexDirectionProp>>;
122
+ grow?: boolean;
123
+ shrink?: boolean;
114
124
  gap?: GapProp;
115
125
  /**
116
126
  * CỬA THOÁT: một khoảng cách tính bằng pixel, ngoài mọi bậc của thang.
@@ -213,8 +223,9 @@ export type ResponsiveGridColumnsProp = number | {
213
223
  * `pricing-plans` — the canonical billing/pricing-plan collection: 1 column until the `lg` step
214
224
  * (container ≥ 64rem), then 3 columns from `lg` upward.
215
225
  */
226
+ export type ResponsiveGridFlowProp = "rows" | "columns";
216
227
  export type ResponsiveGridPresetProp = "pricing-plans";
217
- export type MasterDetailRailWidthProp = "compact" | "standard";
228
+ export type MasterDetailRailWidthProp = "narrow" | "compact" | "standard" | "wide";
218
229
  export type MasterDetailRailProp = "master" | "detail";
219
230
  /**
220
231
  * Bounded viewport preset for the master collection. `auto` (default) never bounds it — the region
@@ -262,7 +273,8 @@ export type PageInsetProp = React.HTMLAttributes<HTMLDivElement> & {
262
273
  };
263
274
  /** @see AppShell */
264
275
  export type AppShellProp = {
265
- sidebar: ReactNode;
276
+ /** Omit or pass null/false for a shell without the sidebar landmark or grid track. */
277
+ sidebar?: ReactNode;
266
278
  children: ReactNode;
267
279
  topbar?: ReactNode;
268
280
  topbarLeft?: ReactNode;
@@ -324,6 +336,30 @@ export type AppShellProp = {
324
336
  * to be tellable apart by name; the shell always supplies both defaults rather than requiring
325
337
  * this prop, so the two columns of equal rank behave the same way.
326
338
  */
339
+ /**
340
+ * WHICH EDGE the rail sits on. `start` (default) and `end` are the INLINE edges — logical, so an
341
+ * RTL document mirrors them without a `[dir]` rule; `top` and `bottom` are the block edges, where
342
+ * the rail becomes a full-measure horizontal strip and the shell grows a ROW instead of a column.
343
+ *
344
+ * The scope contract does not move with it: wherever it sits, the rail is PLATFORM scope. The
345
+ * edge is a presentation choice — a docked column reads as permanent chrome (Slack), a bottom
346
+ * strip reads as the phone/tab-bar shape, a top strip as a platform band above the app's own bar.
347
+ * Thickness follows the orientation: `--app-shell-nav-rail-width` as a column,
348
+ * `--app-shell-nav-rail-height` as a strip.
349
+ *
350
+ * Collapsing the sidebar folds the sidebar track only, at every position.
351
+ */
352
+ navRailPosition?: "start" | "end" | "top" | "bottom";
353
+ /**
354
+ * Rail content pinned to its FAR end — the counterpart of `Sidebar`'s `footer`, and the tray end
355
+ * of a taskbar: settings, appearance, the account glyph. It follows the orientation, so it is the
356
+ * bottom of a column and the inline-end of a strip, and it stays put while `navRail` scrolls.
357
+ *
358
+ * A slot rather than "whatever you put last", because pinning it needs an auto margin on the
359
+ * right axis — geometry that would otherwise land in consumer CSS, which this library does not
360
+ * accept. Ignored when `navRail` is not passed: there is no rail to pin anything to.
361
+ */
362
+ navRailEnd?: ReactNode;
327
363
  navRailLabel?: string;
328
364
  /**
329
365
  * Navigation shown in the mobile drawer at the DXS 900px breakpoint, where the docked sidebar is
@@ -386,6 +422,22 @@ export type AuthShellProp = {
386
422
  * flow geometry.
387
423
  */
388
424
  measure?: "default" | "wide";
425
+ /**
426
+ * Block-axis placement of the auth column, ORTHOGONAL to `preset` the way `variant` is: a preset
427
+ * owns the page MEASURE (card width, inline gutters, section rhythm), `align` owns where that
428
+ * column sits vertically.
429
+ *
430
+ * Omit it to keep the preset's own choice — `"login"` and `"registration"` anchor so a
431
+ * requester/identity line that wraps to two lines cannot move the card, every other preset
432
+ * centres. Pass `"center"` for a vertically centred column (the block-start inset collapses to
433
+ * the preset's block-end one, desktop and mobile, so the padding is symmetric) or `"anchored"`
434
+ * for a top-anchored one. This replaces re-declaring a preset's offset tokens from consumer CSS.
435
+ *
436
+ * HAZARD on a tall flow: a vertically centred tall card overflows ABOVE the scroll origin on a
437
+ * short viewport, putting its first field out of reach. That is why `"registration"` anchors by
438
+ * default; `"center"` is legal there but is the caller's judgement.
439
+ */
440
+ align?: "anchored" | "center";
389
441
  /**
390
442
  * Vertical density scoped to auth-card descendants. The canonical variant defaults to
391
443
  * `"compact"`; the default variant defaults to `"comfortable"`.
@@ -467,6 +519,10 @@ export type MobileShellProp = {
467
519
  * so a service retunes a day divider or an unread watermark from its theme and never forks CSS.
468
520
  */
469
521
  export type SeparatorProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
522
+ labelSize?: "2xs" | "xs" | "sm" | "md";
523
+ space?: GapProp;
524
+ hideBelow?: BreakpointProp;
525
+ hideFrom?: BreakpointProp;
470
526
  /** Rule axis. Default `"horizontal"`. */
471
527
  orientation?: OrientationProp;
472
528
  /**
@@ -869,14 +925,170 @@ export type OrgSwitcherProp = {
869
925
  error?: ReactNode;
870
926
  onRetry?: () => void;
871
927
  labels: OrgSwitcherLabels;
928
+ /**
929
+ * WHICH SURFACE the panel opens on.
930
+ *
931
+ * - `"auto"` (default) — popover above `--sheet-responsive-breakpoint-width`, focus-trapped
932
+ * bottom Sheet at or below it.
933
+ * - `"dialog"` — a centred modal above that breakpoint, the same bottom Sheet below it. Reach for
934
+ * this once the panel carries more than a name per row — a role, a plan, a member count, a
935
+ * "create organization" action. A popover is anchored to its trigger, clipped by the viewport
936
+ * and sized by `--org-switcher-menu-width`; a dialog has a real title, a scrolling body and a
937
+ * footer, and takes the reader's full attention, which is the right trade when switching
938
+ * organization re-scopes everything on screen.
939
+ * - `"popover"` / `"sheet"` — pinned to one surface at every width. Useful for a deterministic
940
+ * embedded surface or a component test, rarely in a product.
941
+ *
942
+ * `auto` and `dialog` are the two RESPONSIVE pairs and differ only in their desktop half; the
943
+ * mobile half is the same Sheet, because a centred modal on a phone is a Sheet with worse
944
+ * ergonomics. All four resolve the breakpoint through the shared `useSheetResponsiveMode()`
945
+ * hook, so a service moves the line once for every overlay.
946
+ */
947
+ responsive?: "auto" | "popover" | "sheet" | "dialog";
948
+ open?: boolean;
949
+ onOpenChange?: (open: boolean) => void;
950
+ className?: ClassNameProp;
951
+ };
952
+ /**
953
+ * One app tile in the public {@link AppLauncher} contract.
954
+ *
955
+ * A tile is a REAL LINK, always. There is no `onClick`-only entry and no `disabled` entry: an app
956
+ * the viewer may not open is an app the consumer does not pass. A launcher that renders inert tiles
957
+ * is a launcher that teaches its users to stop trusting the grid.
958
+ */
959
+ export type AppLauncherApp = {
960
+ /** Stable app identifier — the React key, and the `data-app` hook an end-to-end test holds. */
961
+ id: string;
962
+ /** Visible app name. It is also the tile's accessible name — the mark is presentational. */
963
+ name: string;
964
+ /** The destination. Required, because the tile IS an `<a href>` (WCAG 2.2 / APG: link, not button). */
965
+ href: string;
966
+ /**
967
+ * Owned mark — a Lucide icon, a `<Logo>`, an `<img>`, an `<Avatar>`. Rendered `aria-hidden`;
968
+ * when omitted the launcher falls back to the first character of `name`, exactly as
969
+ * `OrgSwitcher` does for an organization with no avatar.
970
+ */
971
+ icon?: ReactNode;
972
+ /** The app the viewer is currently inside. That tile carries `aria-current="page"`. */
973
+ current?: boolean;
974
+ /**
975
+ * The destination leaves this SPA. Such a tile renders a plain `<a target="_blank" rel="noreferrer
976
+ * noopener">` and DELIBERATELY bypasses `linkComponent`: a client-side router link to another
977
+ * origin is a router asked to route somewhere it does not own, which is how a launcher entry
978
+ * turns into a blank screen. Pair it with `labels.externalHint` (WCAG 3.2.5).
979
+ */
980
+ external?: boolean;
981
+ };
982
+ /** A labelled section of the launcher grid — the "more from …" band of the Google-style panel. */
983
+ export type AppLauncherGroup = {
984
+ /** Section heading. Rendered as a real heading and used to name the section's own grid. */
985
+ label: string;
986
+ apps: readonly AppLauncherApp[];
987
+ };
988
+ /** Localized copy owned by the consuming product, never hard-coded by the component. */
989
+ export type AppLauncherLabels = {
990
+ /**
991
+ * Accessible name of the nine-dot trigger ("Apps" / "アプリ"). A plain string, NOT a function of
992
+ * the current app the way `OrgSwitcherLabels.trigger` is a function of the organization: the
993
+ * launcher's trigger shows no current value, so naming one in the trigger would announce a
994
+ * destination the button does not go to.
995
+ */
996
+ trigger: string;
997
+ /** Panel name — the popover's accessible name and the bottom Sheet's header title. */
998
+ title: string;
999
+ empty: string;
1000
+ loading: string;
1001
+ retry?: string;
1002
+ /** Screen-reader suffix for an `external` tile, e.g. "(opens in a new tab)" (WCAG 3.2.5). */
1003
+ externalHint?: string;
1004
+ };
1005
+ /**
1006
+ * @see AppLauncher — the PLATFORM-scope app switcher that lives in the topbar.
1007
+ *
1008
+ * Related, and repeatedly confused with it:
1009
+ *
1010
+ * - `ServiceLauncherCard` (data-display) is also a launcher tile, but a PAGE-SIZED one: status,
1011
+ * hostname, plan, an action button, a reason it is locked. It belongs on a service-catalogue
1012
+ * page, where choosing is a considered act. `AppLauncher`'s tile is bar-sized — mark plus name,
1013
+ * the whole tile a link — because switching app is a reflex, not a decision. Neither is built
1014
+ * out of the other; a grid of `ServiceLauncherCard`s inside a popover is the wrong component.
1015
+ * - `AppShellProp.navRail` expresses the SAME platform scope as a docked column. These are the two
1016
+ * ways to say it, and a product picks ONE: the launcher suits a platform with MANY apps where
1017
+ * switching is occasional (the Google Workspace shape), the rail suits a single product where
1018
+ * switching workspace is a constant action worth permanent screen width (the Slack shape).
1019
+ * Shipping both puts one scope in two places and makes neither authoritative.
1020
+ */
1021
+ export type AppLauncherProp = {
1022
+ /** Ungrouped apps, rendered first, with no heading above them. */
1023
+ apps: readonly AppLauncherApp[];
1024
+ /** Labelled sections rendered after `apps`, in order. */
1025
+ groups?: readonly AppLauncherGroup[];
1026
+ labels: AppLauncherLabels;
1027
+ /**
1028
+ * Grid column count. Omit it and the panel keeps the stylesheet's own `--app-launcher-columns`
1029
+ * (3, the Google-launcher shape, declared on `.ui-app-launcher-panel`): the default is where a
1030
+ * theme can reach it, and this prop is the per-instance override written inline on top.
1031
+ *
1032
+ * `responsive="fullscreen"` steps that DEFAULT up with the surface — 3 · 4 · 5 · 6 on the house
1033
+ * container ladder — because a fixed three columns on a full viewport is three columns of tiles
1034
+ * and a screen of nothing. Passing `columns` still wins everywhere: an inline custom property
1035
+ * beats every stylesheet rule, so a stated count is a stated count on both surfaces.
1036
+ */
1037
+ columns?: number;
1038
+ /**
1039
+ * THE framework-router contract, reusing `SidebarLinkComponentProp` VERBATIM — the same type
1040
+ * `Sidebar` and `NavList` take, so a service that already wrote `inertiaSidebarLink(Link)` or
1041
+ * `createSidebarLink(Link, "to")` for its rail hands the same value here. The launcher still
1042
+ * composes the tile (mark, name, `aria-current`, the external hint); the consumer supplies only
1043
+ * the element type. `external` apps bypass it — see {@link AppLauncherApp.external}.
1044
+ */
1045
+ linkComponent?: SidebarLinkComponentProp;
1046
+ loading?: boolean;
1047
+ /** Error content replaces the grid while preserving the trigger and the retry affordance. */
1048
+ error?: ReactNode;
1049
+ onRetry?: () => void;
872
1050
  /**
873
1051
  * `"auto"` (default) uses the desktop popover above `--sheet-responsive-breakpoint-width` and a
874
1052
  * focus-trapped bottom Sheet at/below it — the SAME token that drives `SheetContent
875
1053
  * responsive="auto"`, resolved through the shared `useSheetResponsiveMode()` hook, so a service
876
1054
  * moves the drawer line once for every overlay instead of per component.
877
- * Explicit modes are useful for deterministic embedded surfaces and component tests.
1055
+ *
1056
+ * `"fullscreen"` is the LAUNCHPAD: one full-viewport surface, the page behind it blurred, the
1057
+ * grid floating on that ground at tile size — the macOS Launchpad / Windows Start shape. It is
1058
+ * pinned at every width, because a start surface that becomes a popover on a wide screen is two
1059
+ * different products.
1060
+ *
1061
+ * WHEN IT IS RIGHT, AND WHEN IT IS NOT. This prop used to say a modal was always wrong here:
1062
+ * "a launcher grid is a jump table, and a modal that takes over the screen to offer nine links
1063
+ * is heavier than the errand." That reasoning is sound for a launcher in ONE application's
1064
+ * topbar, where the grid is a shortcut away from the work on screen and the work should stay
1065
+ * visible. It does not hold for a PLATFORM start bar — a strip that is present in every service,
1066
+ * whose launcher is the primary way to move between products rather than a shortcut. There the
1067
+ * grid IS the errand, the page behind it is the thing being left, and the interruption is the
1068
+ * point. Keep `"auto"` for a topbar launcher; reach for `"fullscreen"` for a dock.
1069
+ */
1070
+ responsive?: "auto" | "popover" | "sheet" | "fullscreen";
1071
+ /**
1072
+ * The BOX the trigger takes — the same split `AppSettingToggle` draws, and for the same reason.
1073
+ * `bar` (default) is a `TopbarItem`: a cell as tall as the bar, whose hover is the bar's own
1074
+ * surface. `icon` is a square ghost `Button`, for chrome that is NOT a bar — a nav rail, a card
1075
+ * header, a toolbar. A `TopbarItem` outside a bar has nothing to bleed to: it stretches to a
1076
+ * container that never set a band height, and its squared corners and full-bleed hover read as a
1077
+ * broken cell. The panel, the grid and the responsive contract are identical either way.
1078
+ */
1079
+ appearance?: "bar" | "icon";
1080
+ /**
1081
+ * Which side of the trigger the panel opens on, and how it aligns to it. Both default from
1082
+ * `appearance` — a bar drops the grid below and aligns to the bar's end; anything else opens to
1083
+ * the inline-end aligned to the trigger's start.
1084
+ *
1085
+ * State them when the chrome can be RE-DOCKED. `appearance` says the trigger is not in a bar; it
1086
+ * cannot say which way is out, and a rail pinned to the top edge still opens downward. Measured
1087
+ * without this: a launcher in a top strip opened sideways and left the panel 440px from the
1088
+ * trigger it belonged to.
878
1089
  */
879
- responsive?: "auto" | "popover" | "sheet";
1090
+ side?: "top" | "right" | "bottom" | "left";
1091
+ align?: "start" | "center" | "end";
880
1092
  open?: boolean;
881
1093
  onOpenChange?: (open: boolean) => void;
882
1094
  className?: ClassNameProp;
@@ -890,7 +1102,21 @@ export type SidebarProp = {
890
1102
  sections?: SidebarSectionProp[];
891
1103
  product?: SidebarProductProp;
892
1104
  onProductClick?: () => void;
893
- brand?: ReactNode;
1105
+ /**
1106
+ * Header slot ABOVE the navigation, replacing `product`.
1107
+ *
1108
+ * Pass a FUNCTION to follow the EFFECTIVE collapsed state. A plain node cannot: `AppShell` hands
1109
+ * the same Sidebar to the drawer and the drawer un-collapses the rows (see `NavSurface`), so a
1110
+ * node built from the consumer's own `collapsed` boolean renders a glyph-only lockup inside a
1111
+ * full-width drawer. The escape hatch consumers reach for is a SECOND hand-built `Sidebar` in
1112
+ * `AppShell.mobileNav` — and that override is precisely what switches off `railInDrawer`, so the
1113
+ * `navRail` silently stops reaching mobile. The function is called with the surface-effective
1114
+ * value, which removes the reason to build the second node at all.
1115
+ *
1116
+ * SCOPE: this is the APP's brand lockup. A PLATFORM switch (which organization, which app) does
1117
+ * not belong here — see AppShell's `navRail` for where it goes and why.
1118
+ */
1119
+ brand?: ReactNode | ((collapsed: boolean) => ReactNode);
894
1120
  collapsed?: boolean;
895
1121
  children?: ChildrenProp;
896
1122
  /**
@@ -939,6 +1165,8 @@ export type SidebarProp = {
939
1165
  * configured via THEIR own props and dropped into a slot.
940
1166
  */
941
1167
  export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
1168
+ height?: "bar" | "auto";
1169
+ pad?: PadProp;
942
1170
  /** Inline-start cluster — typically the sidebar toggle + a brand mark (`Avatar`) + primary nav. */
943
1171
  start?: ReactNode;
944
1172
  /** Center cluster — optional (e.g. a search trigger or a page/entity switcher). */
@@ -964,6 +1192,25 @@ export type TopbarItemProp = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>,
964
1192
  * Render the bar-cell shape ONTO the child instead of emitting a `<button>` — for a router link,
965
1193
  * or for a menu/popover trigger that needs to own the element itself.
966
1194
  */
1195
+ /** Hide below a shared responsive breakpoint, without changing cell height. */
1196
+ hideBelow?: BreakpointProp;
1197
+ /**
1198
+ * Unread count OVERLAID on the cell's glyph — the notification-bell affordance the cell's own use
1199
+ * cases name (gh#398). Pass the CONTENT ONLY, exactly like `SidebarItemProp.badge`: a number, a
1200
+ * string, `"99+"`. Position, size and colour come from `--topbar-item-badge-*`, so the count does
1201
+ * NOT widen the cell (an inline chip pushes the end cluster's shrink budget around every time the
1202
+ * count crosses a digit boundary) and a theme can retune the overlay once.
1203
+ *
1204
+ * The count is not an accessible name: give the cell an `aria-label` that says what the number
1205
+ * means (`aria-label={t("topbar.notifications.unread", { count })}`).
1206
+ */
1207
+ badge?: ReactNode;
1208
+ /**
1209
+ * Emphasis of `badge`. Vocabulary shared VERBATIM with `SidebarItemProp.badgeTone` so one count
1210
+ * pill reads the same in the rail and in the bar: `destructive` when the count is addressed to
1211
+ * the user rather than merely unread.
1212
+ */
1213
+ badgeTone?: SidebarBadgeToneProp;
967
1214
  asChild?: boolean;
968
1215
  children?: ReactNode;
969
1216
  className?: ClassNameProp;