@godxjp/ui 19.6.0 → 20.1.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 (326) hide show
  1. package/README.md +8 -2
  2. package/dist/app/theme-axes.d.ts +2 -0
  3. package/dist/app/theme-axes.js +45 -0
  4. package/dist/components/charts/chart-cartesian.d.ts +2 -1
  5. package/dist/components/charts/chart-cartesian.js +52 -3
  6. package/dist/components/charts/chart-category-axis.d.ts +55 -0
  7. package/dist/components/charts/chart-category-axis.js +93 -0
  8. package/dist/components/charts/chart-frame.d.ts +10 -1
  9. package/dist/components/charts/chart-frame.js +19 -3
  10. package/dist/components/charts/compact-bar-trend.d.ts +1 -1
  11. package/dist/components/charts/compact-bar-trend.js +2 -0
  12. package/dist/components/charts/pie-chart.d.ts +1 -1
  13. package/dist/components/charts/pie-chart.js +12 -1
  14. package/dist/components/charts/recharts-peer.d.ts +49 -0
  15. package/dist/components/charts/recharts-peer.js +45 -0
  16. package/dist/components/data-display/card.d.ts +14 -3
  17. package/dist/components/data-display/card.js +22 -4
  18. package/dist/components/data-display/code-block.js +6 -2
  19. package/dist/components/data-display/collapsible.d.ts +23 -4
  20. package/dist/components/data-display/collapsible.js +119 -4
  21. package/dist/components/data-display/data-table.d.ts +39 -4
  22. package/dist/components/data-display/data-table.js +802 -270
  23. package/dist/components/data-display/descriptions.d.ts +27 -6
  24. package/dist/components/data-display/descriptions.js +58 -17
  25. package/dist/components/data-display/index.d.ts +5 -1
  26. package/dist/components/data-display/index.js +4 -0
  27. package/dist/components/data-display/legend.d.ts +15 -0
  28. package/dist/components/data-display/legend.js +11 -0
  29. package/dist/components/data-display/list-row.d.ts +10 -1
  30. package/dist/components/data-display/list-row.js +8 -2
  31. package/dist/components/data-display/popover.d.ts +60 -7
  32. package/dist/components/data-display/popover.js +268 -39
  33. package/dist/components/data-display/progress.d.ts +50 -2
  34. package/dist/components/data-display/progress.js +57 -9
  35. package/dist/components/data-display/range-timeline.d.ts +38 -0
  36. package/dist/components/data-display/range-timeline.js +161 -0
  37. package/dist/components/data-display/scroll-area.js +13 -2
  38. package/dist/components/data-display/service-launcher-card.js +10 -10
  39. package/dist/components/data-display/table.d.ts +11 -3
  40. package/dist/components/data-display/table.js +48 -18
  41. package/dist/components/data-display/tree-list.js +4 -9
  42. package/dist/components/data-entry/calendar.d.ts +2 -2
  43. package/dist/components/data-entry/calendar.js +79 -33
  44. package/dist/components/data-entry/cascader.d.ts +1 -1
  45. package/dist/components/data-entry/cascader.js +188 -24
  46. package/dist/components/data-entry/checkbox.d.ts +19 -6
  47. package/dist/components/data-entry/checkbox.js +55 -16
  48. package/dist/components/data-entry/choice-option.d.ts +1 -1
  49. package/dist/components/data-entry/color-picker.d.ts +1 -1
  50. package/dist/components/data-entry/color-picker.js +17 -5
  51. package/dist/components/data-entry/control-appearance.d.ts +64 -0
  52. package/dist/components/data-entry/control-appearance.js +39 -0
  53. package/dist/components/data-entry/control-surface.d.ts +61 -0
  54. package/dist/components/data-entry/control-surface.js +39 -0
  55. package/dist/components/data-entry/date-picker.d.ts +1 -1
  56. package/dist/components/data-entry/date-picker.js +344 -113
  57. package/dist/components/data-entry/date-range-picker.d.ts +2 -2
  58. package/dist/components/data-entry/date-range-picker.js +278 -140
  59. package/dist/components/data-entry/field.js +0 -1
  60. package/dist/components/data-entry/form-field.d.ts +1 -1
  61. package/dist/components/data-entry/form-field.js +39 -4
  62. package/dist/components/data-entry/form.d.ts +4 -0
  63. package/dist/components/data-entry/form.js +5 -3
  64. package/dist/components/data-entry/index.d.ts +7 -3
  65. package/dist/components/data-entry/index.js +10 -1
  66. package/dist/components/data-entry/input-otp.d.ts +1 -1
  67. package/dist/components/data-entry/input.d.ts +12 -34
  68. package/dist/components/data-entry/input.js +92 -24
  69. package/dist/components/data-entry/label.d.ts +3 -2
  70. package/dist/components/data-entry/label.js +23 -10
  71. package/dist/components/data-entry/month-picker.d.ts +2 -2
  72. package/dist/components/data-entry/month-picker.js +47 -10
  73. package/dist/components/data-entry/month-range-picker.d.ts +2 -2
  74. package/dist/components/data-entry/month-range-picker.js +51 -11
  75. package/dist/components/data-entry/number-input.d.ts +7 -0
  76. package/dist/components/data-entry/number-input.js +147 -98
  77. package/dist/components/data-entry/password-input.d.ts +1 -1
  78. package/dist/components/data-entry/radio.d.ts +1 -1
  79. package/dist/components/data-entry/radio.js +61 -13
  80. package/dist/components/data-entry/search-input.d.ts +1 -1
  81. package/dist/components/data-entry/search-input.js +16 -2
  82. package/dist/components/data-entry/search-select.d.ts +2 -2
  83. package/dist/components/data-entry/search-select.js +209 -78
  84. package/dist/components/data-entry/select.d.ts +22 -4
  85. package/dist/components/data-entry/select.js +215 -163
  86. package/dist/components/data-entry/slider.d.ts +9 -1
  87. package/dist/components/data-entry/slider.js +87 -9
  88. package/dist/components/data-entry/switch.d.ts +3 -0
  89. package/dist/components/data-entry/switch.js +29 -3
  90. package/dist/components/data-entry/textarea.d.ts +14 -56
  91. package/dist/components/data-entry/textarea.js +80 -33
  92. package/dist/components/data-entry/time-picker.d.ts +2 -2
  93. package/dist/components/data-entry/time-picker.js +333 -109
  94. package/dist/components/data-entry/time-range-picker.d.ts +5 -0
  95. package/dist/components/data-entry/time-range-picker.js +89 -0
  96. package/dist/components/data-entry/transfer.d.ts +1 -1
  97. package/dist/components/data-entry/transfer.js +99 -31
  98. package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
  99. package/dist/components/data-entry/tree-select.d.ts +1 -1
  100. package/dist/components/data-entry/tree-select.js +197 -92
  101. package/dist/components/data-entry/tree-utils.d.ts +1 -1
  102. package/dist/components/data-entry/tree-utils.js +7 -14
  103. package/dist/components/data-entry/upload-files.d.ts +2 -0
  104. package/dist/components/data-entry/upload-files.js +31 -0
  105. package/dist/components/data-entry/upload-request.d.ts +4 -0
  106. package/dist/components/data-entry/upload-request.js +53 -0
  107. package/dist/components/data-entry/upload-types.d.ts +28 -0
  108. package/dist/components/data-entry/upload-types.js +2 -0
  109. package/dist/components/data-entry/upload.d.ts +2 -2
  110. package/dist/components/data-entry/upload.js +482 -117
  111. package/dist/components/feedback/dialog.d.ts +105 -38
  112. package/dist/components/feedback/dialog.js +268 -193
  113. package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
  114. package/dist/components/feedback/overlay-close-focus.js +31 -0
  115. package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
  116. package/dist/components/feedback/sheet.d.ts +53 -11
  117. package/dist/components/feedback/sheet.js +150 -81
  118. package/dist/components/feedback/tooltip.d.ts +51 -7
  119. package/dist/components/feedback/tooltip.js +107 -25
  120. package/dist/components/general/button.d.ts +1 -1
  121. package/dist/components/general/button.js +7 -3
  122. package/dist/components/general/index.d.ts +1 -0
  123. package/dist/components/general/index.js +2 -0
  124. package/dist/components/general/logo.d.ts +17 -0
  125. package/dist/components/general/logo.js +22 -16
  126. package/dist/components/general/typography.d.ts +3 -0
  127. package/dist/components/general/typography.js +15 -1
  128. package/dist/components/general/visually-hidden.d.ts +3 -0
  129. package/dist/components/general/visually-hidden.js +9 -0
  130. package/dist/components/layout/app-launcher.d.ts +34 -0
  131. package/dist/components/layout/app-launcher.js +228 -0
  132. package/dist/components/layout/app-shell.d.ts +3 -1
  133. package/dist/components/layout/app-shell.js +69 -12
  134. package/dist/components/layout/aspect-ratio.js +0 -1
  135. package/dist/components/layout/auth-divider.js +0 -1
  136. package/dist/components/layout/breadcrumb.d.ts +14 -2
  137. package/dist/components/layout/breadcrumb.js +46 -4
  138. package/dist/components/layout/flex.d.ts +1 -1
  139. package/dist/components/layout/flex.js +41 -3
  140. package/dist/components/layout/index.d.ts +3 -0
  141. package/dist/components/layout/index.js +5 -1
  142. package/dist/components/layout/nav-surface.d.ts +26 -0
  143. package/dist/components/layout/nav-surface.js +17 -0
  144. package/dist/components/layout/org-switcher.d.ts +5 -1
  145. package/dist/components/layout/org-switcher.js +25 -4
  146. package/dist/components/layout/page-container.d.ts +1 -1
  147. package/dist/components/layout/page-container.js +5 -3
  148. package/dist/components/layout/responsive-grid.d.ts +16 -2
  149. package/dist/components/layout/responsive-grid.js +29 -2
  150. package/dist/components/layout/separator.js +0 -1
  151. package/dist/components/layout/sidebar.js +4 -1
  152. package/dist/components/layout/split-pane.d.ts +14 -1
  153. package/dist/components/layout/split-pane.js +26 -13
  154. package/dist/components/layout/topbar-item.d.ts +3 -0
  155. package/dist/components/layout/topbar-item.js +19 -5
  156. package/dist/components/layout/topbar.d.ts +1 -1
  157. package/dist/components/layout/topbar.js +27 -6
  158. package/dist/components/navigation/app-setting-picker.js +21 -2
  159. package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
  160. package/dist/components/navigation/app-setting-toggle.js +96 -0
  161. package/dist/components/navigation/dropdown-menu.d.ts +227 -18
  162. package/dist/components/navigation/dropdown-menu.js +402 -118
  163. package/dist/components/navigation/index.d.ts +2 -0
  164. package/dist/components/navigation/index.js +2 -0
  165. package/dist/components/navigation/pagination-utils.d.ts +2 -1
  166. package/dist/components/navigation/pagination.d.ts +2 -2
  167. package/dist/components/navigation/pagination.js +155 -83
  168. package/dist/components/navigation/steps.d.ts +2 -2
  169. package/dist/components/navigation/steps.js +29 -4
  170. package/dist/components/navigation/tabs.d.ts +29 -16
  171. package/dist/components/navigation/tabs.js +374 -144
  172. package/dist/components/ui/accordion.d.ts +50 -5
  173. package/dist/components/ui/accordion.js +239 -33
  174. package/dist/components/ui/aspect-ratio.d.ts +23 -2
  175. package/dist/components/ui/aspect-ratio.js +15 -13
  176. package/dist/components/ui/avatar.d.ts +29 -4
  177. package/dist/components/ui/avatar.js +111 -25
  178. package/dist/components/ui/hover-card.d.ts +42 -4
  179. package/dist/components/ui/hover-card.js +183 -27
  180. package/dist/components/ui/input-otp.d.ts +26 -28
  181. package/dist/components/ui/input-otp.js +50 -7
  182. package/dist/components/ui/label.js +0 -1
  183. package/dist/components/ui/password-input.d.ts +36 -2
  184. package/dist/components/ui/password-input.js +27 -7
  185. package/dist/components/ui/rating.d.ts +25 -0
  186. package/dist/components/ui/rating.js +67 -27
  187. package/dist/components/ui/segmented.d.ts +12 -3
  188. package/dist/components/ui/segmented.js +17 -2
  189. package/dist/components/ui/separator.d.ts +12 -2
  190. package/dist/components/ui/separator.js +30 -9
  191. package/dist/components/ui/tag-input.d.ts +45 -0
  192. package/dist/components/ui/tag-input.js +109 -27
  193. package/dist/components/ui/toggle-group.d.ts +50 -5
  194. package/dist/components/ui/toggle-group.js +79 -20
  195. package/dist/components/ui/toggle.d.ts +31 -5
  196. package/dist/components/ui/toggle.js +42 -3
  197. package/dist/form/form-context.d.ts +29 -0
  198. package/dist/form/form-context.js +44 -4
  199. package/dist/form/form-field-array.d.ts +22 -0
  200. package/dist/form/form-field-array.js +50 -0
  201. package/dist/form/form-field-control.d.ts +2 -1
  202. package/dist/form/form-field-control.js +116 -40
  203. package/dist/form/form-root.d.ts +2 -1
  204. package/dist/form/form-root.js +142 -23
  205. package/dist/form/index.d.ts +3 -1
  206. package/dist/form/index.js +12 -1
  207. package/dist/i18n/messages/en.json +64 -5
  208. package/dist/i18n/messages/ja.json +64 -5
  209. package/dist/i18n/messages/vi.json +64 -5
  210. package/dist/inertia/index.d.ts +20 -0
  211. package/dist/inertia/index.js +60 -1
  212. package/dist/lib/control-styles.d.ts +25 -3
  213. package/dist/lib/control-styles.js +9 -3
  214. package/dist/lib/datetime/picker-format.d.ts +8 -0
  215. package/dist/lib/datetime/picker-format.js +54 -0
  216. package/dist/lib/slot.d.ts +32 -0
  217. package/dist/lib/slot.js +22 -0
  218. package/dist/lib/variants.d.ts +22 -3
  219. package/dist/lib/variants.js +56 -1
  220. package/dist/props/components/app.prop.d.ts +25 -1
  221. package/dist/props/components/charts.prop.d.ts +21 -0
  222. package/dist/props/components/data-display.prop.d.ts +72 -4
  223. package/dist/props/components/data-entry.prop.d.ts +718 -46
  224. package/dist/props/components/form.prop.d.ts +142 -4
  225. package/dist/props/components/general.prop.d.ts +17 -1
  226. package/dist/props/components/index.d.ts +3 -3
  227. package/dist/props/components/layout.prop.d.ts +281 -11
  228. package/dist/props/components/navigation.prop.d.ts +114 -4
  229. package/dist/props/registry.d.ts +397 -11
  230. package/dist/props/registry.js +526 -11
  231. package/dist/props/vocabulary/content.prop.d.ts +1 -1
  232. package/dist/props/vocabulary/data.prop.d.ts +179 -1
  233. package/dist/props/vocabulary/index.d.ts +5 -5
  234. package/dist/props/vocabulary/interaction.prop.d.ts +68 -3
  235. package/dist/props/vocabulary/layout.prop.d.ts +57 -1
  236. package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
  237. package/dist/props/vocabulary/shared.prop.d.ts +33 -0
  238. package/dist/styles/badge-layout.css +2 -1
  239. package/dist/styles/card-layout.css +26 -4
  240. package/dist/styles/chart-layout.css +15 -0
  241. package/dist/styles/control.css +657 -15
  242. package/dist/styles/core.css +5 -2
  243. package/dist/styles/data-display-layout.css +313 -6
  244. package/dist/styles/data-entry-layout.css +13 -0
  245. package/dist/styles/focus-ring.css +4 -0
  246. package/dist/styles/index.css +5 -2
  247. package/dist/styles/layout.css +327 -4
  248. package/dist/styles/navigation-layout.css +227 -0
  249. package/dist/styles/shell-layout.css +369 -13
  250. package/dist/styles/table-layout.css +95 -6
  251. package/dist/styles/text-layout.css +10 -4
  252. package/dist/tokens/base.css +2 -1
  253. package/dist/tokens/components/badge.css +1 -0
  254. package/dist/tokens/components/chart.css +6 -0
  255. package/dist/tokens/components/control.css +79 -2
  256. package/dist/tokens/components/data-display.css +20 -0
  257. package/dist/tokens/components/descriptions.css +11 -0
  258. package/dist/tokens/components/flex.css +8 -0
  259. package/dist/tokens/components/navigation.css +55 -0
  260. package/dist/tokens/components/shell.css +53 -3
  261. package/dist/tokens/components/table.css +14 -0
  262. package/dist/tokens/foundation.css +2 -0
  263. package/dist/tokens/semantic/layout.css +9 -0
  264. package/docs/COMPONENTS.md +9 -3
  265. package/docs/CONSUMER-RULES.md +13 -0
  266. package/docs/DESIGN-AUTHORITY.md +145 -83
  267. package/docs/DEVELOPMENT.md +4 -3
  268. package/docs/FORMS.md +151 -6
  269. package/docs/FRAME-COVERAGE-REPORT.md +32 -17
  270. package/docs/README.md +15 -15
  271. package/docs/STANDARDS-vocabulary-tokens.md +1 -1
  272. package/docs/TESTING.md +15 -6
  273. package/docs/WHAT-BELONGS-HERE.md +179 -0
  274. package/docs/charts/cjk-category-axis.tsx +81 -0
  275. package/docs/data-display/card/index.tsx +22 -0
  276. package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
  277. package/docs/data-display/data-table/index.tsx +23 -0
  278. package/docs/data-display/descriptions.tsx +41 -0
  279. package/docs/data-display/legend.tsx +145 -0
  280. package/docs/data-display/progress.tsx +32 -0
  281. package/docs/data-display/scroll-area.tsx +31 -25
  282. package/docs/data-display/table.tsx +104 -45
  283. package/docs/data-display/timeline.tsx +41 -0
  284. package/docs/data-entry/date-picker.tsx +85 -0
  285. package/docs/data-entry/date-range-picker.tsx +26 -0
  286. package/docs/data-entry/form-dynamic-fields.tsx +199 -0
  287. package/docs/data-entry/form.tsx +74 -4
  288. package/docs/data-entry/input-otp.tsx +24 -0
  289. package/docs/data-entry/input.tsx +46 -1
  290. package/docs/data-entry/month-range-picker.tsx +1 -1
  291. package/docs/data-entry/segmented.tsx +1 -1
  292. package/docs/data-entry/select.tsx +29 -2
  293. package/docs/data-entry/switch.tsx +41 -0
  294. package/docs/data-entry/textarea.tsx +38 -0
  295. package/docs/data-entry/time-picker.tsx +85 -1
  296. package/docs/data-entry/time-range-picker.tsx +55 -0
  297. package/docs/data-entry/transfer.tsx +17 -0
  298. package/docs/data-entry/upload.tsx +56 -12
  299. package/docs/feedback/sheet.tsx +1 -1
  300. package/docs/feedback/tooltip.tsx +1 -1
  301. package/docs/general/activity.tsx +2 -2
  302. package/docs/general/button/index.tsx +14 -1
  303. package/docs/general/typography.tsx +14 -1
  304. package/docs/layout/app-launcher.tsx +151 -0
  305. package/docs/layout/app-shell-arrangements.tsx +225 -0
  306. package/docs/layout/app-shell.tsx +11 -0
  307. package/docs/layout/aspect-ratio.tsx +1 -1
  308. package/docs/layout/flex.tsx +50 -0
  309. package/docs/layout/responsive-grid.tsx +21 -1
  310. package/docs/layout/topbar.tsx +5 -9
  311. package/docs/navigation/app-setting-picker.tsx +37 -1
  312. package/docs/navigation/app-setting-toggle.tsx +111 -0
  313. package/docs/navigation/breadcrumb.tsx +48 -0
  314. package/docs/navigation/dropdown-menu.tsx +21 -0
  315. package/docs/navigation/pagination.tsx +52 -0
  316. package/docs/navigation/steps.tsx +37 -0
  317. package/docs/navigation/tabs.tsx +97 -1
  318. package/docs/query/button-refetch.tsx +1 -0
  319. package/package.json +28 -8
  320. package/scripts/_agent-setup.mjs +182 -3
  321. package/scripts/consumer-rule.md +98 -0
  322. package/scripts/guinea-pig-skill.md +322 -0
  323. package/scripts/init-guinea-pig.mjs +84 -0
  324. package/scripts/postinstall.mjs +13 -2
  325. package/scripts/ui-audit.mjs +351 -37
  326. /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
@@ -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;
@@ -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";
9
- export type { AppProviderProp, AppContextValue, AppSettingKind, AppSettingPickerProp, } from "./app.prop.js";
8
+ export type { DropdownMenuPlacementProp, PaginationProp, PaginationSizeProp, PaginationAlignProp, StepsProp, StepItemProp, StepStatusProp, StepsTypeProp, TabsProp, TabItemProp, TabsVariantProp, TabsPlacementProp, TabsExtraProp, TabsOnEditProp, } from "./navigation.prop.js";
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";
@@ -1,7 +1,7 @@
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, StatusProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, OrientationProp, TextAlignProp, TextToneProp, BreakpointProp, GapProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp, ToneProp } from "../vocabulary/index.js";
4
+ import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, OrientationProp, TextAlignProp, TextToneProp, BreakpointProp, GapProp, PadProp, PadRawProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp, ToneProp, WidthProp } from "../vocabulary/index.js";
5
5
  import type { EmptyStateToneProp } from "./data-display.prop.js";
6
6
  /**
7
7
  * Arrangement of the page header's title band and its `extra` slot below the 640px step.
@@ -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,9 +111,66 @@ 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;
125
+ /**
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.
127
+ *
128
+ * ## Vì sao một design system lại mở cửa thoát
129
+ *
130
+ * Vì bịt nó lại không làm thiết kế biến mất — nó chỉ làm cách làm ĐÚNG trở
131
+ * thành bất hợp pháp. Một bản thiết kế thật dùng 2px, 5px, 6px, 10px; thang
132
+ * gốc không có bậc nào như thế. Người viết mã khi ấy có ba nước, và cả ba
133
+ * đều tệ: làm tròn xuống bậc gần nhất (lệch bố cục, và "gần nhất" giữa hai
134
+ * số cách đều vốn đã là một phép đoán), viết literal (audit chặn), hoặc mở
135
+ * issue ngược lên rồi CHỜ. Đó là lý do mọi dự án đều lệch design — không
136
+ * phải người viết cẩu thả.
137
+ *
138
+ * ## Cái giá của nó
139
+ *
140
+ * Nó phát ra `data-gap-raw` lên DOM. Đó không phải trang trí: nó làm mỗi lần
141
+ * thoát trở nên ĐẾM ĐƯỢC — grep mã nguồn hoặc quét DOM đều ra, nên một kho
142
+ * đang trôi dần khỏi thang sẽ tự lộ ra bằng con số thay vì bằng cảm giác.
143
+ *
144
+ * ## Khi nào ĐỪNG dùng
145
+ *
146
+ * Khi giá trị bạn cần có trong thang. `gap={3}` là 12px và nó đọc theo
147
+ * `--scaling` của người dùng; `gapRaw={12}` thì không, nó đứng yên khi người
148
+ * ta phóng to giao diện. Thang có mười bậc — hãy tiêu hết chúng trước.
149
+ *
150
+ * Khi có mặt, nó THẮNG `gap`, và `gap` không phát lớp nào nữa để hai bên
151
+ * không tranh độ đặc hiệu.
152
+ */
153
+ gapRaw?: number;
154
+ /**
155
+ * Đệm TRONG, theo thang token. Nhận một bậc cho cả bốn cạnh, hoặc một object
156
+ * theo cạnh LOGIC (`inline`, `block`, `inlineStart`, `blockEnd`…).
157
+ *
158
+ * Vì sao có: chạy `ui-audit` trên consumer thật (godx-chat, 08/09/2026) ra
159
+ * **42 trong 51 lỗi** là `no-utility-spacing`, và gần như tất cả xin cùng
160
+ * một thứ — padding. `<Flex className="p-3">` không phải cẩu thả; đó là nước
161
+ * đi duy nhất còn lại khi primitive không có prop đệm. Một prop thiếu đẻ ra
162
+ * 42 lỗi (gh#408).
163
+ */
164
+ pad?: PadProp;
165
+ /**
166
+ * Đệm bằng pixel THÔ, cho giá trị ngoài thang — cùng lý do và cùng cái giá
167
+ * với `gapRaw`: nó để lại `data-pad-raw` trên DOM nên mỗi lần thoát đều đếm
168
+ * được. Đo trên 51 lỗi ấy: thiết kế cần 2px, 6px, 10px, 14px, 44px, không
169
+ * bậc nào có.
170
+ *
171
+ * Ghi đè `pad` ở TỪNG CẠNH, không phải cả cụm.
172
+ */
173
+ padRaw?: PadRawProp;
115
174
  align?: FlexAlignProp;
116
175
  justify?: FlexJustifyProp;
117
176
  wrap?: boolean;
@@ -126,6 +185,31 @@ export type FlexProp = React.HTMLAttributes<HTMLDivElement> & {
126
185
  * only on the narrow side (a compact-only affordance). Omit for no attribute and no rule.
127
186
  */
128
187
  hideFrom?: BreakpointProp;
188
+ /**
189
+ * Take the space the siblings leave — the Flex becomes the row's ELASTIC column.
190
+ *
191
+ * ## Vì sao là một trục, không phải một tiện ích
192
+ *
193
+ * Một hàng thật gần như luôn có hình `cố định | co giãn | cố định`: tên bên trái, thước đo ở
194
+ * giữa, con số bên phải. Không có trục này thì nước đi duy nhất là `className="flex-1 min-w-0"`
195
+ * — mà `ui-audit` chặn `no-utility-spacing`, nên cách làm ĐÚNG lại là cách bất hợp pháp.
196
+ * `PageContainer` đã có `fill` với đúng nghĩa ấy; `Flex` không có là bất đối xứng, không phải
197
+ * quyết định (gh#405 §2).
198
+ *
199
+ * Nó kèm luôn `min-inline-size: 0`. Đó không phải chi tiết thừa: một flex item mặc định không
200
+ * co nhỏ hơn nội dung, nên một `Text truncate` bên trong sẽ ĐẨY hàng rộng ra thay vì cắt bớt.
201
+ */
202
+ fill?: boolean;
203
+ /**
204
+ * Bề rộng CỐ ĐỊNH của một cột trong hàng — số là px, chuỗi là mọi CSS length (`"12rem"`, `"40%"`).
205
+ *
206
+ * Đi kèm `flex: none`. Một `inline-size` mà sibling vẫn bóp được thì không phải cột, nó chỉ là
207
+ * một đề nghị — và sáu thanh xếp dọc dưới nhau sẽ bắt đầu ở sáu toạ độ x khác nhau.
208
+ *
209
+ * Nó để lại `data-width-raw` trên DOM, cùng lý do với `gapRaw`/`padRaw`: mỗi số đo cứng viết ở
210
+ * call site đều ĐẾM ĐƯỢC, nên một kho đang trôi khỏi thang tự lộ ra bằng con số.
211
+ */
212
+ width?: WidthProp;
129
213
  };
130
214
  /** Container column counts; omitted steps inherit from the previous step. Base defaults to 1. */
131
215
  export type ResponsiveGridColumnsProp = number | {
@@ -139,8 +223,9 @@ export type ResponsiveGridColumnsProp = number | {
139
223
  * `pricing-plans` — the canonical billing/pricing-plan collection: 1 column until the `lg` step
140
224
  * (container ≥ 64rem), then 3 columns from `lg` upward.
141
225
  */
226
+ export type ResponsiveGridFlowProp = "rows" | "columns";
142
227
  export type ResponsiveGridPresetProp = "pricing-plans";
143
- export type MasterDetailRailWidthProp = "compact" | "standard";
228
+ export type MasterDetailRailWidthProp = "narrow" | "compact" | "standard" | "wide";
144
229
  export type MasterDetailRailProp = "master" | "detail";
145
230
  /**
146
231
  * Bounded viewport preset for the master collection. `auto` (default) never bounds it — the region
@@ -188,7 +273,8 @@ export type PageInsetProp = React.HTMLAttributes<HTMLDivElement> & {
188
273
  };
189
274
  /** @see AppShell */
190
275
  export type AppShellProp = {
191
- sidebar: ReactNode;
276
+ /** Omit or pass null/false for a shell without the sidebar landmark or grid track. */
277
+ sidebar?: ReactNode;
192
278
  children: ReactNode;
193
279
  topbar?: ReactNode;
194
280
  topbarLeft?: ReactNode;
@@ -213,11 +299,50 @@ export type AppShellProp = {
213
299
  * belonging to it.
214
300
  */
215
301
  topbarSpan?: "content" | "full";
302
+ /**
303
+ * A SECOND navigation column, narrower than `sidebar` and placed before it — the
304
+ * workspace/organization switcher shape (Slack, Teams, Discord). Passing a node adds the track;
305
+ * omitting it leaves the two-column shell exactly as it was. Width is
306
+ * `--app-shell-nav-rail-width` (3.5rem — deliberately NOT the collapsed sidebar's 4rem: at equal
307
+ * widths the two navigation tracks fuse into one block the moment the sidebar collapses).
308
+ *
309
+ * THE THREE COLUMNS ARE THREE SCOPES, and the scope — not the free space — is what decides where
310
+ * a control goes. The rail is PLATFORM scope: whatever is true across every app in the
311
+ * organization (which organization, which app, notifications, messages, events, organization
312
+ * settings, cross-app shortcuts). The sidebar is APP scope: this app's own sections. The topbar
313
+ * is PAGE scope: where you are and what you can do here.
314
+ *
315
+ * So app navigation never goes in the rail, a platform switch never goes in the sidebar, and a
316
+ * destination that would fit both belongs to the rail — because it survives changing apps. A
317
+ * rail that repeats the sidebar's own entries is a second chrome band carrying the first one's
318
+ * rank, just vertical instead of horizontal.
319
+ *
320
+ * Orthogonal to `topbarSpan`: the rail says how many navigation COLUMNS there are, `topbarSpan`
321
+ * says how far the BAR reaches, and every combination of the two is a real shape, so they never
322
+ * need to be reconciled. `sidebarCollapsed` folds the sidebar track only — the rail keeps its
323
+ * width, which is what keeps its destinations reachable while collapsed.
324
+ *
325
+ * Building this by hand inside the single `sidebar` slot is the trap it replaces: `Sidebar`
326
+ * renders `.sb-root { display: contents }`, so two of them dropped side by side dissolve into
327
+ * one flex row and both collapse to zero unless each is separately wrapped — and sizing the one
328
+ * available track for two columns means overriding `--app-shell-sidebar-width`, which is how a
329
+ * shipped consumer moved its content edge by 64px between routes.
330
+ */
331
+ navRail?: ReactNode;
332
+ /**
333
+ * Accessible name for the `navRail` landmark. Defaults to the localized "Workspaces".
334
+ *
335
+ * The rail and the sidebar are two `complementary` landmarks on one page, so ARIA requires them
336
+ * to be tellable apart by name; the shell always supplies both defaults rather than requiring
337
+ * this prop, so the two columns of equal rank behave the same way.
338
+ */
339
+ navRailLabel?: string;
216
340
  /**
217
341
  * Navigation shown in the mobile drawer at the DXS 900px breakpoint, where the docked sidebar is
218
- * hidden. Defaults to `sidebar`, so the same nav is available on mobile with no extra wiring;
219
- * pass a distinct node for a mobile-tailored menu, or `null` to opt out (only when navigation
220
- * lives elsewhere, e.g. a bottom bar).
342
+ * hidden. Defaults to `navRail` followed by `sidebar` both docked columns are hidden at that
343
+ * width, so a default of `sidebar` alone would silently strip every app-level destination the
344
+ * rail carries. Pass a distinct node for a mobile-tailored menu, or `null` to opt out (only when
345
+ * navigation lives elsewhere, e.g. a bottom bar).
221
346
  */
222
347
  mobileNav?: ReactNode;
223
348
  /** Accessible title for the mobile navigation drawer. Defaults to the localized "Menu". */
@@ -354,6 +479,10 @@ export type MobileShellProp = {
354
479
  * so a service retunes a day divider or an unread watermark from its theme and never forks CSS.
355
480
  */
356
481
  export type SeparatorProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
482
+ labelSize?: "2xs" | "xs" | "sm" | "md";
483
+ space?: GapProp;
484
+ hideBelow?: BreakpointProp;
485
+ hideFrom?: BreakpointProp;
357
486
  /** Rule axis. Default `"horizontal"`. */
358
487
  orientation?: OrientationProp;
359
488
  /**
@@ -756,12 +885,132 @@ export type OrgSwitcherProp = {
756
885
  error?: ReactNode;
757
886
  onRetry?: () => void;
758
887
  labels: OrgSwitcherLabels;
888
+ /**
889
+ * WHICH SURFACE the panel opens on.
890
+ *
891
+ * - `"auto"` (default) — popover above `--sheet-responsive-breakpoint-width`, focus-trapped
892
+ * bottom Sheet at or below it.
893
+ * - `"dialog"` — a centred modal above that breakpoint, the same bottom Sheet below it. Reach for
894
+ * this once the panel carries more than a name per row — a role, a plan, a member count, a
895
+ * "create organization" action. A popover is anchored to its trigger, clipped by the viewport
896
+ * and sized by `--org-switcher-menu-width`; a dialog has a real title, a scrolling body and a
897
+ * footer, and takes the reader's full attention, which is the right trade when switching
898
+ * organization re-scopes everything on screen.
899
+ * - `"popover"` / `"sheet"` — pinned to one surface at every width. Useful for a deterministic
900
+ * embedded surface or a component test, rarely in a product.
901
+ *
902
+ * `auto` and `dialog` are the two RESPONSIVE pairs and differ only in their desktop half; the
903
+ * mobile half is the same Sheet, because a centred modal on a phone is a Sheet with worse
904
+ * ergonomics. All four resolve the breakpoint through the shared `useSheetResponsiveMode()`
905
+ * hook, so a service moves the line once for every overlay.
906
+ */
907
+ responsive?: "auto" | "popover" | "sheet" | "dialog";
908
+ open?: boolean;
909
+ onOpenChange?: (open: boolean) => void;
910
+ className?: ClassNameProp;
911
+ };
912
+ /**
913
+ * One app tile in the public {@link AppLauncher} contract.
914
+ *
915
+ * A tile is a REAL LINK, always. There is no `onClick`-only entry and no `disabled` entry: an app
916
+ * the viewer may not open is an app the consumer does not pass. A launcher that renders inert tiles
917
+ * is a launcher that teaches its users to stop trusting the grid.
918
+ */
919
+ export type AppLauncherApp = {
920
+ /** Stable app identifier — the React key, and the `data-app` hook an end-to-end test holds. */
921
+ id: string;
922
+ /** Visible app name. It is also the tile's accessible name — the mark is presentational. */
923
+ name: string;
924
+ /** The destination. Required, because the tile IS an `<a href>` (WCAG 2.2 / APG: link, not button). */
925
+ href: string;
926
+ /**
927
+ * Owned mark — a Lucide icon, a `<Logo>`, an `<img>`, an `<Avatar>`. Rendered `aria-hidden`;
928
+ * when omitted the launcher falls back to the first character of `name`, exactly as
929
+ * `OrgSwitcher` does for an organization with no avatar.
930
+ */
931
+ icon?: ReactNode;
932
+ /** The app the viewer is currently inside. That tile carries `aria-current="page"`. */
933
+ current?: boolean;
934
+ /**
935
+ * The destination leaves this SPA. Such a tile renders a plain `<a target="_blank" rel="noreferrer
936
+ * noopener">` and DELIBERATELY bypasses `linkComponent`: a client-side router link to another
937
+ * origin is a router asked to route somewhere it does not own, which is how a launcher entry
938
+ * turns into a blank screen. Pair it with `labels.externalHint` (WCAG 3.2.5).
939
+ */
940
+ external?: boolean;
941
+ };
942
+ /** A labelled section of the launcher grid — the "more from …" band of the Google-style panel. */
943
+ export type AppLauncherGroup = {
944
+ /** Section heading. Rendered as a real heading and used to name the section's own grid. */
945
+ label: string;
946
+ apps: readonly AppLauncherApp[];
947
+ };
948
+ /** Localized copy owned by the consuming product, never hard-coded by the component. */
949
+ export type AppLauncherLabels = {
950
+ /**
951
+ * Accessible name of the nine-dot trigger ("Apps" / "アプリ"). A plain string, NOT a function of
952
+ * the current app the way `OrgSwitcherLabels.trigger` is a function of the organization: the
953
+ * launcher's trigger shows no current value, so naming one in the trigger would announce a
954
+ * destination the button does not go to.
955
+ */
956
+ trigger: string;
957
+ /** Panel name — the popover's accessible name and the bottom Sheet's header title. */
958
+ title: string;
959
+ empty: string;
960
+ loading: string;
961
+ retry?: string;
962
+ /** Screen-reader suffix for an `external` tile, e.g. "(opens in a new tab)" (WCAG 3.2.5). */
963
+ externalHint?: string;
964
+ };
965
+ /**
966
+ * @see AppLauncher — the PLATFORM-scope app switcher that lives in the topbar.
967
+ *
968
+ * Related, and repeatedly confused with it:
969
+ *
970
+ * - `ServiceLauncherCard` (data-display) is also a launcher tile, but a PAGE-SIZED one: status,
971
+ * hostname, plan, an action button, a reason it is locked. It belongs on a service-catalogue
972
+ * page, where choosing is a considered act. `AppLauncher`'s tile is bar-sized — mark plus name,
973
+ * the whole tile a link — because switching app is a reflex, not a decision. Neither is built
974
+ * out of the other; a grid of `ServiceLauncherCard`s inside a popover is the wrong component.
975
+ * - `AppShellProp.navRail` expresses the SAME platform scope as a docked column. These are the two
976
+ * ways to say it, and a product picks ONE: the launcher suits a platform with MANY apps where
977
+ * switching is occasional (the Google Workspace shape), the rail suits a single product where
978
+ * switching workspace is a constant action worth permanent screen width (the Slack shape).
979
+ * Shipping both puts one scope in two places and makes neither authoritative.
980
+ */
981
+ export type AppLauncherProp = {
982
+ /** Ungrouped apps, rendered first, with no heading above them. */
983
+ apps: readonly AppLauncherApp[];
984
+ /** Labelled sections rendered after `apps`, in order. */
985
+ groups?: readonly AppLauncherGroup[];
986
+ labels: AppLauncherLabels;
987
+ /**
988
+ * Grid column count. Omit it and the panel keeps the stylesheet's own `--app-launcher-columns`
989
+ * (3, the Google-launcher shape, declared on `.ui-app-launcher-panel`): the default is where a
990
+ * theme can reach it, and this prop is the per-instance override written inline on top.
991
+ */
992
+ columns?: number;
993
+ /**
994
+ * THE framework-router contract, reusing `SidebarLinkComponentProp` VERBATIM — the same type
995
+ * `Sidebar` and `NavList` take, so a service that already wrote `inertiaSidebarLink(Link)` or
996
+ * `createSidebarLink(Link, "to")` for its rail hands the same value here. The launcher still
997
+ * composes the tile (mark, name, `aria-current`, the external hint); the consumer supplies only
998
+ * the element type. `external` apps bypass it — see {@link AppLauncherApp.external}.
999
+ */
1000
+ linkComponent?: SidebarLinkComponentProp;
1001
+ loading?: boolean;
1002
+ /** Error content replaces the grid while preserving the trigger and the retry affordance. */
1003
+ error?: ReactNode;
1004
+ onRetry?: () => void;
759
1005
  /**
760
1006
  * `"auto"` (default) uses the desktop popover above `--sheet-responsive-breakpoint-width` and a
761
1007
  * focus-trapped bottom Sheet at/below it — the SAME token that drives `SheetContent
762
1008
  * responsive="auto"`, resolved through the shared `useSheetResponsiveMode()` hook, so a service
763
1009
  * moves the drawer line once for every overlay instead of per component.
764
- * Explicit modes are useful for deterministic embedded surfaces and component tests.
1010
+ *
1011
+ * No `"dialog"` here, unlike `OrgSwitcher`: a launcher grid is a jump table, and a modal that
1012
+ * takes over the screen to offer nine links is heavier than the errand. Switching ORGANIZATION
1013
+ * re-scopes everything on screen and earns the interruption; opening an app does not.
765
1014
  */
766
1015
  responsive?: "auto" | "popover" | "sheet";
767
1016
  open?: boolean;
@@ -826,6 +1075,8 @@ export type SidebarProp = {
826
1075
  * configured via THEIR own props and dropped into a slot.
827
1076
  */
828
1077
  export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
1078
+ height?: "bar" | "auto";
1079
+ pad?: PadProp;
829
1080
  /** Inline-start cluster — typically the sidebar toggle + a brand mark (`Avatar`) + primary nav. */
830
1081
  start?: ReactNode;
831
1082
  /** Center cluster — optional (e.g. a search trigger or a page/entity switcher). */
@@ -842,7 +1093,7 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
842
1093
  * It exists because the alternative is a `Button`, and a Button in a bar is a control that landed
843
1094
  * in the bar rather than a part of it — a pill of its own height floating in a taller strip, with
844
1095
  * its own hover surface and its own focus ring drawn around that pill. Fluent, SLDS, Atlassian and
845
- * antd's ProLayout all draw a top-bar trigger the other way: the cell is as tall as the bar, its
1096
+ * and enterprise pro-layouts all draw a top-bar trigger the other way: the cell is as tall as the bar, its
846
1097
  * hover is the bar's own surface, and the focus mark is hosted INSIDE the cell because a
847
1098
  * full-bleed cell has nothing outside itself to ring.
848
1099
  */
@@ -851,6 +1102,25 @@ export type TopbarItemProp = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>,
851
1102
  * Render the bar-cell shape ONTO the child instead of emitting a `<button>` — for a router link,
852
1103
  * or for a menu/popover trigger that needs to own the element itself.
853
1104
  */
1105
+ /** Hide below a shared responsive breakpoint, without changing cell height. */
1106
+ hideBelow?: BreakpointProp;
1107
+ /**
1108
+ * Unread count OVERLAID on the cell's glyph — the notification-bell affordance the cell's own use
1109
+ * cases name (gh#398). Pass the CONTENT ONLY, exactly like `SidebarItemProp.badge`: a number, a
1110
+ * string, `"99+"`. Position, size and colour come from `--topbar-item-badge-*`, so the count does
1111
+ * NOT widen the cell (an inline chip pushes the end cluster's shrink budget around every time the
1112
+ * count crosses a digit boundary) and a theme can retune the overlay once.
1113
+ *
1114
+ * The count is not an accessible name: give the cell an `aria-label` that says what the number
1115
+ * means (`aria-label={t("topbar.notifications.unread", { count })}`).
1116
+ */
1117
+ badge?: ReactNode;
1118
+ /**
1119
+ * Emphasis of `badge`. Vocabulary shared VERBATIM with `SidebarItemProp.badgeTone` so one count
1120
+ * pill reads the same in the rail and in the bar: `destructive` when the count is addressed to
1121
+ * the user rather than merely unread.
1122
+ */
1123
+ badgeTone?: SidebarBadgeToneProp;
854
1124
  asChild?: boolean;
855
1125
  children?: ReactNode;
856
1126
  className?: ClassNameProp;