@godxjp/ui 19.1.0 → 19.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 (436) hide show
  1. package/README.md +25 -128
  2. package/dist/app/theme-axes.d.ts +4 -15
  3. package/dist/app/timezones.d.ts +1 -4
  4. package/dist/components/charts/area-chart.d.ts +2 -3
  5. package/dist/components/charts/bar-chart.d.ts +2 -3
  6. package/dist/components/charts/chart-frame.d.ts +7 -16
  7. package/dist/components/charts/chart-summary.d.ts +2 -2
  8. package/dist/components/charts/compact-bar-trend.d.ts +9 -24
  9. package/dist/components/charts/index.d.ts +1 -6
  10. package/dist/components/data-display/badge.d.ts +2 -12
  11. package/dist/components/data-display/card.d.ts +21 -41
  12. package/dist/components/data-display/carousel.d.ts +1 -2
  13. package/dist/components/data-display/code-block.d.ts +4 -0
  14. package/dist/components/data-display/code-block.js +24 -0
  15. package/dist/components/data-display/data-table.d.ts +15 -37
  16. package/dist/components/data-display/data-table.js +1 -3
  17. package/dist/components/data-display/descriptions.d.ts +2 -6
  18. package/dist/components/data-display/descriptions.js +0 -2
  19. package/dist/components/data-display/index.d.ts +4 -0
  20. package/dist/components/data-display/index.js +4 -0
  21. package/dist/components/data-display/list-row.d.ts +3 -22
  22. package/dist/components/data-display/permission-matrix.d.ts +7 -20
  23. package/dist/components/data-display/progress.d.ts +1 -1
  24. package/dist/components/data-display/prose.d.ts +4 -0
  25. package/dist/components/data-display/prose.js +20 -0
  26. package/dist/components/data-display/service-launcher-card.d.ts +3 -10
  27. package/dist/components/data-display/table.d.ts +20 -80
  28. package/dist/components/data-display/table.js +0 -3
  29. package/dist/components/data-display/timeline.d.ts +3 -5
  30. package/dist/components/data-entry/branch-scope-picker.d.ts +2 -19
  31. package/dist/components/data-entry/branch-scope-picker.js +0 -1
  32. package/dist/components/data-entry/calendar.d.ts +3 -2
  33. package/dist/components/data-entry/calendar.js +58 -3
  34. package/dist/components/data-entry/command-palette.d.ts +8 -22
  35. package/dist/components/data-entry/date-picker.d.ts +4 -5
  36. package/dist/components/data-entry/date-picker.js +6 -1
  37. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  38. package/dist/components/data-entry/date-range-picker.js +8 -3
  39. package/dist/components/data-entry/form-errors.d.ts +7 -18
  40. package/dist/components/data-entry/form-field.js +1 -5
  41. package/dist/components/data-entry/form.d.ts +1 -9
  42. package/dist/components/data-entry/input.d.ts +16 -14
  43. package/dist/components/data-entry/input.js +3 -0
  44. package/dist/components/data-entry/month-picker.js +2 -2
  45. package/dist/components/data-entry/month-range-picker.d.ts +4 -6
  46. package/dist/components/data-entry/month-range-picker.js +2 -2
  47. package/dist/components/data-entry/number-input.d.ts +1 -5
  48. package/dist/components/data-entry/number-input.js +1 -0
  49. package/dist/components/data-entry/radio.js +0 -1
  50. package/dist/components/data-entry/search-select.d.ts +2 -4
  51. package/dist/components/data-entry/select.d.ts +11 -6
  52. package/dist/components/data-entry/select.js +4 -2
  53. package/dist/components/data-entry/switch.js +0 -1
  54. package/dist/components/data-entry/textarea.d.ts +10 -16
  55. package/dist/components/data-entry/time-picker.d.ts +3 -5
  56. package/dist/components/data-entry/upload-types.d.ts +1 -5
  57. package/dist/components/data-entry/use-upload-draft.d.ts +0 -4
  58. package/dist/components/feedback/dialog.d.ts +6 -15
  59. package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
  60. package/dist/components/feedback/sheet.d.ts +3 -10
  61. package/dist/components/feedback/sheet.js +1 -1
  62. package/dist/components/feedback/sonner.js +2 -3
  63. package/dist/components/general/activity.d.ts +4 -30
  64. package/dist/components/general/button.js +1 -10
  65. package/dist/components/general/logo.d.ts +6 -28
  66. package/dist/components/general/reveal.d.ts +3 -9
  67. package/dist/components/general/typography.d.ts +1 -3
  68. package/dist/components/layout/account-chip.d.ts +8 -0
  69. package/dist/components/layout/account-chip.js +45 -0
  70. package/dist/components/layout/auth-divider.d.ts +2 -9
  71. package/dist/components/layout/auth-shell.d.ts +1 -14
  72. package/dist/components/layout/breadcrumb.d.ts +1 -5
  73. package/dist/components/layout/centered-shell.d.ts +4 -18
  74. package/dist/components/layout/error-surface.d.ts +11 -28
  75. package/dist/components/layout/error-surface.js +4 -1
  76. package/dist/components/layout/index.d.ts +2 -0
  77. package/dist/components/layout/index.js +2 -0
  78. package/dist/components/layout/legal-document-shell.d.ts +5 -28
  79. package/dist/components/layout/master-detail.d.ts +2 -18
  80. package/dist/components/layout/page-container.js +0 -1
  81. package/dist/components/layout/responsive-grid.d.ts +6 -1
  82. package/dist/components/layout/responsive-grid.js +9 -2
  83. package/dist/components/layout/service-role-panel.d.ts +7 -19
  84. package/dist/components/layout/sidebar.d.ts +5 -27
  85. package/dist/components/layout/sidebar.js +0 -1
  86. package/dist/components/layout/split-pane.d.ts +0 -7
  87. package/dist/components/layout/topbar.d.ts +1 -14
  88. package/dist/components/navigation/app-setting-picker.js +1 -3
  89. package/dist/components/navigation/filter-bar.d.ts +1 -6
  90. package/dist/components/navigation/pagination-utils.d.ts +2 -12
  91. package/dist/components/navigation/tabs-scroll.d.ts +2 -36
  92. package/dist/components/navigation/tabs.js +4 -8
  93. package/dist/components/query/data-state.d.ts +2 -2
  94. package/dist/components/ui/credential-reveal.d.ts +1 -4
  95. package/dist/components/ui/hover-card.d.ts +1 -2
  96. package/dist/components/ui/separator.d.ts +2 -15
  97. package/dist/components/ui/toggle-group.d.ts +1 -5
  98. package/dist/components/ui/toggle.d.ts +6 -31
  99. package/dist/email/brand-mark.d.ts +6 -34
  100. package/dist/email/color.d.ts +7 -17
  101. package/dist/email/geometry.d.ts +4 -8
  102. package/dist/email/index.d.ts +4 -48
  103. package/dist/email/inline-style.d.ts +3 -9
  104. package/dist/email/urgency.d.ts +2 -7
  105. package/dist/form/form-context.d.ts +2 -5
  106. package/dist/form/form-root.d.ts +1 -2
  107. package/dist/i18n/messages/en.json +4 -0
  108. package/dist/i18n/messages/ja.json +4 -0
  109. package/dist/i18n/messages/vi.json +4 -0
  110. package/dist/inertia/index.d.ts +2 -10
  111. package/dist/lib/control-styles.d.ts +10 -21
  112. package/dist/lib/control-styles.js +4 -6
  113. package/dist/lib/datetime/format-date.d.ts +1 -4
  114. package/dist/lib/field-a11y.d.ts +11 -68
  115. package/dist/lib/field-a11y.js +0 -1
  116. package/dist/lib/format.d.ts +4 -8
  117. package/dist/lib/hooks.d.ts +6 -12
  118. package/dist/lib/permission-grid.d.ts +3 -18
  119. package/dist/lib/query-error.d.ts +3 -3
  120. package/dist/props/components/app.prop.d.ts +15 -38
  121. package/dist/props/components/charts.prop.d.ts +2 -3
  122. package/dist/props/components/data-display.prop.d.ts +48 -72
  123. package/dist/props/components/data-entry.prop.d.ts +35 -40
  124. package/dist/props/components/form.prop.d.ts +2 -6
  125. package/dist/props/components/general.prop.d.ts +11 -27
  126. package/dist/props/components/layout.prop.d.ts +89 -260
  127. package/dist/props/components/navigation.prop.d.ts +27 -42
  128. package/dist/props/registry.d.ts +31 -0
  129. package/dist/props/registry.js +33 -0
  130. package/dist/props/vocabulary/data.prop.d.ts +18 -49
  131. package/dist/props/vocabulary/index.d.ts +1 -1
  132. package/dist/props/vocabulary/interaction.prop.d.ts +10 -12
  133. package/dist/props/vocabulary/layout.prop.d.ts +8 -30
  134. package/dist/props/vocabulary/shared.prop.d.ts +3 -2
  135. package/dist/styles/alert-layout.css +1 -32
  136. package/dist/styles/badge-layout.css +4 -18
  137. package/dist/styles/base.css +14 -113
  138. package/dist/styles/card-layout.css +6 -128
  139. package/dist/styles/chart-layout.css +2 -17
  140. package/dist/styles/control.css +380 -458
  141. package/dist/styles/core.css +21 -0
  142. package/dist/styles/data-display-layout.css +175 -94
  143. package/dist/styles/data-entry-layout.css +0 -45
  144. package/dist/styles/density.css +1 -30
  145. package/dist/styles/dialog-layout.css +5 -69
  146. package/dist/styles/focus-ring.css +13 -87
  147. package/dist/styles/fonts.css +1 -31
  148. package/dist/styles/form-layout.css +7 -91
  149. package/dist/styles/index.css +2 -20
  150. package/dist/styles/layout.css +38 -456
  151. package/dist/styles/logo-layout.css +1 -35
  152. package/dist/styles/motion.css +3 -42
  153. package/dist/styles/navigation-layout.css +3 -50
  154. package/dist/styles/shell-layout.css +37 -403
  155. package/dist/styles/table-layout.css +16 -274
  156. package/dist/styles/text-layout.css +7 -21
  157. package/dist/styles/toggle.css +1 -23
  158. package/dist/theme/dxs.canonical.css +1 -55
  159. package/dist/theme/example.service.css +1 -26
  160. package/dist/theme/famgia.service.css +5 -15
  161. package/dist/tokens/axes.css +3 -40
  162. package/dist/tokens/base.css +1 -4
  163. package/dist/tokens/components/activity.css +6 -42
  164. package/dist/tokens/components/badge.css +4 -13
  165. package/dist/tokens/components/banner.css +4 -9
  166. package/dist/tokens/components/card.css +32 -88
  167. package/dist/tokens/components/chart.css +9 -26
  168. package/dist/tokens/components/control.css +37 -222
  169. package/dist/tokens/components/data-display.css +28 -79
  170. package/dist/tokens/components/data-entry.css +5 -19
  171. package/dist/tokens/components/descriptions.css +7 -27
  172. package/dist/tokens/components/email.css +38 -84
  173. package/dist/tokens/components/error-surface.css +2 -18
  174. package/dist/tokens/components/feedback.css +27 -124
  175. package/dist/tokens/components/form.css +13 -45
  176. package/dist/tokens/components/legal-document.css +16 -41
  177. package/dist/tokens/components/list-row.css +11 -36
  178. package/dist/tokens/components/logo.css +2 -57
  179. package/dist/tokens/components/navigation.css +16 -94
  180. package/dist/tokens/components/scroll-area.css +2 -18
  181. package/dist/tokens/components/separator.css +8 -30
  182. package/dist/tokens/components/sheet.css +3 -14
  183. package/dist/tokens/components/shell.css +53 -307
  184. package/dist/tokens/components/sidebar.css +5 -22
  185. package/dist/tokens/components/table.css +19 -140
  186. package/dist/tokens/components/toggle.css +3 -65
  187. package/dist/tokens/components/upload.css +7 -43
  188. package/dist/tokens/foundation.css +108 -383
  189. package/dist/tokens/semantic/layout.css +33 -207
  190. package/docs/CANONICAL-CONTRACTS.md +206 -0
  191. package/docs/COMPONENTS.md +157 -0
  192. package/docs/COMPOSITION-VS-COMPONENT.md +105 -0
  193. package/docs/CONSUMER-RULES.md +14 -0
  194. package/docs/CUSTOMER-THEMING.md +162 -0
  195. package/docs/DATETIME.md +69 -0
  196. package/docs/DEVELOPMENT.md +173 -0
  197. package/docs/FORMS.md +143 -0
  198. package/docs/FRAME-A11Y-CI.md +267 -0
  199. package/docs/FRAME-COVERAGE-LEDGER.md +123 -0
  200. package/docs/FRAME-COVERAGE-REPORT.md +140 -0
  201. package/docs/FRAME-COVERAGE-STANDARD.md +65 -0
  202. package/docs/FRAME-COVERAGE.md +29 -0
  203. package/docs/PROPS-REGISTRY.md +61 -0
  204. package/docs/PROPS-VOCABULARY.md +100 -0
  205. package/docs/README.md +60 -0
  206. package/docs/REGISTRY.md +70 -0
  207. package/docs/SPACING.md +56 -0
  208. package/docs/STANDARDS-vocabulary-tokens.md +48 -0
  209. package/docs/TESTING.md +98 -0
  210. package/docs/TOKENS.md +347 -0
  211. package/docs/asset-modules.d.ts +46 -0
  212. package/docs/assets/cover-terrain.svg +6 -0
  213. package/docs/assets/portrait-amber.svg +5 -0
  214. package/docs/assets/portrait-clay.svg +5 -0
  215. package/docs/assets/portrait-indigo.svg +5 -0
  216. package/docs/assets/portrait-iris.svg +5 -0
  217. package/docs/assets/portrait-moss.svg +5 -0
  218. package/docs/assets/portrait-plum.svg +5 -0
  219. package/docs/assets/portrait-slate.svg +5 -0
  220. package/docs/charts/compact-bar-trend.tsx +328 -0
  221. package/docs/charts/size-tiers.tsx +192 -0
  222. package/docs/data-display/accordion.tsx +99 -0
  223. package/docs/data-display/avatar.tsx +355 -0
  224. package/docs/data-display/badge.tsx +247 -0
  225. package/docs/data-display/card/examples/detail-panel.tsx +106 -0
  226. package/docs/data-display/card/index.md +3 -0
  227. package/docs/data-display/card/index.tsx +285 -0
  228. package/docs/data-display/carousel.tsx +156 -0
  229. package/docs/data-display/charts.tsx +198 -0
  230. package/docs/data-display/code-block.tsx +90 -0
  231. package/docs/data-display/collapsible.tsx +162 -0
  232. package/docs/data-display/credential-reveal.tsx +145 -0
  233. package/docs/data-display/data-table/examples/approval-queue.tsx +212 -0
  234. package/docs/data-display/data-table/examples/grid-features.tsx +158 -0
  235. package/docs/data-display/data-table/examples/list-page.tsx +175 -0
  236. package/docs/data-display/data-table/index.md +5 -0
  237. package/docs/data-display/data-table/index.tsx +295 -0
  238. package/docs/data-display/descriptions.tsx +143 -0
  239. package/docs/data-display/empty-state.tsx +164 -0
  240. package/docs/data-display/hover-card.tsx +181 -0
  241. package/docs/data-display/list-row.tsx +420 -0
  242. package/docs/data-display/permission-matrix.tsx +184 -0
  243. package/docs/data-display/popover-touch.tsx +27 -0
  244. package/docs/data-display/popover.tsx +245 -0
  245. package/docs/data-display/progress.tsx +84 -0
  246. package/docs/data-display/prose.tsx +105 -0
  247. package/docs/data-display/qr-code.tsx +68 -0
  248. package/docs/data-display/scroll-area.tsx +271 -0
  249. package/docs/data-display/service-launcher-card.tsx +145 -0
  250. package/docs/data-display/stat-card.tsx +99 -0
  251. package/docs/data-display/table.tsx +251 -0
  252. package/docs/data-display/timeline.tsx +112 -0
  253. package/docs/data-display/touch-actions.tsx +36 -0
  254. package/docs/data-display/tree-list.tsx +107 -0
  255. package/docs/data-entry/branch-scope-picker.tsx +123 -0
  256. package/docs/data-entry/calendar.tsx +180 -0
  257. package/docs/data-entry/cascader.tsx +378 -0
  258. package/docs/data-entry/checkbox.tsx +256 -0
  259. package/docs/data-entry/color-picker.tsx +117 -0
  260. package/docs/data-entry/command-palette.tsx +179 -0
  261. package/docs/data-entry/command.tsx +262 -0
  262. package/docs/data-entry/country-picker-recipe.tsx +170 -0
  263. package/docs/data-entry/date-picker.tsx +191 -0
  264. package/docs/data-entry/date-range-picker.tsx +149 -0
  265. package/docs/data-entry/form/examples/employee-settings.tsx +267 -0
  266. package/docs/data-entry/form/examples/invoice-form.tsx +387 -0
  267. package/docs/data-entry/form-field/examples/a11y-contract.tsx +240 -0
  268. package/docs/data-entry/form-field/examples/create-form.tsx +105 -0
  269. package/docs/data-entry/form-field/index.md +8 -0
  270. package/docs/data-entry/form-field/index.tsx +326 -0
  271. package/docs/data-entry/form-inertia.tsx +80 -0
  272. package/docs/data-entry/form-root.tsx +102 -0
  273. package/docs/data-entry/form.tsx +724 -0
  274. package/docs/data-entry/input-otp.tsx +231 -0
  275. package/docs/data-entry/input.tsx +114 -0
  276. package/docs/data-entry/label.tsx +100 -0
  277. package/docs/data-entry/month-picker.tsx +101 -0
  278. package/docs/data-entry/month-range-picker.tsx +115 -0
  279. package/docs/data-entry/number-input.tsx +240 -0
  280. package/docs/data-entry/password-input.tsx +106 -0
  281. package/docs/data-entry/password-strength.tsx +97 -0
  282. package/docs/data-entry/radio-group.tsx +95 -0
  283. package/docs/data-entry/rating.tsx +87 -0
  284. package/docs/data-entry/search-input.tsx +120 -0
  285. package/docs/data-entry/select-async-contracts.tsx +108 -0
  286. package/docs/data-entry/select-matrix.tsx +204 -0
  287. package/docs/data-entry/select.tsx +369 -0
  288. package/docs/data-entry/slider.tsx +130 -0
  289. package/docs/data-entry/switch.tsx +67 -0
  290. package/docs/data-entry/tag-input.tsx +108 -0
  291. package/docs/data-entry/textarea.tsx +209 -0
  292. package/docs/data-entry/time-picker.tsx +99 -0
  293. package/docs/data-entry/toggle-count.tsx +293 -0
  294. package/docs/data-entry/toggle-group.tsx +225 -0
  295. package/docs/data-entry/toggle.tsx +127 -0
  296. package/docs/data-entry/transfer.tsx +97 -0
  297. package/docs/data-entry/tree-select.tsx +204 -0
  298. package/docs/data-entry/upload.tsx +184 -0
  299. package/docs/feedback/alert-dialog.tsx +270 -0
  300. package/docs/feedback/alert.tsx +248 -0
  301. package/docs/feedback/banner.tsx +183 -0
  302. package/docs/feedback/danger-confirm.tsx +88 -0
  303. package/docs/feedback/dialog-touch.tsx +24 -0
  304. package/docs/feedback/dialog.tsx +193 -0
  305. package/docs/feedback/sheet.tsx +410 -0
  306. package/docs/feedback/skeleton.tsx +111 -0
  307. package/docs/feedback/toast.tsx +135 -0
  308. package/docs/feedback/tooltip.tsx +171 -0
  309. package/docs/feedback/two-factor-setup.tsx +68 -0
  310. package/docs/foundation/_email-specimen.ts +274 -0
  311. package/docs/foundation/colors.tsx +202 -0
  312. package/docs/foundation/density.tsx +232 -0
  313. package/docs/foundation/email-tokens.tsx +306 -0
  314. package/docs/foundation/radius-shadow.tsx +152 -0
  315. package/docs/foundation/spacing.tsx +211 -0
  316. package/docs/foundation/typography.tsx +147 -0
  317. package/docs/general/activity.tsx +224 -0
  318. package/docs/general/button/examples/destructive-confirm.tsx +90 -0
  319. package/docs/general/button/examples/form-actions.tsx +85 -0
  320. package/docs/general/button/examples/toolbar.tsx +160 -0
  321. package/docs/general/button/index.md +12 -0
  322. package/docs/general/button/index.tsx +211 -0
  323. package/docs/general/logo.tsx +131 -0
  324. package/docs/general/reveal.tsx +41 -0
  325. package/docs/general/typography.tsx +152 -0
  326. package/docs/layout/account-chip.tsx +72 -0
  327. package/docs/layout/admin-collection.tsx +122 -0
  328. package/docs/layout/app-shell.tsx +374 -0
  329. package/docs/layout/aspect-ratio.tsx +119 -0
  330. package/docs/layout/auth-account-summary.tsx +103 -0
  331. package/docs/layout/auth-divider.tsx +28 -0
  332. package/docs/layout/auth-footer.tsx +37 -0
  333. package/docs/layout/auth-identity.tsx +36 -0
  334. package/docs/layout/auth-recovery/examples/mfa-challenge.md +41 -0
  335. package/docs/layout/auth-recovery/examples/mfa-challenge.tsx +254 -0
  336. package/docs/layout/auth-recovery/examples/mobile-390.md +25 -0
  337. package/docs/layout/auth-recovery/examples/mobile-390.tsx +82 -0
  338. package/docs/layout/auth-recovery/examples/password-recovery.md +24 -0
  339. package/docs/layout/auth-recovery/examples/password-recovery.tsx +197 -0
  340. package/docs/layout/auth-recovery/examples/wrapping-ja-en-vi.md +27 -0
  341. package/docs/layout/auth-recovery/examples/wrapping-ja-en-vi.tsx +156 -0
  342. package/docs/layout/auth-recovery/index.md +140 -0
  343. package/docs/layout/auth-recovery/index.tsx +96 -0
  344. package/docs/layout/auth-shell-context.tsx +103 -0
  345. package/docs/layout/auth-shell-device.tsx +112 -0
  346. package/docs/layout/auth-shell-registration.tsx +335 -0
  347. package/docs/layout/auth-shell-variants.tsx +139 -0
  348. package/docs/layout/auth-shell.tsx +59 -0
  349. package/docs/layout/auth-stack.tsx +40 -0
  350. package/docs/layout/centered-shell.tsx +102 -0
  351. package/docs/layout/error-surface/examples/application-403.tsx +84 -0
  352. package/docs/layout/error-surface/examples/application-404.tsx +77 -0
  353. package/docs/layout/error-surface/examples/system-500.tsx +47 -0
  354. package/docs/layout/error-surface/examples/system-503.tsx +48 -0
  355. package/docs/layout/error-surface/index.md +153 -0
  356. package/docs/layout/error-surface/index.tsx +239 -0
  357. package/docs/layout/flex.tsx +262 -0
  358. package/docs/layout/legal-document-shell/_data.ts +110 -0
  359. package/docs/layout/legal-document-shell/examples/desktop-1440.md +12 -0
  360. package/docs/layout/legal-document-shell/examples/desktop-1440.tsx +83 -0
  361. package/docs/layout/legal-document-shell/examples/mobile-390.md +22 -0
  362. package/docs/layout/legal-document-shell/examples/mobile-390.tsx +70 -0
  363. package/docs/layout/legal-document-shell/examples/tablet-1024.md +3 -0
  364. package/docs/layout/legal-document-shell/examples/tablet-1024.tsx +84 -0
  365. package/docs/layout/legal-document-shell/examples/wrapping-ja-en-vi.md +18 -0
  366. package/docs/layout/legal-document-shell/examples/wrapping-ja-en-vi.tsx +61 -0
  367. package/docs/layout/legal-document-shell/index.md +51 -0
  368. package/docs/layout/legal-document-shell/index.tsx +101 -0
  369. package/docs/layout/master-detail.tsx +387 -0
  370. package/docs/layout/org-switcher.tsx +131 -0
  371. package/docs/layout/page-container.tsx +946 -0
  372. package/docs/layout/resizable-panel.tsx +419 -0
  373. package/docs/layout/responsive-grid.tsx +235 -0
  374. package/docs/layout/separator.tsx +327 -0
  375. package/docs/layout/service-role-panel.tsx +144 -0
  376. package/docs/layout/sidebar.tsx +586 -0
  377. package/docs/layout/split-pane.tsx +451 -0
  378. package/docs/layout/topbar.tsx +347 -0
  379. package/docs/navigation/app-setting-picker.tsx +141 -0
  380. package/docs/navigation/breadcrumb.tsx +128 -0
  381. package/docs/navigation/context-menu.tsx +117 -0
  382. package/docs/navigation/dropdown-menu.tsx +213 -0
  383. package/docs/navigation/filter-bar.tsx +227 -0
  384. package/docs/navigation/menubar.tsx +141 -0
  385. package/docs/navigation/navigation-menu.tsx +165 -0
  386. package/docs/navigation/pagination.tsx +241 -0
  387. package/docs/navigation/steps.tsx +263 -0
  388. package/docs/navigation/tabs-rtl.tsx +28 -0
  389. package/docs/navigation/tabs.tsx +308 -0
  390. package/docs/navigation/toolbar.tsx +339 -0
  391. package/docs/providers/app-provider.tsx +191 -0
  392. package/docs/providers/format-date.tsx +230 -0
  393. package/docs/query/button-refetch.tsx +160 -0
  394. package/docs/query/data-state.tsx +192 -0
  395. package/docs/query/infinite-query-state.tsx +93 -0
  396. package/docs/query/mutation-feedback.tsx +70 -0
  397. package/docs/query/prefetch-link.tsx +262 -0
  398. package/docs/query/touch-actions.tsx +68 -0
  399. package/docs/showcase/acme-portal.tsx +441 -0
  400. package/docs/showcase/acme-website.tsx +455 -0
  401. package/docs/showcase/case1-warehouse-dashboard.tsx +343 -0
  402. package/docs/showcase/case2-employee-me.tsx +441 -0
  403. package/docs/showcase/case3-approval-workflow.tsx +527 -0
  404. package/docs/showcase/case4-login.tsx +307 -0
  405. package/docs/showcase/case5-shift-calendar.tsx +795 -0
  406. package/docs/showcase/case6-agency-handy.tsx +1072 -0
  407. package/docs/showcase/futurelastic-web.tsx +390 -0
  408. package/docs/showcase/org-switcher.tsx +313 -0
  409. package/docs/showcase/permission-matrix.tsx +343 -0
  410. package/docs/showcase/public-landing.tsx +210 -0
  411. package/docs/showcase/settings-account-sections.tsx +559 -0
  412. package/docs/showcase/settings-security-mfa.tsx +145 -0
  413. package/docs/showcase/table-approval-queue.tsx +199 -0
  414. package/docs/showcase/table-bulk-actions.tsx +460 -0
  415. package/docs/showcase/table-compact-kintone.tsx +438 -0
  416. package/docs/showcase/table-conditional-format.tsx +367 -0
  417. package/docs/showcase/table-crud-list.tsx +544 -0
  418. package/docs/showcase/table-density.tsx +340 -0
  419. package/docs/showcase/table-expandable-rows.tsx +471 -0
  420. package/docs/showcase/table-filter-chips.tsx +461 -0
  421. package/docs/showcase/table-footer-totals.tsx +311 -0
  422. package/docs/showcase/table-grouped-subtotals.tsx +388 -0
  423. package/docs/showcase/table-master-detail.tsx +479 -0
  424. package/docs/showcase/table-pagination.tsx +320 -0
  425. package/docs/showcase/table-states.tsx +363 -0
  426. package/docs/showcase/table-sticky-columns.tsx +373 -0
  427. package/docs/showcase/table-tree-rows.tsx +444 -0
  428. package/docs/showcase/table-view-tabs.tsx +488 -0
  429. package/package.json +9 -11
  430. package/scripts/_agent-setup.mjs +16 -2
  431. package/scripts/audit-hook.mjs +3 -13
  432. package/scripts/init-agent-kit.mjs +1 -14
  433. package/scripts/postinstall.mjs +12 -12
  434. package/scripts/ui-audit.mjs +93 -31
  435. package/scripts/visual-audit-rules.mjs +51 -7
  436. package/scripts/visual-audit.mjs +118 -47
@@ -1,6 +1,4 @@
1
- /*
2
- * CONTROLS — height, padding, font size follow active density tokens.
3
- */
1
+
4
2
 
5
3
  @layer components {
6
4
  .ui-toggle {
@@ -70,6 +68,7 @@
70
68
  display: inline-flex;
71
69
  align-items: center;
72
70
  justify-content: center;
71
+ cursor: pointer;
73
72
  gap: var(--control-gap);
74
73
  white-space: nowrap;
75
74
  border-radius: var(--control-radius);
@@ -102,8 +101,7 @@
102
101
  .ui-button--default {
103
102
  background: hsl(var(--primary));
104
103
  color: hsl(var(--primary-foreground));
105
- /* Brand glow layers on top of the resting shadow; --shadow-glow is invisible by default
106
- * (quiet) so a service opts the CTA halo in by setting the token alone. */
104
+
107
105
  box-shadow: var(--shadow-sm), var(--shadow-glow);
108
106
  }
109
107
 
@@ -117,10 +115,6 @@
117
115
  box-shadow: var(--shadow-sm);
118
116
  }
119
117
 
120
- /* SOLID hover/active tokens, never `--destructive / 0.9`: an alpha fill composites with whatever
121
- * surface sits behind the button (a lighter Card/AlertDialog vs the page), so the effective colour
122
- * — and its contrast with the label — drifts per context and can dip below AA (gh#199). The solid
123
- * --destructive-hover/active are tuned to clear 4.5:1 on every surface. */
124
118
  .ui-button--destructive:hover {
125
119
  background: hsl(var(--destructive-hover));
126
120
  }
@@ -129,9 +123,6 @@
129
123
  background: hsl(var(--destructive-active));
130
124
  }
131
125
 
132
- /* Outline + ghost set their text colour EXPLICITLY (read --foreground) — never rely on inherited
133
- * body colour. Inheriting breaks in a scoped dark region (e.g. an on-navy hero): the body's
134
- * computed dark colour would carry in and the label goes near-invisible on the dark fill. */
135
126
  .ui-button--outline {
136
127
  border: 1px solid hsl(var(--input));
137
128
  background: hsl(var(--background));
@@ -183,11 +174,6 @@
183
174
  height: var(--control-icon-size-sm);
184
175
  }
185
176
 
186
- /* A Badge nested in a Button is a compact count/indicator chip, not a free-standing status
187
- * chip. A full-height badge grows to the button's inner edges, so its border and (tighter)
188
- * corner radius collide with the button's rounded border — it looks like the badge "cuts"
189
- * the button outline. Drop its own vertical padding and pin the line box + radius so it
190
- * always sits cleanly inside the button at every control height. */
191
177
  .ui-button [data-slot="badge"] {
192
178
  padding-block: 0;
193
179
  line-height: var(--line-height-tight);
@@ -206,18 +192,12 @@
206
192
  padding: 0;
207
193
  }
208
194
 
209
- /* Icon-only sizes must be square at the SAME height as their text-size counterpart, so an
210
- * icon button can sit flush beside a labelled one (split buttons, toolbars). Derive them from
211
- * the density tokens — never from a hand-written offset off the base --control-height, which
212
- * is how icon-sm drifted a step down to the xs height (issue #133). */
213
195
  .ui-button--icon-xs {
214
196
  width: var(--control-height-xs);
215
197
  height: var(--control-height-xs);
216
198
  padding: 0;
217
199
  }
218
200
 
219
- /* Rule #24: compact visual tiers never shrink the actual coarse-pointer hit target below 44px.
220
- * The glyph remains 12px; only the interactive square expands. */
221
201
  @media (pointer: coarse) {
222
202
  .ui-button--icon-xs {
223
203
  width: var(--control-height-default);
@@ -257,25 +237,17 @@
257
237
  box-shadow: var(--control-shadow);
258
238
  }
259
239
 
260
- /* TEXTAREA AUTO-GROW (gh#310) — the box grows with its content, bounded in TEXT ROWS.
261
- *
262
- * TECHNIQUE: the replicated-content grid. The wrapper is a one-cell grid; a hidden `::after`
263
- * carrying the same text in the same typography occupies that cell and SIZES the row, and the
264
- * textarea (a grid item, `align-self: stretch` by default) fills whatever the row became. The
265
- * JS side therefore only ever WRITES one attribute — it never reads `scrollHeight`, never sets
266
- * `style.height`, and so never forces a synchronous reflow per keystroke, and never touches
267
- * `scrollTop` (which is what makes typing in the middle of a long value jump).
268
- *
269
- * Both bounds live on the PSEUDO-ELEMENT rather than on the textarea, deliberately: a
270
- * pseudo-element is out of reach of Tailwind utilities, so `variant="ghost"`'s `min-h-0`
271
- * (@layer utilities, which beats anything here in @layer components) cannot silently defeat
272
- * the floor the way it would if the bounds sat on the control itself. */
240
+ .ui-control:disabled,
241
+ .ui-control[data-disabled],
242
+ .ui-control-multiline:disabled {
243
+ cursor: not-allowed;
244
+ opacity: var(--disabled-opacity);
245
+ }
246
+
273
247
  .ui-textarea-autogrow {
274
248
  position: relative;
275
249
  display: grid;
276
- /* `minmax(0, 1fr)`, never the default `auto`: an auto column is sized by the replica's
277
- * MAX-CONTENT, so a single unbroken 2,000-character token would widen the column — and with
278
- * it the control — to 18,000px instead of wrapping. Measured, not assumed. */
250
+
279
251
  grid-template-columns: minmax(0, 1fr);
280
252
  inline-size: 100%;
281
253
  }
@@ -288,13 +260,10 @@
288
260
  .ui-textarea-autogrow::after {
289
261
  content: attr(data-autogrow-value) " ";
290
262
  visibility: hidden;
291
- /* Explicit, not inherited from preflight: the replica's bounds are BORDER-box measures, and a
292
- * content-box replica overshoots every height by the box inset. */
263
+
293
264
  box-sizing: border-box;
294
265
  margin: 0;
295
- /* `pre-wrap` + `break-word` is the UA treatment of a textarea's own text: newlines are kept,
296
- * an unbroken 2,000-character token wraps instead of widening the box. The trailing " " is
297
- * what keeps a value ending in a newline one row taller, exactly as the control renders it. */
266
+
298
267
  white-space: pre-wrap;
299
268
  overflow-wrap: break-word;
300
269
  font-size: var(--control-font-size);
@@ -302,8 +271,7 @@
302
271
  padding-block: var(--control-multiline-padding-block);
303
272
  padding-inline: var(--control-padding-x);
304
273
  border: var(--control-border-width) solid transparent;
305
- /* The floor never undercuts the control tier, so a resting one-row composer still lines up
306
- * with the Input / Button on its row; above it the box counts in rows. */
274
+
307
275
  min-block-size: max(
308
276
  var(--control-height),
309
277
  calc(
@@ -319,7 +287,6 @@
319
287
  );
320
288
  }
321
289
 
322
- /* The ghost variant draws no border, so the row maths must not add one back. */
323
290
  .ui-textarea-autogrow--ghost {
324
291
  --textarea-autogrow-box-inset: calc(var(--control-multiline-padding-block) * 2);
325
292
  }
@@ -329,10 +296,9 @@
329
296
  }
330
297
 
331
298
  .ui-textarea-autogrow > .ui-control-multiline {
332
- /* The mirror owns the floor; the control must be free to stretch down to it. */
299
+
333
300
  min-block-size: 0;
334
- /* Growth is the box's job, not a drag handle's, and past the ceiling the control scrolls
335
- * itself rather than pushing the page. */
301
+
336
302
  resize: none;
337
303
  overflow-y: auto;
338
304
  line-height: var(--textarea-autogrow-line-height, var(--line-height-normal));
@@ -354,9 +320,7 @@
354
320
  .ui-choice-group[data-orientation="horizontal"] {
355
321
  display: flex;
356
322
  flex-wrap: wrap;
357
- /* Occupy the control height and centre the row so a horizontal radio/checkbox
358
- * group lines up with sibling inputs in a horizontal FormField (its options
359
- * are only ~icon-height tall, which otherwise sat above the field label). */
323
+
360
324
  align-items: center;
361
325
  min-height: var(--control-height);
362
326
  column-gap: var(--choice-group-gap-x);
@@ -391,28 +355,6 @@
391
355
  font-size: var(--choice-description-font-size);
392
356
  }
393
357
 
394
- /* Label primitive (#319). Its box and type were Tailwind literals on label.tsx
395
- * (`flex items-center gap-2 text-sm leading-none select-none`), so a service theme could not
396
- * retune the label type ramp or the label→addon gap without forking the component.
397
- *
398
- * SOURCE ORDER IS LOAD-BEARING — this rule MUST stay AFTER `.ui-choice-label` above.
399
- * `Field` renders `<Label className="ui-choice-label">`, so that element carries both classes at
400
- * equal specificity (0,1,0) in the same layer and the LATER rule wins. Today the `text-sm`
401
- * utility out-ranks `.ui-choice-label`'s `font-size: var(--font-size-base)` outright (utilities
402
- * are layered after components); putting `.ui-label` EARLIER would hand every checkbox/radio
403
- * label back to `.ui-choice-label` and silently change its type. `font-weight` is deliberately
404
- * NOT declared here for the same reason: `.ui-choice-label` sets `--font-weight-normal` and
405
- * Label's `font-medium` utility beats it today, so `font-medium` stays a utility.
406
- *
407
- * The disabled states stay utilities too (`peer-disabled:` / `group-data-[disabled=true]:`):
408
- * a state must out-rank the resting rule, which in Tailwind v4 means the utilities layer. So
409
- * does `line-height` — the `leading-none` it replaces existed precisely to beat the companion
410
- * line-height that every `text-*` utility carries, and only a utility can still do that when a
411
- * consumer passes their own `text-lg`. Its value is --control-label-line-height all the same.
412
- *
413
- * Not a regression for the auth shell: the canonical field-label override in shell-layout.css is
414
- * INTENTIONALLY UNLAYERED (gh#263) and unlayered author CSS still beats every layer, so it keeps
415
- * winning over these declarations exactly as it won over the utilities. */
416
358
  .ui-label {
417
359
  display: flex;
418
360
  align-items: center;
@@ -439,10 +381,6 @@
439
381
  border-radius: var(--radius-pill);
440
382
  }
441
383
 
442
- /* `:disabled` only matches form elements, so a purely visual checkbox rendered as a <span>
443
- * (Cascader's CheckboxVisual, Transfer's row marks) never picked this up and had to repeat
444
- * `cursor-not-allowed opacity-50` on the component instead (#319). Matching [data-disabled]
445
- * too lets those share the one rule. The alpha is the system token, not a literal 0.5. */
446
384
  .ui-checkbox:disabled,
447
385
  .ui-radio:disabled,
448
386
  .ui-slider-thumb:disabled,
@@ -477,6 +415,16 @@
477
415
  fill: currentColor;
478
416
  }
479
417
 
418
+ .ui-switch[aria-invalid="true"] {
419
+ --tw-ring-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
420
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
421
+ }
422
+
423
+ .ui-tag-input[aria-invalid="true"],
424
+ .ui-tag-input:has([aria-invalid="true"]) {
425
+ border-color: hsl(var(--destructive));
426
+ }
427
+
480
428
  .ui-switch {
481
429
  display: inline-flex;
482
430
  width: var(--switch-width);
@@ -790,331 +738,323 @@
790
738
  }
791
739
  }
792
740
 
793
- /* PasswordInput */
794
- .ui-password-input {
795
- position: relative;
796
- display: block;
797
- }
798
- .ui-password-input-field {
799
- padding-inline-end: 2.5rem;
800
- }
801
- .ui-password-input-toggle {
802
- position: absolute;
803
- inset-block: 0;
804
- inset-inline-end: 0;
805
- display: inline-flex;
806
- align-items: center;
807
- justify-content: center;
808
- width: 2.5rem;
809
- color: hsl(var(--muted-foreground));
810
- background: transparent;
811
- border: 0;
812
- cursor: pointer;
813
- }
814
- .ui-password-input-toggle:hover {
815
- color: hsl(var(--foreground));
816
- }
817
- .ui-password-input-toggle svg {
818
- width: 1rem;
819
- height: 1rem;
820
- }
741
+ @layer components {
742
+ .ui-password-input {
743
+ position: relative;
744
+ display: block;
745
+ }
746
+ .ui-password-input-field {
747
+ padding-inline-end: 2.5rem;
748
+ }
749
+ .ui-password-input-toggle {
750
+ position: absolute;
751
+ inset-block: 0;
752
+ inset-inline-end: 0;
753
+ display: inline-flex;
754
+ align-items: center;
755
+ justify-content: center;
756
+ width: 2.5rem;
757
+ color: hsl(var(--muted-foreground));
758
+ background: transparent;
759
+ border: 0;
760
+ cursor: pointer;
761
+ }
762
+ .ui-password-input-toggle:hover {
763
+ color: hsl(var(--foreground));
764
+ }
765
+ .ui-password-input-toggle svg {
766
+ width: 1rem;
767
+ height: 1rem;
768
+ }
821
769
 
822
- /* InputOTP */
823
- .ui-otp-container {
824
- display: flex;
825
- align-items: center;
826
- justify-content: var(--otp-container-align, flex-start);
827
- gap: var(--space-2);
828
- }
829
- /* `align` on InputOTP lands on the hidden <input> (input-otp owns the container element), so the
830
- * container reads it through :has() — the same mechanism this file already uses for the invalid
831
- * and disabled states. Specificity puts the per-instance prop above the theme token. */
832
- .ui-otp-container:has(.ui-otp-input[data-align="center"]) {
833
- justify-content: center;
834
- }
835
- .ui-otp-container:has(.ui-otp-input[data-align="end"]) {
836
- justify-content: flex-end;
837
- }
838
- .ui-otp-input:disabled {
839
- cursor: not-allowed;
840
- }
841
- .ui-otp-group {
842
- display: flex;
843
- align-items: center;
844
- }
845
- .ui-otp-slot {
846
- position: relative;
847
- display: flex;
848
- width: var(--otp-slot-inline-size, var(--otp-slot-size, var(--control-height)));
849
- height: var(--otp-slot-block-size, var(--otp-slot-size, var(--control-height)));
850
- align-items: center;
851
- justify-content: center;
852
- font-size: var(--font-size-base);
853
- border: 1px solid hsl(var(--border));
854
- border-inline-start-width: 0;
855
- background: hsl(var(--background));
856
- transition: box-shadow 0.15s ease;
857
- }
858
- .ui-otp-group .ui-otp-slot:first-child {
859
- border-inline-start-width: 1px;
860
- border-start-start-radius: var(--radius-md);
861
- border-end-start-radius: var(--radius-md);
862
- }
863
- .ui-otp-group .ui-otp-slot:last-child {
864
- border-start-end-radius: var(--radius-md);
865
- border-end-end-radius: var(--radius-md);
866
- }
867
- .ui-otp-group[data-appearance="grouped"] {
868
- overflow: hidden;
869
- border: 1px solid hsl(var(--border));
870
- border-radius: var(--radius-md);
871
- background: hsl(var(--background));
872
- }
873
- .ui-otp-group[data-appearance="grouped"] .ui-otp-slot {
874
- border: 0;
875
- border-radius: 0;
876
- background: transparent;
877
- }
878
- .ui-otp-group[data-appearance="grouped"] .ui-otp-slot[data-active="true"] {
879
- box-shadow: none;
880
- }
881
- .ui-otp-group[data-appearance="grouped"]:has(.ui-otp-slot[data-active="true"]) {
882
- border-color: hsl(var(--focus-ring-color, var(--ring)));
883
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
884
- }
885
- .ui-otp-container:has(.ui-otp-input[aria-invalid="true"]) .ui-otp-group[data-appearance="grouped"] {
886
- border-color: hsl(var(--destructive));
887
- }
888
- .ui-otp-container:has(.ui-otp-input[aria-invalid="true"])
770
+ .ui-otp-container {
771
+ display: flex;
772
+ align-items: center;
773
+ justify-content: var(--otp-container-align, flex-start);
774
+ gap: var(--space-2);
775
+ }
776
+
777
+ .ui-otp-container:has(.ui-otp-input[data-align="center"]) {
778
+ justify-content: center;
779
+ }
780
+ .ui-otp-container:has(.ui-otp-input[data-align="end"]) {
781
+ justify-content: flex-end;
782
+ }
783
+ .ui-otp-input:disabled {
784
+ cursor: not-allowed;
785
+ }
786
+ .ui-otp-group {
787
+ display: flex;
788
+ align-items: center;
789
+ }
790
+ .ui-otp-slot {
791
+ position: relative;
792
+ display: flex;
793
+ width: var(--otp-slot-inline-size, var(--otp-slot-size, var(--control-height)));
794
+ height: var(--otp-slot-block-size, var(--otp-slot-size, var(--control-height)));
795
+ align-items: center;
796
+ justify-content: center;
797
+ font-size: var(--font-size-base);
798
+ border: var(--control-border-width) solid hsl(var(--border));
799
+ border-inline-start-width: 0;
800
+ background: hsl(var(--background));
801
+ transition: box-shadow 0.15s ease;
802
+ }
803
+ .ui-otp-group .ui-otp-slot:first-child {
804
+ border-inline-start-width: var(--control-border-width);
805
+ border-start-start-radius: var(--radius-md);
806
+ border-end-start-radius: var(--radius-md);
807
+ }
808
+ .ui-otp-group .ui-otp-slot:last-child {
809
+ border-start-end-radius: var(--radius-md);
810
+ border-end-end-radius: var(--radius-md);
811
+ }
812
+ .ui-otp-group[data-appearance="grouped"] {
813
+ overflow: hidden;
814
+ border: var(--control-border-width) solid hsl(var(--border));
815
+ border-radius: var(--radius-md);
816
+ background: hsl(var(--background));
817
+ }
818
+ .ui-otp-group[data-appearance="grouped"] .ui-otp-slot {
819
+ border: 0;
820
+ border-radius: 0;
821
+ background: transparent;
822
+ }
823
+ .ui-otp-group[data-appearance="grouped"] .ui-otp-slot[data-active="true"] {
824
+ box-shadow: none;
825
+ }
889
826
  .ui-otp-group[data-appearance="grouped"]:has(.ui-otp-slot[data-active="true"]) {
890
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
891
- }
892
- .ui-otp-container:has(.ui-otp-input:disabled) .ui-otp-group[data-appearance="grouped"] {
893
- background: hsl(var(--muted));
894
- }
895
- .ui-otp-slot[data-active="true"] {
896
- z-index: 1;
897
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
898
- }
899
- .ui-otp-container:has(.ui-otp-input[aria-invalid="true"]) .ui-otp-slot {
900
- border-color: hsl(var(--destructive));
901
- }
902
- .ui-otp-container:has(.ui-otp-input[aria-invalid="true"]) .ui-otp-slot[data-active="true"] {
903
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
904
- }
905
- .ui-otp-container:has(.ui-otp-input:disabled) .ui-otp-slot {
906
- cursor: not-allowed;
907
- background: hsl(var(--muted));
908
- color: hsl(var(--muted-foreground));
909
- }
910
- .ui-otp-caret-wrapper {
911
- position: absolute;
912
- inset: 0;
913
- display: flex;
914
- align-items: center;
915
- justify-content: center;
916
- }
917
- .ui-otp-caret {
918
- width: var(--otp-caret-inline-size);
919
- height: var(--otp-caret-block-size);
920
- background: hsl(var(--foreground));
921
- animation: ui-otp-blink 1s step-end infinite;
922
- }
923
- @keyframes ui-otp-blink {
924
- 50% {
925
- opacity: 0;
827
+ border-color: hsl(var(--focus-ring-color, var(--ring)) / var(--focus-ring-opacity, 1));
828
+ box-shadow: 0 0 0 var(--focus-ring-width)
829
+ hsl(var(--focus-ring-color, var(--ring)) / var(--focus-ring-opacity, 1));
830
+ }
831
+ .ui-otp-container:has(.ui-otp-input[aria-invalid="true"])
832
+ .ui-otp-group[data-appearance="grouped"] {
833
+ border-color: hsl(var(--destructive));
834
+ }
835
+ .ui-otp-container:has(.ui-otp-input[aria-invalid="true"])
836
+ .ui-otp-group[data-appearance="grouped"]:has(.ui-otp-slot[data-active="true"]) {
837
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
838
+ }
839
+ .ui-otp-container:has(.ui-otp-input:disabled) .ui-otp-group[data-appearance="grouped"] {
840
+ background: hsl(var(--muted));
841
+ }
842
+ .ui-otp-slot[data-active="true"] {
843
+ z-index: 1;
844
+ box-shadow: 0 0 0 var(--focus-ring-width)
845
+ hsl(var(--focus-ring-color, var(--ring)) / var(--focus-ring-opacity, 1));
846
+ }
847
+ .ui-otp-container:has(.ui-otp-input[aria-invalid="true"]) .ui-otp-slot {
848
+ border-color: hsl(var(--destructive));
849
+ }
850
+ .ui-otp-container:has(.ui-otp-input[aria-invalid="true"]) .ui-otp-slot[data-active="true"] {
851
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
852
+ }
853
+ .ui-otp-container:has(.ui-otp-input:disabled) .ui-otp-slot {
854
+ cursor: not-allowed;
855
+ background: hsl(var(--muted));
856
+ color: hsl(var(--muted-foreground));
857
+ }
858
+ .ui-otp-caret-wrapper {
859
+ position: absolute;
860
+ inset: 0;
861
+ display: flex;
862
+ align-items: center;
863
+ justify-content: center;
864
+ }
865
+ .ui-otp-caret {
866
+ width: var(--otp-caret-inline-size);
867
+ height: var(--otp-caret-block-size);
868
+ background: hsl(var(--foreground));
869
+ animation: ui-otp-blink 1s step-end infinite;
870
+ }
871
+ @keyframes ui-otp-blink {
872
+ 50% {
873
+ opacity: 0;
874
+ }
875
+ }
876
+ .ui-otp-separator-icon {
877
+ width: var(--otp-separator-icon-size);
878
+ height: var(--otp-separator-icon-size);
879
+ color: hsl(var(--muted-foreground));
926
880
  }
927
- }
928
- .ui-otp-separator-icon {
929
- width: var(--otp-separator-icon-size);
930
- height: var(--otp-separator-icon-size);
931
- color: hsl(var(--muted-foreground));
932
- }
933
881
 
934
- /* Rating */
935
- .ui-rating {
936
- display: inline-flex;
937
- align-items: center;
938
- gap: var(--space-1);
939
- /* A 10-star scale needs ~276px of hit area; a 320px viewport offers ~212 inside a card. Without
940
- * wrapping, the last stars are painted outside the surface with nothing to scroll them into
941
- * view, so they can be neither seen nor clicked (WCAG 2.1.1 / 2.4.7). Wrapping keeps every star
942
- * reachable and is a no-op wherever the row already fits. */
943
- flex-wrap: wrap;
944
- }
945
- .ui-rating-star {
946
- /* ≥24px hit area (WCAG 2.5.8) around the 20px glyph */
947
- display: inline-flex;
948
- align-items: center;
949
- justify-content: center;
950
- min-width: 1.5rem;
951
- min-height: 1.5rem;
952
- background: transparent;
953
- border: 0;
954
- padding: 0;
955
- cursor: pointer;
956
- color: hsl(var(--muted-foreground) / 0.45);
957
- line-height: 0;
958
- }
959
- .ui-rating-star:disabled {
960
- cursor: default;
961
- }
962
- .ui-rating-star svg {
963
- width: 1.25rem;
964
- height: 1.25rem;
965
- }
966
- .ui-rating-star-filled {
967
- color: hsl(var(--warning));
968
- }
969
- .ui-rating-star-filled svg {
970
- fill: currentColor;
971
- }
972
- .ui-rating-star:focus-visible {
973
- border-radius: var(--radius-sm);
974
- }
882
+ .ui-rating {
883
+ display: inline-flex;
884
+ align-items: center;
885
+ gap: var(--space-1);
975
886
 
976
- /* TagInput */
977
- .ui-tag-input {
978
- display: flex;
979
- flex-wrap: wrap;
980
- align-items: center;
981
- gap: var(--space-2);
982
- min-height: var(--control-height);
983
- padding: var(--space-1) var(--space-2);
984
- border: 1px solid hsl(var(--input));
985
- border-radius: var(--radius-md);
986
- background: hsl(var(--background));
987
- }
988
- .ui-tag-input:focus-within {
989
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
990
- border-color: hsl(var(--focus-ring-color, var(--ring)));
991
- }
992
- .ui-tag-input-disabled {
993
- opacity: 0.6;
994
- cursor: not-allowed;
995
- }
996
- /* The chip list is its own flex row: a real `gap` separates peer chips on the
997
- 8px step (distinct concepts, not a tight group) instead of relying on
998
- collapsed inline whitespace, and flex drops the inter-element whitespace. */
999
- .ui-tag-input-list {
1000
- display: flex;
1001
- flex-wrap: wrap;
1002
- align-items: center;
1003
- gap: var(--space-2);
1004
- margin: 0;
1005
- padding: 0;
1006
- list-style: none;
1007
- }
1008
- .ui-tag-input-chip {
1009
- display: inline-flex;
1010
- align-items: center;
1011
- gap: var(--space-1);
1012
- font-size: var(--tag-input-chip-font-size);
1013
- padding: 0.0625rem var(--space-2);
1014
- border-radius: var(--radius-sm);
1015
- background: hsl(var(--secondary));
1016
- color: hsl(var(--secondary-foreground));
1017
- }
1018
- .ui-tag-input-remove {
1019
- /* ≥24px hit area (WCAG 2.5.8) around the 12px glyph; negative inline margin keeps the chip tight */
1020
- display: inline-flex;
1021
- align-items: center;
1022
- justify-content: center;
1023
- min-width: 1.5rem;
1024
- min-height: 1.5rem;
1025
- margin-inline-end: -0.25rem;
1026
- background: transparent;
1027
- border: 0;
1028
- padding: 0;
1029
- cursor: pointer;
1030
- color: inherit;
1031
- opacity: 0.7;
1032
- }
1033
- .ui-tag-input-remove:hover {
1034
- opacity: 1;
1035
- }
1036
- .ui-tag-input-remove svg {
1037
- width: 0.75rem;
1038
- height: 0.75rem;
1039
- }
1040
- .ui-tag-input-field {
1041
- flex: 1;
1042
- min-width: 6rem;
1043
- border: 0;
1044
- outline: none;
1045
- background: transparent;
1046
- font-size: var(--font-size-base);
1047
- color: inherit;
1048
- }
887
+ flex-wrap: wrap;
888
+ }
889
+ .ui-rating-star {
1049
890
 
1050
- /* NumberInput — spinbutton: composed Input + leading/trailing affix + stepper Buttons.
1051
- `data-size` remaps the --control-height tier so the inner .ui-control field tracks it. */
1052
- .ui-number-input {
1053
- position: relative;
1054
- display: inline-flex;
1055
- align-items: stretch;
1056
- inline-size: 100%;
1057
- }
1058
- .ui-number-input[data-size="xs"] {
1059
- --control-height: var(--control-height-xs);
1060
- }
1061
- .ui-number-input[data-size="sm"] {
1062
- --control-height: var(--control-height-sm);
1063
- }
1064
- .ui-number-input[data-size="lg"] {
1065
- --control-height: var(--control-height-lg);
1066
- }
1067
- /* Reserve room on the trailing edge for the stacked steppers (logical inline-end). */
1068
- .ui-number-input-field {
1069
- inline-size: 100%;
1070
- text-align: end;
1071
- padding-inline-end: calc(var(--control-height) + var(--space-2));
1072
- }
1073
- /* A leading/trailing decorative affix (¥, %) — overlays inside the field. */
1074
- .ui-number-input-affix {
1075
- position: absolute;
1076
- inset-block: 0;
1077
- display: inline-flex;
1078
- align-items: center;
1079
- color: hsl(var(--muted-foreground));
1080
- font-size: var(--font-size-base);
1081
- pointer-events: none;
1082
- }
1083
- .ui-number-input-affix:first-of-type {
1084
- inset-inline-start: var(--control-padding-x);
1085
- }
1086
- /* With a leading affix, pad the field's inline-start so digits don't sit under it. */
1087
- .ui-number-input:has(.ui-number-input-affix:first-of-type) .ui-number-input-field {
1088
- padding-inline-start: calc(var(--control-padding-x) + 1.25rem);
1089
- }
1090
- .ui-number-input-affix:last-of-type {
1091
- inset-inline-end: calc(var(--control-height) + var(--space-2));
1092
- }
1093
- .ui-number-input-steppers {
1094
- position: absolute;
1095
- inset-inline-end: var(--space-1);
1096
- inset-block: 0;
1097
- display: inline-flex;
1098
- flex-direction: column;
1099
- justify-content: center;
1100
- gap: 0.0625rem;
1101
- pointer-events: none;
1102
- }
1103
- .ui-number-input-step {
1104
- pointer-events: auto;
1105
- block-size: calc(var(--control-height) / 2 - 0.1875rem);
1106
- min-block-size: 0;
1107
- inline-size: 1.5rem;
1108
- }
1109
- .ui-number-input-step svg {
1110
- inline-size: 0.75rem;
1111
- block-size: 0.75rem;
891
+ display: inline-flex;
892
+ align-items: center;
893
+ justify-content: center;
894
+ min-width: 1.5rem;
895
+ min-height: 1.5rem;
896
+ background: transparent;
897
+ border: 0;
898
+ padding: 0;
899
+ cursor: pointer;
900
+ color: hsl(var(--muted-foreground) / 0.45);
901
+ line-height: 0;
902
+ }
903
+ .ui-rating-star:disabled {
904
+ cursor: default;
905
+ }
906
+ .ui-rating-star svg {
907
+ width: 1.25rem;
908
+ height: 1.25rem;
909
+ }
910
+ .ui-rating-star-filled {
911
+ color: hsl(var(--warning));
912
+ }
913
+ .ui-rating-star-filled svg {
914
+ fill: currentColor;
915
+ }
916
+ .ui-rating-star:focus-visible {
917
+ border-radius: var(--radius-sm);
918
+ }
919
+
920
+ .ui-tag-input {
921
+ display: flex;
922
+ flex-wrap: wrap;
923
+ align-items: center;
924
+ gap: var(--space-2);
925
+ min-height: var(--control-height);
926
+ padding: var(--space-1) var(--control-padding-x);
927
+ border: var(--control-border-width) solid hsl(var(--input));
928
+ border-radius: var(--control-radius);
929
+ background: hsl(var(--background));
930
+ box-shadow: var(--control-shadow);
931
+ }
932
+ .ui-tag-input-disabled {
933
+ opacity: var(--disabled-opacity);
934
+ cursor: not-allowed;
935
+ }
936
+
937
+ .ui-tag-input-list {
938
+ display: flex;
939
+ flex-wrap: wrap;
940
+ align-items: center;
941
+ gap: var(--space-2);
942
+ margin: 0;
943
+ padding: 0;
944
+ list-style: none;
945
+ }
946
+ .ui-tag-input-chip {
947
+ display: inline-flex;
948
+ align-items: center;
949
+ gap: var(--space-1);
950
+ font-size: var(--tag-input-chip-font-size);
951
+ padding: 0.0625rem var(--space-2);
952
+ border-radius: var(--radius-sm);
953
+ background: hsl(var(--secondary));
954
+ color: hsl(var(--secondary-foreground));
955
+ }
956
+ .ui-tag-input-remove {
957
+
958
+ display: inline-flex;
959
+ align-items: center;
960
+ justify-content: center;
961
+ min-width: 1.5rem;
962
+ min-height: 1.5rem;
963
+ margin-inline-end: -0.25rem;
964
+ background: transparent;
965
+ border: 0;
966
+ padding: 0;
967
+ cursor: pointer;
968
+ color: inherit;
969
+ opacity: 0.7;
970
+ }
971
+ .ui-tag-input-remove:hover {
972
+ opacity: 1;
973
+ }
974
+ .ui-tag-input-remove svg {
975
+ width: 0.75rem;
976
+ height: 0.75rem;
977
+ }
978
+ .ui-tag-input-field {
979
+ flex: 1;
980
+ min-width: 6rem;
981
+ block-size: calc(var(--control-height) - var(--space-1) * 2 - var(--control-border-width) * 2);
982
+ border: 0;
983
+ outline: none;
984
+ background: transparent;
985
+ font-size: var(--control-font-size);
986
+ color: inherit;
987
+ }
988
+
989
+ .ui-number-input {
990
+ position: relative;
991
+ display: inline-flex;
992
+ align-items: stretch;
993
+ inline-size: 100%;
994
+ }
995
+ .ui-number-input[data-size="xs"] {
996
+ --control-height: var(--control-height-xs);
997
+ }
998
+ .ui-input[data-size="sm"] {
999
+ --control-height: var(--control-height-sm);
1000
+ }
1001
+
1002
+ .ui-number-input[data-size="sm"] {
1003
+ --control-height: var(--control-height-sm);
1004
+ }
1005
+ .ui-number-input[data-size="lg"] {
1006
+ --control-height: var(--control-height-lg);
1007
+ }
1008
+
1009
+ .ui-number-input-field {
1010
+ inline-size: 100%;
1011
+ text-align: end;
1012
+ padding-inline-end: calc(var(--control-height) + var(--space-2));
1013
+ }
1014
+
1015
+ .ui-number-input-affix {
1016
+ position: absolute;
1017
+ inset-block: 0;
1018
+ display: inline-flex;
1019
+ align-items: center;
1020
+ color: hsl(var(--muted-foreground));
1021
+ font-size: var(--font-size-base);
1022
+ pointer-events: none;
1023
+ }
1024
+ .ui-number-input-affix:first-of-type {
1025
+ inset-inline-start: var(--control-padding-x);
1026
+ }
1027
+
1028
+ .ui-number-input:has(.ui-number-input-affix:first-of-type) .ui-number-input-field {
1029
+ padding-inline-start: calc(var(--control-padding-x) + 1.25rem);
1030
+ }
1031
+ .ui-number-input-affix:last-of-type {
1032
+ inset-inline-end: calc(var(--control-height) + var(--space-2));
1033
+ }
1034
+ .ui-number-input-steppers {
1035
+ position: absolute;
1036
+ inset-inline-end: var(--space-1);
1037
+ inset-block: 0;
1038
+ display: inline-flex;
1039
+ flex-direction: column;
1040
+ justify-content: center;
1041
+ gap: 0.0625rem;
1042
+ pointer-events: none;
1043
+ }
1044
+ .ui-number-input-step {
1045
+ pointer-events: auto;
1046
+ block-size: calc(var(--control-height) / 2 - 0.1875rem);
1047
+ min-block-size: 0;
1048
+ inline-size: 1.5rem;
1049
+ }
1050
+ .ui-number-input-step svg {
1051
+ inline-size: 0.75rem;
1052
+ block-size: 0.75rem;
1053
+ }
1112
1054
  }
1113
1055
 
1114
1056
  @layer components {
1115
- /* SELECT-FAMILY TRAILING AFFIX — the clear ✕ / chevron overlay parked at a trigger's inline
1116
- * end. Select, SearchSelect and TagInput each repeated the same literal stack (#319); these
1117
- * are the one shared set of knobs. */
1057
+
1118
1058
  .ui-control-affix {
1119
1059
  position: absolute;
1120
1060
  inset-block: 0;
@@ -1146,7 +1086,6 @@
1146
1086
  flex-shrink: 0;
1147
1087
  }
1148
1088
 
1149
- /* The chevron is decorative and never interactive, so it keeps the resting weight. */
1150
1089
  .ui-control-affix-indicator {
1151
1090
  opacity: var(--control-affix-rest-alpha);
1152
1091
  }
@@ -1155,7 +1094,6 @@
1155
1094
  padding-inline-end: var(--control-trigger-space-inline-end);
1156
1095
  }
1157
1096
 
1158
- /* SEARCH-SELECT — Popover + Command combobox. */
1159
1097
  .ui-search-select-panel {
1160
1098
  display: flex;
1161
1099
  flex-direction: column;
@@ -1180,16 +1118,12 @@
1180
1118
  border-block-end: 1px solid hsl(var(--border));
1181
1119
  }
1182
1120
 
1183
- /* The search field is the panel's own header, so it drops the standalone input chrome. */
1184
1121
  .ui-search-select-search-input {
1185
1122
  border: 0;
1186
1123
  border-radius: 0;
1187
1124
  box-shadow: none;
1188
1125
  }
1189
1126
 
1190
- /* The panel itself is the focused surface, so the inner search field suppresses its own RING
1191
- * (this replaces a `focus-visible:ring-0` utility). It must NOT suppress the outline as well —
1192
- * that would leave keyboard users with no focus affordance at all inside the panel. */
1193
1127
  .ui-search-select-search-input:focus-visible {
1194
1128
  box-shadow: none;
1195
1129
  }
@@ -1202,8 +1136,6 @@
1202
1136
  padding: var(--search-select-list-space-inset);
1203
1137
  }
1204
1138
 
1205
- /* The consumer's renderOption output — it must be able to truncate inside the row, which is
1206
- * what min-inline-size:0 buys in a flex child. */
1207
1139
  .ui-search-select-option-slot {
1208
1140
  min-inline-size: 0;
1209
1141
  flex: 1 1 0%;
@@ -1253,7 +1185,6 @@
1253
1185
  block-size: var(--search-select-spinner-size);
1254
1186
  }
1255
1187
 
1256
- /* Empty / error occupy the panel alone, so they get a taller centred block. */
1257
1188
  .ui-search-select-placeholder {
1258
1189
  padding-inline: var(--search-select-status-space-inline);
1259
1190
  padding-block: var(--search-select-placeholder-space-block);
@@ -1274,7 +1205,7 @@
1274
1205
  }
1275
1206
 
1276
1207
  @layer components {
1277
- /* CASCADER — column browser (desktop) + flat search list (query mode). */
1208
+
1278
1209
  .ui-cascader-columns {
1279
1210
  display: flex;
1280
1211
  max-block-size: var(--cascader-columns-max-height);
@@ -1307,7 +1238,6 @@
1307
1238
  flex-shrink: 0;
1308
1239
  }
1309
1240
 
1310
- /* The chevron marks "has children"; it is decorative, so it keeps the resting weight. */
1311
1241
  .ui-cascader-option-chevron {
1312
1242
  inline-size: var(--cascader-option-icon-size);
1313
1243
  block-size: var(--cascader-option-icon-size);
@@ -1336,7 +1266,6 @@
1336
1266
  color: hsl(var(--muted-foreground));
1337
1267
  }
1338
1268
 
1339
- /* Flat search result row — denser than a column row (no chevron to balance). */
1340
1269
  .ui-cascader-result {
1341
1270
  display: flex;
1342
1271
  inline-size: 100%;
@@ -1355,19 +1284,15 @@
1355
1284
  flex-shrink: 0;
1356
1285
  }
1357
1286
 
1358
- /* Reserve the check column even when unselected so labels do not jump on select. */
1359
1287
  .ui-cascader-result-icon[data-selected="false"] {
1360
1288
  opacity: 0;
1361
1289
  }
1362
1290
 
1363
- /* Cascader parks a SMALLER affix closer to the edge than SearchSelect does. Same shared knobs,
1364
- * component-scoped values — the vocabulary is shared, the calibration is not. */
1365
1291
  .ui-cascader-affix {
1366
1292
  --control-affix-inset-inline-end: var(--space-3);
1367
1293
  --control-affix-action-size: var(--control-affix-icon-size);
1368
1294
  }
1369
1295
 
1370
- /* Disabled option rows keep pointer-events off and take the one system alpha. */
1371
1296
  .ui-cascader-option[data-disabled],
1372
1297
  .ui-cascader-trigger[data-disabled] {
1373
1298
  pointer-events: none;
@@ -1376,9 +1301,7 @@
1376
1301
  }
1377
1302
 
1378
1303
  @layer components {
1379
- /* SELECT — the Radix listbox popup. Its row rhythm, separator and label come from the shared
1380
- * menu rules in navigation-layout.css (the same surface ContextMenu/Menubar/DropdownMenu use);
1381
- * only what is genuinely Select's own lives here (#319). */
1304
+
1382
1305
  .ui-select-content {
1383
1306
  position: relative;
1384
1307
  z-index: var(--overlay-z-index);
@@ -1396,8 +1319,28 @@
1396
1319
  padding: var(--menu-content-space-inset);
1397
1320
  }
1398
1321
 
1399
- /* A listbox row has no leading icon column, so it takes a slightly wider inline inset to read
1400
- * level with a menu row that does. */
1322
+ .ui-select-item,
1323
+ .ui-select-label {
1324
+ border-radius: var(--menu-item-radius);
1325
+ padding-inline: var(--menu-item-space-inline);
1326
+ height: var(--menu-item-height);
1327
+ display: flex;
1328
+ align-items: center;
1329
+ gap: var(--menu-item-space-gap);
1330
+ font-size: var(--menu-item-font-size);
1331
+ }
1332
+
1333
+ .ui-select-item,
1334
+ .ui-select-label {
1335
+ border-radius: var(--menu-item-radius);
1336
+ padding-inline: var(--menu-item-space-inline);
1337
+ height: var(--menu-item-height);
1338
+ display: flex;
1339
+ align-items: center;
1340
+ gap: var(--menu-item-space-gap);
1341
+ font-size: var(--menu-item-font-size);
1342
+ }
1343
+
1401
1344
  .ui-select-item {
1402
1345
  position: relative;
1403
1346
  inline-size: 100%;
@@ -1453,7 +1396,7 @@
1453
1396
  }
1454
1397
 
1455
1398
  @layer components {
1456
- /* TIME PICKER — hour/minute/second scroll columns + an inline affix pair. */
1399
+
1457
1400
  .ui-time-picker-panel {
1458
1401
  inline-size: var(--time-picker-panel-width);
1459
1402
  }
@@ -1523,9 +1466,6 @@
1523
1466
  padding: 0;
1524
1467
  }
1525
1468
 
1526
- /* Two affix controls (clear + clock), so a wider reserved end than the single-affix triggers. */
1527
- /* A popover whose content owns its own padding (a Calendar, a month grid) — the panel just
1528
- * hugs it. Shared by the date/time pickers. */
1529
1469
  .ui-control-panel-flush {
1530
1470
  inline-size: auto;
1531
1471
  padding: 0;
@@ -1541,8 +1481,6 @@
1541
1481
  gap: var(--time-picker-affix-space-gap);
1542
1482
  }
1543
1483
 
1544
- /* Sits INSIDE the field rather than overlaying it, so it rests a touch heavier than the
1545
- * overlay affix Select/SearchSelect use. Shared by Input and TimePicker. */
1546
1484
  .ui-control-inline-affix-action {
1547
1485
  display: inline-flex;
1548
1486
  inline-size: var(--control-inline-affix-size);
@@ -1566,7 +1504,6 @@
1566
1504
  block-size: var(--control-inline-affix-icon-size);
1567
1505
  }
1568
1506
 
1569
- /* The browser-owned file button, restyled to read as a control inside the control. */
1570
1507
  .ui-input::file-selector-button {
1571
1508
  display: inline-flex;
1572
1509
  block-size: var(--input-file-button-height);
@@ -1577,8 +1514,6 @@
1577
1514
  font-weight: var(--font-weight-medium);
1578
1515
  }
1579
1516
 
1580
- /* A field in a flex row must be allowed to shrink below its content, or a long value pushes
1581
- * the row wider instead of the text truncating. */
1582
1517
  .ui-input {
1583
1518
  min-inline-size: 0;
1584
1519
  }
@@ -1589,7 +1524,6 @@
1589
1524
  opacity: var(--disabled-opacity);
1590
1525
  }
1591
1526
 
1592
- /* INPUT affix slots — leading/trailing controls parked inside the field box. */
1593
1527
  .ui-input-affix-wrapper {
1594
1528
  position: relative;
1595
1529
  display: inline-flex;
@@ -1615,16 +1549,12 @@
1615
1549
  inset-inline-end: var(--control-inline-affix-inset-inline);
1616
1550
  }
1617
1551
 
1618
- /* A textarea has no single line to centre an affix on, so its clear control parks at the
1619
- * top-end corner. Everything else — box, radius, resting alpha, hover — is the shared
1620
- * inside-field affix it borrows from Input. */
1621
1552
  .ui-textarea-clear {
1622
1553
  position: absolute;
1623
1554
  inset-block-start: var(--textarea-clear-inset-block-start);
1624
1555
  inset-inline-end: var(--control-inline-affix-inset-inline);
1625
1556
  }
1626
1557
 
1627
- /* The field reserves room on whichever side carries an affix, so its text never runs under. */
1628
1558
  .ui-input--leading-affix {
1629
1559
  padding-inline-start: var(--control-inline-affix-space-inline-end);
1630
1560
  }
@@ -1635,7 +1565,7 @@
1635
1565
  }
1636
1566
 
1637
1567
  @layer components {
1638
- /* TREE SELECT — a Select whose panel holds a tree. */
1568
+
1639
1569
  .ui-tree-select-popover {
1640
1570
  inline-size: var(--radix-popover-trigger-width);
1641
1571
  padding: 0;
@@ -1656,8 +1586,6 @@
1656
1586
  color: hsl(var(--muted-foreground));
1657
1587
  }
1658
1588
 
1659
- /* Indent is base + depth × step, driven by `--tree-select-depth` on the row. It used to be a
1660
- * magic expression inline in JSX, so no theme could reach it. */
1661
1589
  .ui-tree-select-row {
1662
1590
  display: flex;
1663
1591
  align-items: center;
@@ -1719,8 +1647,6 @@
1719
1647
  gap: var(--tree-select-label-space-gap);
1720
1648
  }
1721
1649
 
1722
- /* Its clear control is vertically centred against the whole field rather than laid out in a
1723
- * flex overlay, so it keeps its own rule — but reads the shared affix knobs. */
1724
1650
  .ui-tree-select-clear {
1725
1651
  position: absolute;
1726
1652
  inset-inline-end: var(--space-3);
@@ -1741,15 +1667,13 @@
1741
1667
  opacity: 1;
1742
1668
  }
1743
1669
 
1744
- /* Reserves room for a clear control that sits further in than the standard overlay affix. */
1745
1670
  .ui-tree-select-trigger-affixed {
1746
1671
  padding-inline-end: var(--space-10);
1747
1672
  }
1748
1673
  }
1749
1674
 
1750
1675
  @layer components {
1751
- /* CALENDAR — react-day-picker frame. The day/weekday cells keep sizing from --control-height
1752
- * (a system tier decision); everything around them is now a knob. */
1676
+
1753
1677
  .ui-calendar {
1754
1678
  position: relative;
1755
1679
  inline-size: fit-content;
@@ -1847,8 +1771,6 @@
1847
1771
  z-index: 20;
1848
1772
  }
1849
1773
 
1850
- /* Scoped to the calendar root so it out-ranks `.ui-button--default-size`, which is declared
1851
- * later in this file and would otherwise re-apply its block padding to a day cell. */
1852
1774
  .ui-calendar .ui-calendar-day-button {
1853
1775
  inline-size: var(--control-height);
1854
1776
  block-size: var(--control-height);
@@ -1865,10 +1787,24 @@
1865
1787
  color: hsl(var(--muted-foreground));
1866
1788
  opacity: var(--disabled-opacity);
1867
1789
  }
1790
+
1791
+ .ui-calendar-footer {
1792
+ display: flex;
1793
+ align-items: center;
1794
+ justify-content: flex-end;
1795
+ gap: var(--calendar-footer-space-gap);
1796
+ margin-block-start: var(--calendar-footer-space-block-start);
1797
+ padding-block-start: var(--calendar-footer-space-block-start);
1798
+ border-block-start: var(--calendar-footer-border-width) solid hsl(var(--border));
1799
+ }
1800
+
1801
+ .ui-calendar-footer-today {
1802
+ margin-inline-end: auto;
1803
+ }
1868
1804
  }
1869
1805
 
1870
1806
  @layer components {
1871
- /* TRANSFER — two-pane list mover. */
1807
+
1872
1808
  .ui-transfer {
1873
1809
  display: flex;
1874
1810
  flex-wrap: wrap;
@@ -1936,8 +1872,6 @@
1936
1872
  font-size: var(--transfer-row-font-size);
1937
1873
  }
1938
1874
 
1939
- /* Top-aligned against a two-line row, so it takes a hair of optical offset to sit level with
1940
- * the first line's cap height rather than its box. */
1941
1875
  .ui-transfer-row-check {
1942
1876
  margin-block-start: var(--transfer-row-check-space-block-start);
1943
1877
  }
@@ -1965,8 +1899,7 @@
1965
1899
  }
1966
1900
 
1967
1901
  @layer components {
1968
- /* MONTH PICKER — the year-nav + 3-column month grid shared by MonthPicker and
1969
- * MonthRangePicker. One rule set for both; they used to hold identical copies. */
1902
+
1970
1903
  .ui-control-composite-field {
1971
1904
  display: flex;
1972
1905
  inline-size: 100%;
@@ -1974,7 +1907,7 @@
1974
1907
  align-items: center;
1975
1908
  gap: var(--control-composite-field-space-gap);
1976
1909
  border-radius: var(--control-radius);
1977
- border: 1px solid hsl(var(--input));
1910
+ border-color: hsl(var(--input));
1978
1911
  background: hsl(var(--background));
1979
1912
  outline: none;
1980
1913
  transition:
@@ -2045,8 +1978,7 @@
2045
1978
  .ui-month-picker-grid {
2046
1979
  margin-block-start: var(--month-picker-grid-space-block-start);
2047
1980
  display: grid;
2048
- /* 3 columns × 4 rows is the month grid's shape, not a service knob — 12 months only read
2049
- * as a calendar year in this arrangement. */
1981
+
2050
1982
  grid-template-columns: repeat(3, minmax(0, 1fr));
2051
1983
  gap: var(--month-picker-grid-space-gap);
2052
1984
  }
@@ -2056,7 +1988,6 @@
2056
1988
  font-weight: var(--font-weight-normal);
2057
1989
  }
2058
1990
 
2059
- /* BUTTON size-xs + icon-adjacent padding + the count pill (#319). */
2060
1991
  .ui-button--xs {
2061
1992
  height: var(--button-xs-height);
2062
1993
  gap: var(--button-xs-space-gap);
@@ -2069,7 +2000,6 @@
2069
2000
  padding-block: var(--button-space-block);
2070
2001
  }
2071
2002
 
2072
- /* An icon-only child needs less inline room than a label, or the glyph floats in a wide box. */
2073
2003
  .ui-button--default-size:has(> svg),
2074
2004
  .ui-button--md:has(> svg) {
2075
2005
  padding-inline: var(--button-icon-space-inline-md);
@@ -2087,11 +2017,6 @@
2087
2017
  padding-inline: var(--button-icon-space-inline-xs);
2088
2018
  }
2089
2019
 
2090
- /* COUNT PILL (gh#320). Opaque role fills, not a translucent tint over the button's surface: a
2091
- * tint takes its contrast from whatever is behind it, and five variant×theme×hover combinations
2092
- * measured below WCAG 1.4.3's 4.5:1 for small text. The measurements and the share-vs-split
2093
- * reasoning live in tokens/components/control.css beside the knobs. Same treatment gh#312
2094
- * validated on Toggle. */
2095
2020
  .ui-button-count {
2096
2021
  display: inline-flex;
2097
2022
  min-inline-size: var(--button-count-min-width);
@@ -2106,9 +2031,6 @@
2106
2031
  font-variant-numeric: tabular-nums;
2107
2032
  }
2108
2033
 
2109
- /* Each filled variant wears its OWN label pair, swapped. That makes the pill exactly as legible
2110
- * as the label it sits beside — it cannot be less legible under any theme that keeps the button
2111
- * itself readable, which a fixed colour could not promise. */
2112
2034
  .ui-button--default .ui-button-count {
2113
2035
  background: var(--button-count-default-background, hsl(var(--primary-foreground)));
2114
2036
  color: var(--button-count-default-color, hsl(var(--primary)));