banhaten 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/CONTRIBUTING.md +6 -4
  3. package/README.md +4 -2
  4. package/docs/design-spacing.md +13 -0
  5. package/docs/design-system/README.md +2 -0
  6. package/docs/design-system/appearances/default.md +1 -1
  7. package/docs/design-system/appearances/rounded.md +1 -1
  8. package/docs/design-system/appearances/sharp.md +1 -1
  9. package/docs/design-system/component-showcase-consistency-report.md +5 -5
  10. package/docs/design-system/components/README.md +15 -0
  11. package/docs/design-system/components/accordion.md +1 -1
  12. package/docs/design-system/components/activity-feed.md +1 -1
  13. package/docs/design-system/components/activity-heatmap.md +1 -1
  14. package/docs/design-system/components/agent-input.md +85 -0
  15. package/docs/design-system/components/alert-dialog.md +1 -1
  16. package/docs/design-system/components/alert.md +10 -4
  17. package/docs/design-system/components/app-header.md +97 -0
  18. package/docs/design-system/components/approval-card.md +134 -0
  19. package/docs/design-system/components/attribute.md +14 -2
  20. package/docs/design-system/components/avatar.md +1 -1
  21. package/docs/design-system/components/badge.md +1 -1
  22. package/docs/design-system/components/banner.md +1 -1
  23. package/docs/design-system/components/breadcrumbs.md +8 -3
  24. package/docs/design-system/components/button-group.md +1 -1
  25. package/docs/design-system/components/button.md +8 -5
  26. package/docs/design-system/components/card.md +15 -1
  27. package/docs/design-system/components/catalog-components.md +1 -1
  28. package/docs/design-system/components/chart.md +95 -24
  29. package/docs/design-system/components/checkbox.md +1 -1
  30. package/docs/design-system/components/collapsible.md +20 -6
  31. package/docs/design-system/components/command.md +1 -1
  32. package/docs/design-system/components/context-menu.md +1 -1
  33. package/docs/design-system/components/date-picker.md +1 -1
  34. package/docs/design-system/components/file-upload.md +1 -1
  35. package/docs/design-system/components/hover-card.md +1 -1
  36. package/docs/design-system/components/inline-citations.md +93 -0
  37. package/docs/design-system/components/input-group.md +1 -1
  38. package/docs/design-system/components/input.md +59 -3
  39. package/docs/design-system/components/item.md +1 -1
  40. package/docs/design-system/components/kbd.md +6 -4
  41. package/docs/design-system/components/menu.md +35 -4
  42. package/docs/design-system/components/menubar.md +1 -1
  43. package/docs/design-system/components/modal.md +2 -2
  44. package/docs/design-system/components/native-select.md +1 -1
  45. package/docs/design-system/components/navigation-menu.md +1 -1
  46. package/docs/design-system/components/onboarding-step-list-item.md +1 -1
  47. package/docs/design-system/components/page-header.md +58 -17
  48. package/docs/design-system/components/pagination.md +2 -2
  49. package/docs/design-system/components/popover.md +1 -1
  50. package/docs/design-system/components/progress-slider.md +1 -1
  51. package/docs/design-system/components/radio-card.md +1 -1
  52. package/docs/design-system/components/radio-group.md +1 -1
  53. package/docs/design-system/components/resizable.md +1 -1
  54. package/docs/design-system/components/rolling-count.md +61 -0
  55. package/docs/design-system/components/scroll-area.md +1 -1
  56. package/docs/design-system/components/select.md +12 -2
  57. package/docs/design-system/components/sheet.md +1 -1
  58. package/docs/design-system/components/shimmer-text.md +67 -0
  59. package/docs/design-system/components/sidebar.md +75 -8
  60. package/docs/design-system/components/skeleton.md +2 -2
  61. package/docs/design-system/components/slideout.md +1 -1
  62. package/docs/design-system/components/slider.md +2 -2
  63. package/docs/design-system/components/social-button.md +2 -2
  64. package/docs/design-system/components/streaming-text.md +82 -0
  65. package/docs/design-system/components/table.md +103 -21
  66. package/docs/design-system/components/tabs.md +25 -6
  67. package/docs/design-system/components/tag.md +1 -1
  68. package/docs/design-system/components/task-list.md +95 -0
  69. package/docs/design-system/components/textarea.md +1 -1
  70. package/docs/design-system/components/thinking-reasoning.md +72 -0
  71. package/docs/design-system/components/thinking-state.md +67 -0
  72. package/docs/design-system/components/timeline.md +1 -1
  73. package/docs/design-system/components/toast.md +1 -1
  74. package/docs/design-system/components/toggle.md +2 -2
  75. package/docs/design-system/components/toolbar.md +11 -6
  76. package/docs/design-system/components/tooltip.md +6 -4
  77. package/docs/design-system/density-and-elevation.md +16 -16
  78. package/docs/design-system/design-system-test-missing-items.md +8 -8
  79. package/docs/design-system/installation.md +2 -0
  80. package/docs/design-system/tabs.md +2 -1
  81. package/docs/design-system/template-gap-audit.md +32 -0
  82. package/docs/design-system/templates.md +59 -0
  83. package/docs/design-system/token-governance.md +15 -2
  84. package/package.json +2 -2
  85. package/registry/components/accordion.tsx +2 -2
  86. package/registry/components/agent-input-parts.tsx +552 -0
  87. package/registry/components/agent-input-state.ts +156 -0
  88. package/registry/components/agent-input.css +307 -0
  89. package/registry/components/agent-input.tsx +382 -0
  90. package/registry/components/alert-dialog.tsx +2 -2
  91. package/registry/components/alert.tsx +23 -7
  92. package/registry/components/app-header.tsx +151 -0
  93. package/registry/components/approval-card.css +409 -0
  94. package/registry/components/approval-card.tsx +401 -0
  95. package/registry/components/approval-frame.tsx +108 -0
  96. package/registry/components/approval-questions.tsx +559 -0
  97. package/registry/components/attribute.tsx +48 -13
  98. package/registry/components/autocomplete.tsx +15 -68
  99. package/registry/components/avatar.tsx +2 -2
  100. package/registry/components/badge.tsx +8 -7
  101. package/registry/components/button-group.tsx +2 -2
  102. package/registry/components/button.tsx +19 -14
  103. package/registry/components/card.tsx +18 -5
  104. package/registry/components/chart-block-data.ts +177 -0
  105. package/registry/components/chart-block.css +171 -0
  106. package/registry/components/chart-block.tsx +260 -0
  107. package/registry/components/chart-interactions.ts +123 -0
  108. package/registry/components/chart-options.cartesian.ts +162 -0
  109. package/registry/components/chart-options.paint.ts +99 -0
  110. package/registry/components/chart-options.parts.ts +72 -0
  111. package/registry/components/chart-options.radial.ts +109 -0
  112. package/registry/components/chart-options.ts +44 -0
  113. package/registry/components/chart-options.types.ts +67 -0
  114. package/registry/components/chart.tsx +1 -1
  115. package/registry/components/checkbox.tsx +6 -6
  116. package/registry/components/collapsible.tsx +43 -3
  117. package/registry/components/command.tsx +1 -1
  118. package/registry/components/context-menu.tsx +1 -1
  119. package/registry/components/data-chart.tsx +40 -0
  120. package/registry/components/date-picker.tsx +9 -9
  121. package/registry/components/echart-theme.ts +246 -0
  122. package/registry/components/echart.css +49 -0
  123. package/registry/components/echart.tsx +178 -0
  124. package/registry/components/expanded/Breadcrumbs.tsx +5 -1
  125. package/registry/components/expanded/PageHeader.tsx +67 -24
  126. package/registry/components/expanded/Table.tsx +60 -22
  127. package/registry/components/expanded/activityFeed.css +2 -2
  128. package/registry/components/expanded/activityHeatmap.css +1 -1
  129. package/registry/components/expanded/banner.css +2 -2
  130. package/registry/components/expanded/breadcrumbs.css +19 -2
  131. package/registry/components/expanded/catalogComponentsShowcase.css +1 -1
  132. package/registry/components/expanded/fileUpload.css +7 -7
  133. package/registry/components/expanded/onboardingStepListItem.css +2 -2
  134. package/registry/components/expanded/pageHeader.css +38 -44
  135. package/registry/components/expanded/slideout.css +4 -4
  136. package/registry/components/expanded/table.css +58 -9
  137. package/registry/components/expanded/timeline.css +1 -1
  138. package/registry/components/hover-card.tsx +1 -1
  139. package/registry/components/inline-citations.css +126 -0
  140. package/registry/components/inline-citations.tsx +292 -0
  141. package/registry/components/input-group.tsx +4 -4
  142. package/registry/components/input.tsx +150 -137
  143. package/registry/components/item.tsx +1 -1
  144. package/registry/components/kbd.tsx +3 -2
  145. package/registry/components/menu.tsx +117 -12
  146. package/registry/components/menubar.tsx +2 -2
  147. package/registry/components/modal.tsx +3 -3
  148. package/registry/components/native-select.tsx +4 -4
  149. package/registry/components/navigation-menu.tsx +2 -2
  150. package/registry/components/pagination.tsx +4 -4
  151. package/registry/components/popover.tsx +3 -3
  152. package/registry/components/progress-slider.tsx +1 -1
  153. package/registry/components/radio-card.tsx +7 -7
  154. package/registry/components/radio-group.tsx +2 -2
  155. package/registry/components/resizable.tsx +2 -2
  156. package/registry/components/rolling-count.css +37 -0
  157. package/registry/components/rolling-count.tsx +110 -0
  158. package/registry/components/scroll-area.tsx +1 -1
  159. package/registry/components/select.tsx +15 -10
  160. package/registry/components/sheet.tsx +2 -2
  161. package/registry/components/shimmer-text.css +69 -0
  162. package/registry/components/shimmer-text.tsx +43 -0
  163. package/registry/components/sidebar.tsx +528 -223
  164. package/registry/components/skeleton.tsx +1 -1
  165. package/registry/components/slider.tsx +2 -2
  166. package/registry/components/social-button.tsx +1 -1
  167. package/registry/components/streaming-text.css +45 -0
  168. package/registry/components/streaming-text.tsx +165 -0
  169. package/registry/components/table-elements.tsx +164 -5
  170. package/registry/components/tabs.css +32 -4
  171. package/registry/components/tabs.tsx +5 -1
  172. package/registry/components/tag.tsx +1 -1
  173. package/registry/components/task-list.css +328 -0
  174. package/registry/components/task-list.tsx +429 -0
  175. package/registry/components/textarea.tsx +4 -4
  176. package/registry/components/thinking-reasoning.css +178 -0
  177. package/registry/components/thinking-reasoning.tsx +253 -0
  178. package/registry/components/thinking-state.css +8 -0
  179. package/registry/components/thinking-state.tsx +46 -0
  180. package/registry/components/toast.tsx +1 -1
  181. package/registry/components/toggle.tsx +2 -2
  182. package/registry/components/toolbar.tsx +6 -6
  183. package/registry/components/tooltip.tsx +6 -4
  184. package/registry/examples/agent-input-demo.tsx +90 -0
  185. package/registry/examples/app-header-demo.tsx +40 -0
  186. package/registry/examples/approval-card-demo.tsx +83 -0
  187. package/registry/examples/button-demo.tsx +3 -0
  188. package/registry/examples/chart-block-demo.tsx +5 -0
  189. package/registry/examples/chart-demo.tsx +6 -21
  190. package/registry/examples/collection-filter-bar-demo.tsx +1 -1
  191. package/registry/examples/control-density-contract-demo.tsx +5 -1
  192. package/registry/examples/inline-citations-demo.tsx +31 -0
  193. package/registry/examples/input-actions-demo.tsx +36 -0
  194. package/registry/examples/menu-demo.tsx +1 -1
  195. package/registry/examples/popover-demo.tsx +1 -0
  196. package/registry/examples/rolling-count-demo.tsx +39 -0
  197. package/registry/examples/shimmer-text-demo.tsx +20 -0
  198. package/registry/examples/streaming-text-demo.tsx +28 -0
  199. package/registry/examples/task-list-demo.tsx +74 -0
  200. package/registry/examples/thinking-reasoning-demo.tsx +74 -0
  201. package/registry/examples/thinking-state-demo.tsx +13 -0
  202. package/registry/guidance/charts.LICENSE.txt +24 -0
  203. package/registry/hooks/use-controllable-state.ts +33 -0
  204. package/registry/hooks/use-listbox-navigation.ts +73 -0
  205. package/registry/hooks/use-motion-end.ts +55 -0
  206. package/registry/hooks/use-reduced-motion.ts +28 -0
  207. package/registry/index.json +1808 -190
  208. package/registry/styles/globals.css +992 -1088
  209. package/registry.json +385 -10
  210. package/schema/registry.schema.json +5 -1
  211. package/schema/tokens.schema.json +8 -4
  212. package/src/cli/file-transaction.js +15 -8
  213. package/src/cli/index.js +22 -29
  214. package/tokens/banhaten.tokens.json +490 -192
@@ -19,15 +19,43 @@ Figma reference: `Page Header`, node `1154:2816` in `Design system 3.0 Copy`.
19
19
 
20
20
  | Prop | Type | Default |
21
21
  | --- | --- | --- |
22
- | `title` | `string` | `Section Tile` |
22
+ | `title` | `ReactNode` (inline content) | `Section Tile` |
23
23
  | `metaInfo` | `string` | `Meta info` |
24
- | `description` | `string` | `Section description` |
25
- | `icon` | `ReactNode` or `false` | home icon |
24
+ | `description` | `ReactNode` (inline content) | `Section description` |
25
+ | `icon` | `ReactNode` or `false` | home icon; none in `comfortable` unless passed |
26
26
  | `breadcrumbs` | `PageHeaderBreadcrumb[]` or `false` | four `Home` items |
27
- | `actions` | `PageHeaderAction[]` or `false` | four `Button` actions |
28
- | `tabs` | `PageHeaderTabs` or `false` | underline `Tabs` |
27
+ | `actions` | `PageHeaderAction[]`, your own nodes, or `false` | four `Button` actions |
28
+ | `children` | `ReactNode` | none; supporting content under the description, such as a status badge |
29
+ | `size` | `default` or `comfortable` | `default` |
30
+ | `tabs` | `PageHeaderTabs` or `false` | underline `Tabs`; none in `comfortable` unless passed |
31
+ | `tabs.fullWidth` | `boolean` | `true`; set `false` for content-width triggers |
29
32
  | `dir` | `ltr` or `rtl` | `ltr` |
30
33
 
34
+ ### Comfortable size
35
+
36
+ `size="comfortable"` is the page heading inside an app shell whose header already carries the breadcrumbs. This is the `Page heading` frame in the ATS360 product files.
37
+
38
+ - **Spacing:** no block padding; the title and description sit `8px` apart, and actions sit `24px` from the copy and `12px` from each other.
39
+ - **Action data** render as `40px` (`density="comfortable"`) secondary Buttons.
40
+ - **Action widths:** Buttons in the actions keep a minimum width of `160px`, or `176px` for primary Buttons. Longer labels grow past it, and icon-only Buttons keep their own size.
41
+ - **Icon and tabs** appear only when you pass them.
42
+
43
+ When a page needs a primary action, pass your own nodes instead of action data. The page's Buttons keep their own variants:
44
+
45
+ ```tsx
46
+ <PageHeader
47
+ size="comfortable"
48
+ title="Requisitions"
49
+ description="Track headcount requests, budget checks, and approval ownership."
50
+ actions={<>
51
+ <Button variant="outline">Budgets</Button>
52
+ <Button>Create request</Button>
53
+ </>}
54
+ />
55
+ ```
56
+
57
+ `description` takes inline content, for example a record ID followed by its status text. `children` render under it in a wrapping row, for badges or other supporting content.
58
+
31
59
  ## Structure
32
60
 
33
61
  The implementation follows the Figma hierarchy:
@@ -36,28 +64,33 @@ The implementation follows the Figma hierarchy:
36
64
  - Title row with optional icon, title, and meta text.
37
65
  - Action group aligned to the end of the title row.
38
66
  - Optional body description below the title row.
39
- - Underline `Tabs` spanning the full header width.
67
+ - Underline `Tabs` spanning the full header width. Triggers take equal shares by default and can opt into content width with `tabs.fullWidth: false`.
68
+
69
+ The title and description each resolve their own content direction. This keeps punctuation attached to the content when its script opposes the surrounding page direction, without changing the mirrored header layout.
70
+
71
+ `PageHeader` reuses the existing `Button` and `Tabs` components instead of duplicating their behavior or visual contracts. Action descriptors render as `secondary`, default-density Buttons; PageHeader owns only their grouping and placement.
40
72
 
41
- `PageHeader` reuses the existing `Tabs` component instead of duplicating tab behavior.
73
+ Breadcrumb items retain Breadcrumbs' icon-slot contract: populated slots use the canonical icon geometry, while empty slots collapse instead of reserving space inside the header.
42
74
 
43
75
  ## Token Contract
44
76
 
45
77
  The component consumes Banhaten semantic tokens and component aliases. Do not hard-code Figma fallback literals in component source; promote any Figma value into its owned `tokens/source/` fragment as a `--bh-*` token or alias first.
46
78
 
47
- - Borders: `--bh-border-default`
48
79
  - Content: `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-heading`, `--bh-content-muted`, `--bh-content-subtle`
49
- - Interactive: `--bh-interactive-secondary-default`
50
- - Shape: `--bh-control-default`
80
+ - Actions inherit Button's secondary surface, border, typography, focus, and disabled tokens instead of redeclaring them in PageHeader.
81
+ - Breadcrumb icon visibility and geometry remain owned by Breadcrumbs.
51
82
 
52
83
  ## Pixel Rules
53
84
 
54
- - Header root is vertically padded by `20px`.
85
+ - Header root is vertically padded by `20px`; `comfortable` has no padding.
55
86
  - Breadcrumb and body text use `15px / 24px`.
56
87
  - Title uses `24px / 28px` at weight `600`.
57
88
  - Meta text uses `12px / 16px`.
58
- - Title row and action group are separated by `40px`.
59
- - Action buttons are `36px` high with an `8px` radius, subtle border, drop shadow, and inner bottom shadow.
60
- - Tabs use the existing `underline`, `md`, `fullWidth` tab treatment.
89
+ - Title row and action group are separated by `40px`; `24px` in `comfortable`.
90
+ - Title and description are `4px` apart; `8px` in `comfortable`. Actions are `8px` apart; `12px` in `comfortable`.
91
+ - Populated breadcrumb icon slots remain `20px`; empty slots collapse to `0px`.
92
+ - Actions use Button's `secondary`, `density="default"` contract (`36px` high, resolving to `sm`); PageHeader owns only action-group placement and gap.
93
+ - Tabs use the existing `underline`, `md`, `fullWidth` treatment by default. Full-width triggers keep equal shares while labels fit; both full and content-width modes preserve intrinsic label width and scroll horizontally when the row is narrower than its labels.
61
94
 
62
95
  ## Accessibility
63
96
 
@@ -65,7 +98,8 @@ The component consumes Banhaten semantic tokens and component aliases. Do not ha
65
98
  - Mark the current breadcrumb with `current: true` so it receives `aria-current="page"`.
66
99
  - Separators are decorative and hidden from assistive technology.
67
100
  - Action items render as real buttons or anchors.
68
- - Focus states use the brand token and remain visible for keyboard users.
101
+ - Action focus and disabled states come from Button; breadcrumb focus remains visible for keyboard users.
102
+ - Tab labels remain complete and non-overlapping; constrained rows scroll locally instead of truncating or painting one label over another.
69
103
 
70
104
  ## Install
71
105
 
@@ -81,7 +115,7 @@ Open the live component page or run `npx banhaten docs page-header` to inspect s
81
115
 
82
116
  ## RTL Rules
83
117
 
84
- This component has no special RTL contract beyond inherited document direction and logical spacing.
118
+ The header layout inherits document direction and uses logical spacing. The title and description independently use `dir="auto"`, so Latin copy in an RTL header (and Arabic copy in an LTR header) keeps neutral punctuation at its content end while breadcrumbs, actions, metadata, and tabs retain the header direction. Horizontal tab overflow follows that inherited direction without changing or truncating label copy.
85
119
 
86
120
  <!-- registry-contract:start -->
87
121
  ## Registry Contract
@@ -94,7 +128,14 @@ This table is generated from `registry/index.json`; edit the component-owned sou
94
128
  | `supportedExports` | `components/expanded/PageHeader.tsx: PageHeader` |
95
129
  | `parts` | `PageHeader` |
96
130
  | `registryDependencies` | `breadcrumbs`, `button`, `tabs` |
131
+ | `props.PageHeader.size` | `"default" \| "comfortable"; default "default"; comfortable is the page heading inside an app shell whose header carries the breadcrumbs: no block padding, 8px between title and description, 24px between copy and actions, 12px between actions, 40px action Buttons with a 160px minimum width (176px for primary), and no icon or tabs unless passed.` |
132
+ | `props.PageHeader.actions` | `PageHeaderAction[] \| ReactNode \| false; default "[]"; Action data renders secondary Buttons at the header density; pass your own nodes for primary actions or other controls.` |
133
+ | `props.PageHeader.description` | `ReactNode; default null; Inline description content under the title, in body-md and content/subtle.` |
134
+ | `props.PageHeader.children` | `ReactNode; default null; Supporting content under the description, such as status badges, in a wrapping row with 8px gaps.` |
135
+ | `props.PageHeader.title` | `ReactNode; default null; Heading content in heading-md semibold (24/28); inline content such as a record name and its type is allowed.` |
136
+ | `features` | `Breadcrumb-owned icon-slot visibility`, `Button-owned default-density secondary action styling`, `content-safe horizontal tab overflow`, `full-width tab opt-out`, `comfortable size for app-shell page headings`, `custom action nodes and rich descriptions` |
97
137
  | `modes` | `breadcrumbs`, `metadata`, `description`, `actions`, `tabs` |
138
+ | `sizes` | `default`, `comfortable` |
98
139
  | `states` | `default`, `without-icon`, `without-actions`, `without-tabs` |
99
- | `tokens` | `--bh-bg-raised`, `--bh-border-default`, `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-control-default`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-icon-stroke-190`, `--bh-interactive-secondary-default`, `--bh-opacity-muted`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-7xl-40`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-font-weight`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-heading-md-semibold-font-size`, `--bh-text-heading-md-semibold-font-weight`, `--bh-text-heading-md-semibold-line-height`, `--shadow-button-secondary` |
140
+ | `tokens` | `--bh-button-label-padding-x`, `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-icon-stroke-190`, `--bh-page-header-comfortable-action-min-width`, `--bh-page-header-comfortable-primary-action-min-width`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-7xl-40`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-font-weight`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-heading-md-semibold-font-size`, `--bh-text-heading-md-semibold-font-weight`, `--bh-text-heading-md-semibold-line-height` |
100
141
  <!-- registry-contract:end -->
@@ -25,7 +25,7 @@ Pagination navigates paged result sets, tables, and long lists. It supports nume
25
25
 
26
26
  ## Token Contract
27
27
 
28
- Pagination uses Banhaten control height, radius, border, background, content, focus, icon, caption, and shadow tokens. Previous/next glyphs are 18px inside 20px slots.
28
+ Pagination uses Banhaten control height, radius, border, background, content, focus, icon, caption, and elevation tokens. Previous/next glyphs are 18px inside 20px slots.
29
29
 
30
30
  ## RTL
31
31
 
@@ -66,7 +66,7 @@ This table is generated from `registry/index.json`; edit the component-owned sou
66
66
  | `parts` | `Pagination` |
67
67
  | `registryDependencies` | None |
68
68
  | `features` | `container-aware compact numeric pages`, `contained horizontal control scrolling`, `wrapping captions and summaries` |
69
- | `tokens` | `--bh-border-brand-strong`, `--bh-content-disabled`, `--bh-icon-stroke-225`, `--bh-pagination-action-gap`, `--bh-pagination-active-bg`, `--bh-pagination-active-border`, `--bh-pagination-active-content`, `--bh-pagination-caption-content`, `--bh-pagination-caption-font-weight`, `--bh-pagination-caption-width`, `--bh-pagination-control-content`, `--bh-pagination-control-gap`, `--bh-pagination-control-ghost-bg`, `--bh-pagination-control-ghost-hover`, `--bh-pagination-control-ghost-pressed`, `--bh-pagination-control-label-padding-x`, `--bh-pagination-control-label-rtl-padding-x`, `--bh-pagination-control-soft-bg`, `--bh-pagination-control-soft-hover`, `--bh-pagination-control-soft-pressed`, `--bh-pagination-control-text-gap`, `--bh-pagination-control-text-padding-x`, `--bh-pagination-control-text-rtl-gap`, `--bh-pagination-control-text-rtl-padding-x`, `--bh-pagination-font-size`, `--bh-pagination-icon-size`, `--bh-pagination-icon-slot-size`, `--bh-pagination-item-size`, `--bh-pagination-label-font-weight`, `--bh-pagination-letter-spacing`, `--bh-pagination-line-height`, `--bh-pagination-numeric-width`, `--bh-pagination-page-bg`, `--bh-pagination-page-content`, `--bh-pagination-page-gap`, `--bh-pagination-page-hover`, `--bh-pagination-page-pressed`, `--bh-pagination-radius`, `--bh-pagination-root-gap`, `--bh-pagination-simple-width`, `--bh-space-md-8`, `--shadow-pagination-active`, `--shadow-pagination-control`, `--shadow-pagination-focus` |
69
+ | `tokens` | `--bh-border-brand-strong`, `--bh-content-disabled`, `--bh-icon-stroke-225`, `--bh-pagination-action-gap`, `--bh-pagination-active-bg`, `--bh-pagination-active-border`, `--bh-pagination-active-content`, `--bh-pagination-caption-content`, `--bh-pagination-caption-font-weight`, `--bh-pagination-caption-width`, `--bh-pagination-control-content`, `--bh-pagination-control-gap`, `--bh-pagination-control-ghost-bg`, `--bh-pagination-control-ghost-hover`, `--bh-pagination-control-ghost-pressed`, `--bh-pagination-control-label-padding-x`, `--bh-pagination-control-label-rtl-padding-x`, `--bh-pagination-control-soft-bg`, `--bh-pagination-control-soft-hover`, `--bh-pagination-control-soft-pressed`, `--bh-pagination-control-text-gap`, `--bh-pagination-control-text-padding-x`, `--bh-pagination-control-text-rtl-gap`, `--bh-pagination-control-text-rtl-padding-x`, `--bh-pagination-font-size`, `--bh-pagination-icon-size`, `--bh-pagination-icon-slot-size`, `--bh-pagination-item-size`, `--bh-pagination-label-font-weight`, `--bh-pagination-letter-spacing`, `--bh-pagination-line-height`, `--bh-pagination-numeric-width`, `--bh-pagination-page-bg`, `--bh-pagination-page-content`, `--bh-pagination-page-gap`, `--bh-pagination-page-hover`, `--bh-pagination-page-pressed`, `--bh-pagination-radius`, `--bh-pagination-root-gap`, `--bh-pagination-simple-width`, `--bh-space-md-8`, `--elevation-pagination-active`, `--elevation-pagination-control`, `--elevation-pagination-focus` |
70
70
  | `types` | `numeric`, `simple`, `summary` |
71
71
  | `variants` | `soft`, `ghost` |
72
72
  <!-- registry-contract:end -->
@@ -73,5 +73,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
73
73
  | `offsets` | `tight`, `default`, `loose` |
74
74
  | `sides` | `top`, `right`, `bottom`, `left` |
75
75
  | `sizes` | `sm`, `md`, `lg`, `auto` |
76
- | `tokens` | `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-control-default`, `--bh-font-family`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-hover`, `--bh-popover-arrow-depth`, `--bh-popover-arrow-width`, `--bh-popover-bg`, `--bh-popover-body-gap`, `--bh-popover-border`, `--bh-popover-close-icon-size`, `--bh-popover-close-size`, `--bh-popover-content-gap`, `--bh-popover-footer-gap`, `--bh-popover-header-gap`, `--bh-popover-lg-width`, `--bh-popover-max-width`, `--bh-popover-md-width`, `--bh-popover-offset`, `--bh-popover-offset-default`, `--bh-popover-offset-loose`, `--bh-popover-offset-tight`, `--bh-popover-padding`, `--bh-popover-radius`, `--bh-popover-sm-width`, `--bh-popover-trigger-gap`, `--bh-radius-full`, `--bh-text-base-letter-spacing`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-z-popover`, `--shadow-button-focus`, `--shadow-popover` |
76
+ | `tokens` | `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-control-default`, `--bh-font-family`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-hover`, `--bh-popover-arrow-depth`, `--bh-popover-arrow-width`, `--bh-popover-bg`, `--bh-popover-body-gap`, `--bh-popover-border`, `--bh-popover-close-icon-size`, `--bh-popover-close-size`, `--bh-popover-content-gap`, `--bh-popover-footer-gap`, `--bh-popover-header-gap`, `--bh-popover-lg-width`, `--bh-popover-max-width`, `--bh-popover-md-width`, `--bh-popover-offset`, `--bh-popover-offset-default`, `--bh-popover-offset-loose`, `--bh-popover-offset-tight`, `--bh-popover-padding`, `--bh-popover-radius`, `--bh-popover-sm-width`, `--bh-popover-trigger-gap`, `--bh-radius-full`, `--bh-text-base-letter-spacing`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-z-popover`, `--elevation-button-focus`, `--elevation-popover` |
77
77
  <!-- registry-contract:end -->
@@ -61,6 +61,6 @@ This table is generated from `registry/index.json`; edit the component-owned sou
61
61
  | `features` | `start-anchored fill`, `grip handle`, `ticks` |
62
62
  | `sizes` | `default`, `lg` |
63
63
  | `states` | `default`, `disabled` |
64
- | `tokens` | `--bh-bg-brand-soft`, `--bh-bg-brand-subtle`, `--bh-bg-danger-soft`, `--bh-bg-danger-subtle`, `--bh-bg-neutral-soft`, `--bh-bg-neutral-subtle`, `--bh-bg-success-soft`, `--bh-bg-success-subtle`, `--bh-bg-warning-soft`, `--bh-bg-warning-subtle`, `--bh-border-brand-strong`, `--bh-border-brand-subtle`, `--bh-border-danger-strong`, `--bh-border-danger-subtle`, `--bh-border-strong`, `--bh-border-subtle`, `--bh-border-success-strong`, `--bh-border-success-subtle`, `--bh-border-warning-strong`, `--bh-border-warning-subtle`, `--bh-content-disabled`, `--bh-opacity-60`, `--bh-progress-slider-border-width-current`, `--bh-progress-slider-border-width-lg`, `--bh-progress-slider-border-width-md`, `--bh-progress-slider-fill-bg`, `--bh-progress-slider-fill-border`, `--bh-progress-slider-grip-bg`, `--bh-progress-slider-grip-height-current`, `--bh-progress-slider-grip-height-lg`, `--bh-progress-slider-grip-height-md`, `--bh-progress-slider-grip-width`, `--bh-progress-slider-handle-width-current`, `--bh-progress-slider-handle-width-lg`, `--bh-progress-slider-handle-width-md`, `--bh-progress-slider-height-current`, `--bh-progress-slider-height-lg`, `--bh-progress-slider-height-md`, `--bh-progress-slider-radius`, `--bh-progress-slider-tick-bg`, `--bh-progress-slider-tick-gap`, `--bh-progress-slider-tick-padding`, `--bh-progress-slider-tick-size`, `--bh-progress-slider-track-bg`, `--bh-progress-slider-width-current`, `--bh-progress-slider-width-lg`, `--bh-progress-slider-width-md`, `--bh-radius-full`, `--bh-z-overlay`, `--bh-z-popover`, `--bh-z-raised`, `--shadow-progress-slider-thumb-focus` |
64
+ | `tokens` | `--bh-bg-brand-soft`, `--bh-bg-brand-subtle`, `--bh-bg-danger-soft`, `--bh-bg-danger-subtle`, `--bh-bg-neutral-soft`, `--bh-bg-neutral-subtle`, `--bh-bg-success-soft`, `--bh-bg-success-subtle`, `--bh-bg-warning-soft`, `--bh-bg-warning-subtle`, `--bh-border-brand-strong`, `--bh-border-brand-subtle`, `--bh-border-danger-strong`, `--bh-border-danger-subtle`, `--bh-border-strong`, `--bh-border-subtle`, `--bh-border-success-strong`, `--bh-border-success-subtle`, `--bh-border-warning-strong`, `--bh-border-warning-subtle`, `--bh-content-disabled`, `--bh-opacity-60`, `--bh-progress-slider-border-width-current`, `--bh-progress-slider-border-width-lg`, `--bh-progress-slider-border-width-md`, `--bh-progress-slider-fill-bg`, `--bh-progress-slider-fill-border`, `--bh-progress-slider-grip-bg`, `--bh-progress-slider-grip-height-current`, `--bh-progress-slider-grip-height-lg`, `--bh-progress-slider-grip-height-md`, `--bh-progress-slider-grip-width`, `--bh-progress-slider-handle-width-current`, `--bh-progress-slider-handle-width-lg`, `--bh-progress-slider-handle-width-md`, `--bh-progress-slider-height-current`, `--bh-progress-slider-height-lg`, `--bh-progress-slider-height-md`, `--bh-progress-slider-radius`, `--bh-progress-slider-tick-bg`, `--bh-progress-slider-tick-gap`, `--bh-progress-slider-tick-padding`, `--bh-progress-slider-tick-size`, `--bh-progress-slider-track-bg`, `--bh-progress-slider-width-current`, `--bh-progress-slider-width-lg`, `--bh-progress-slider-width-md`, `--bh-radius-full`, `--bh-z-overlay`, `--bh-z-popover`, `--bh-z-raised`, `--elevation-progress-slider-thumb-focus` |
65
65
  | `tones` | `brand`, `neutral`, `success`, `warning`, `danger` |
66
66
  <!-- registry-contract:end -->
@@ -60,6 +60,6 @@ This table is generated from `registry/index.json`; edit the component-owned sou
60
60
  | `parts` | `RadioCard` |
61
61
  | `registryDependencies` | None |
62
62
  | `states` | `default`, `active`, `disabled` |
63
- | `tokens` | `--bh-bg-accent-blue-soft`, `--bh-bg-accent-purple-soft`, `--bh-bg-always-white`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-bg-raised-hover`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-180`, `--bh-icon-stroke-200`, `--bh-interactive-radiobutton-default`, `--bh-opacity-disabled-media`, `--bh-radio-card-bg`, `--bh-radio-card-border`, `--bh-radio-card-border-width`, `--bh-radio-card-border-width-active`, `--bh-radio-card-company-mark-color`, `--bh-radio-card-payment-height`, `--bh-radio-card-payment-logo-color`, `--bh-radio-card-payment-logo-height`, `--bh-radio-card-payment-logo-width`, `--bh-radio-card-payment-width`, `--bh-radio-card-width`, `--bh-radio-control-border`, `--bh-radius-full`, `--bh-radius-md-6`, `--bh-radius-xl-10`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-7xl-40`, `--bh-space-lg-10`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--shadow-component-default`, `--shadow-radio-card`, `--shadow-radio-card-current`, `--shadow-radio-card-disabled`, `--shadow-radio-control` |
63
+ | `tokens` | `--bh-bg-accent-blue-soft`, `--bh-bg-accent-purple-soft`, `--bh-bg-always-white`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-bg-raised-hover`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-180`, `--bh-icon-stroke-200`, `--bh-interactive-radiobutton-default`, `--bh-opacity-disabled-media`, `--bh-radio-card-bg`, `--bh-radio-card-border`, `--bh-radio-card-border-width`, `--bh-radio-card-border-width-active`, `--bh-radio-card-company-mark-color`, `--bh-radio-card-payment-height`, `--bh-radio-card-payment-logo-color`, `--bh-radio-card-payment-logo-height`, `--bh-radio-card-payment-logo-width`, `--bh-radio-card-payment-width`, `--bh-radio-card-width`, `--bh-radio-control-border`, `--bh-radius-full`, `--bh-radius-md-6`, `--bh-radius-xl-10`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-7xl-40`, `--bh-space-lg-10`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--elevation-component-default`, `--elevation-radio-card`, `--elevation-radio-card-current`, `--elevation-radio-card-disabled`, `--elevation-radio-control` |
64
64
  | `types` | `with-icon`, `icon-radio-right`, `payment`, `with-avatar`, `company-icon`, `icon-radio-left`, `radio-label` |
65
65
  <!-- registry-contract:end -->
@@ -68,5 +68,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
68
68
  | `controlPositions` | `start`, `end` |
69
69
  | `orientations` | `vertical`, `horizontal` |
70
70
  | `states` | `default`, `error`, `disabled` |
71
- | `tokens` | `--bh-bg-always-white`, `--bh-bg-default`, `--bh-bg-disabled`, `--bh-border-brand-strong`, `--bh-border-danger-strong`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-width-default`, `--bh-content-danger-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-interactive-radiobutton-default`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-radio-control-border`, `--bh-radio-group-message-max-width`, `--bh-radius-full`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-z-raised`, `--shadow-button-focus`, `--shadow-radio-control` |
71
+ | `tokens` | `--bh-bg-always-white`, `--bh-bg-default`, `--bh-bg-disabled`, `--bh-border-brand-strong`, `--bh-border-danger-strong`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-width-default`, `--bh-content-danger-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-interactive-radiobutton-default`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-radio-control-border`, `--bh-radio-group-message-max-width`, `--bh-radius-full`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-z-raised`, `--elevation-button-focus`, `--elevation-radio-control` |
72
72
  <!-- registry-contract:end -->
@@ -53,5 +53,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
53
53
  | `parts` | `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle` |
54
54
  | `registryDependencies` | None |
55
55
  | `features` | `horizontal panels`, `vertical panels`, `keyboard resize handle` |
56
- | `tokens` | `--bh-bg-raised`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-muted`, `--bh-radius-full`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-z-raised`, `--shadow-button-focus`, `--shadow-component-focus-ring` |
56
+ | `tokens` | `--bh-bg-raised`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-muted`, `--bh-radius-full`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-z-raised`, `--elevation-button-focus`, `--elevation-component-focus-ring` |
57
57
  <!-- registry-contract:end -->
@@ -0,0 +1,61 @@
1
+ # Rolling Count
2
+
3
+ `RollingCount` shows tabular figures where each changed character rolls into place like an odometer: the old glyph slides out as the new one slides in, up when a digit grows and down when it shrinks, with 9 to 0 continuing upward. Task List uses it for its progress counter and Approval Card for its step badge and countdown seconds.
4
+
5
+ ## API
6
+
7
+ ```tsx
8
+ import { RollingCount } from "@/components/ui/rolling-count"
9
+
10
+ <RollingCount value={`${done}/${total}`} aria-label={`${done} of ${total} done`} />
11
+ ```
12
+
13
+ | Prop | Type | Default | Description |
14
+ | --- | --- | --- | --- |
15
+ | `value` | `string` | – | Text to show. Only the characters that change roll; a value that grows a digit keeps the existing glyphs in place. |
16
+ | `aria-label` | `string` | `value` | What assistive technology reads instead of the plain value. |
17
+ | `dir` | `"ltr" \| "rtl"` | `"ltr"` | Glyph order. Stays left-to-right by default so values like 2/5 keep their order inside RTL copy. |
18
+
19
+ Any other `span` prop passes through.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npx banhaten add rolling-count
25
+ ```
26
+
27
+ ## Examples
28
+
29
+ Primary registry example: `examples/rolling-count-demo.tsx`.
30
+
31
+ ## Token Contract
32
+
33
+ Glyphs use tabular figures and inherit the surrounding type scale and color. Each roll runs on the `--bh-duration-slow` foundation duration with `--bh-easing-standard`, and a digit returns to plain text when its roll transition ends.
34
+
35
+ Covered source files: `components/rolling-count.tsx` and `components/rolling-count.css`.
36
+
37
+ ## RTL Rules
38
+
39
+ - Digits keep left-to-right order inside RTL copy, so separators such as 2/5 stay in reading order.
40
+ - The counter isolates its bidi context so it never reorders surrounding Arabic text.
41
+
42
+ ## Accessibility
43
+
44
+ Assistive technology reads the plain value, or `aria-label` when given, from a visually hidden span; the rolling glyphs are hidden from it. Under `prefers-reduced-motion` the roll collapses to an instant change.
45
+
46
+ <!-- registry-contract:start -->
47
+ ## Registry Contract
48
+
49
+ This table is generated from `registry/index.json`; edit the component-owned source under `registry/catalog/components/`, not this table or the compiled index.
50
+ `supportedExports` is the exhaustive runtime import contract grouped by source module. `parts` is an ordered, non-exhaustive anatomy list.
51
+
52
+ | Field | Values |
53
+ | --- | --- |
54
+ | `supportedExports` | `components/rolling-count.tsx: RollingCount` |
55
+ | `parts` | `RollingCount` |
56
+ | `registryDependencies` | None |
57
+ | `props.value` | `string; default null; Text to show; each changed character rolls up or down into place.` |
58
+ | `props.aria-label` | `string; default null; What assistive technology reads instead of the value, for example 2 of 5 done.` |
59
+ | `props.dir` | `"ltr" \| "rtl"; default "ltr"; Glyph order. Stays left-to-right by default so values like 2/5 keep their order inside RTL copy.` |
60
+ | `tokens` | `--bh-duration-slow`, `--bh-easing-standard` |
61
+ <!-- registry-contract:end -->
@@ -60,5 +60,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
60
60
  | `parts` | `ScrollArea`, `ScrollBar` |
61
61
  | `registryDependencies` | None |
62
62
  | `features` | `vertical scrollbar`, `horizontal scrollbar`, `two-axis scrollbar composition`, `viewport clipping`, `keyboard-focusable viewport`, `viewportProps escape hatch` |
63
- | `tokens` | `--bh-border-default`, `--bh-radius-full`, `--bh-space-lg-10`, `--bh-space-xxs-2`, `--shadow-component-focus-ring` |
63
+ | `tokens` | `--bh-border-default`, `--bh-radius-full`, `--bh-space-lg-10`, `--bh-space-xxs-2`, `--elevation-component-focus-ring` |
64
64
  <!-- registry-contract:end -->
@@ -13,6 +13,8 @@ Select is a Radix Select-backed field and menu system. It supports default and s
13
13
 
14
14
  | Prop | Type / values | Default | Notes |
15
15
  | --- | --- | --- | --- |
16
+ | `aria-label` | `string` | — | Gives a label-less trigger a stable purpose-based name. It is forwarded to the trigger, not the root. |
17
+ | `aria-labelledby` | `string` | — | Names the trigger from external text. It is forwarded to the trigger, not the root. |
16
18
  | `variant` | `default`, `soft` | `default` | Selects the trigger surface. |
17
19
  | `size` | `lg`, `md` | `lg` | Selects the trigger height. |
18
20
  | `state` | `default`, `filled`, `error`, `disabled` | `default` | Selects the visual state. |
@@ -24,11 +26,16 @@ Select is a Radix Select-backed field and menu system. It supports default and s
24
26
  | `hasTrailingIcon` | `boolean` | `false` | Set true to show the default information icon inside the trigger. |
25
27
  | `trailingIcon` | `ReactNode \| false` | `null` | A node enables the trailing slot; false always removes it. |
26
28
  | `expandIcon` | `ReactNode \| false` | `chevron-down` | Replaces the expand icon, or removes it when false. |
29
+ | `triggerProps` | `React.ComponentProps<"button">` | — | Advanced trigger attributes, spread last so matching values override top-level trigger defaults. |
27
30
 
28
31
  ## Migration
29
32
 
30
33
  Select now renders only its expand chevron when optional trigger-icon props are omitted. Add `hasLeadingIcon` or `hasTrailingIcon` to retain the previous default user or information icon. A supplied icon enables its slot, while explicit `false` always removes it.
31
34
 
35
+ ## Sizing rule
36
+
37
+ Select is `320px` wide by default (`--bh-select-width`), the width of the library Select frame. In a form grid or form card, make each field fill its column: pass `className="w-full"`, or set `--bh-select-width: 100%` on the field. Keep the default for a field that stands on its own.
38
+
32
39
  ## Token Contract
33
40
 
34
41
  Select consumes Banhaten field, trigger, menu, item, checkbox, status, avatar, payment, typography, spacing, border, radius, shadow, and focus tokens.
@@ -39,7 +46,7 @@ Values, labels, helper text, and menu item text use `text-start` and `dir="auto"
39
46
 
40
47
  ## Accessibility
41
48
 
42
- Radix provides listbox semantics, focus management, typeahead, selection state, and keyboard behavior. Use visible helper or error text for validation states.
49
+ Radix provides listbox semantics, focus management, typeahead, selection state, and keyboard behavior. Every Select needs a stable name that describes its purpose rather than its current value. Prefer a visible `label`; for a visually label-less control, pass `aria-label`, `aria-labelledby`, a text `label` with `hasLabel={false}`, or associate an external `<label>` through `triggerId`. Top-level ARIA naming props are forwarded to the trigger. `triggerProps` remains the advanced override path. Do not rely on `value` or `placeholder` as the control name. Use visible helper or error text for validation states.
43
50
 
44
51
  ## Install
45
52
 
@@ -70,6 +77,8 @@ This table is generated from `registry/index.json`; edit the component-owned sou
70
77
  | `supportedExports` | `components/select-content.tsx: SelectIcon, SelectItemAvatar, SelectItemCompanyLogo, SelectItemPaymentIcon, SelectItemStatusDot, getDefaultSelectItemMedia`, `components/select.tsx: Select, SelectCheckboxIndicator, SelectHelperText, SelectItemAvatar, SelectItemCompanyLogo, SelectItemPaymentIcon, SelectItemStatusDot, SelectLabel, SelectMenu, SelectMenuItem, SelectRoot, SelectTrigger, selectMenu, selectMenuItem, selectRoot, selectTrigger` |
71
78
  | `parts` | `Select`, `SelectRoot`, `SelectLabel`, `SelectTrigger`, `SelectHelperText`, `SelectMenu`, `SelectMenuItem`, `SelectCheckboxIndicator`, `SelectItemAvatar`, `SelectItemCompanyLogo`, `SelectItemPaymentIcon`, `SelectItemStatusDot` |
72
79
  | `registryDependencies` | None |
80
+ | `props.aria-label` | `string; default null; Provides a stable purpose-based accessible name for a label-less Select and is forwarded to its trigger.` |
81
+ | `props.aria-labelledby` | `string; default null; Names a label-less Select trigger from external text without placing the relationship on the inert root.` |
73
82
  | `props.density` | `"compact" \| "default" \| "comfortable"; default "default"; Preferred cross-component sizing contract. Overrides the legacy size prop with 32px, 36px, or 40px control heights.` |
74
83
  | `props.hasInformationIcon` | `boolean; default false; Shows an information icon beside the field label.` |
75
84
  | `props.hasLeadingIcon` | `boolean; default false; Shows the default user leading icon when no leadingIcon is supplied.` |
@@ -77,11 +86,12 @@ This table is generated from `registry/index.json`; edit the component-owned sou
77
86
  | `props.hasTrailingIcon` | `boolean; default false; Shows the default information trailing icon when no trailingIcon is supplied.` |
78
87
  | `props.trailingIcon` | `React.ReactNode \| false; default null; Supplies and enables a custom trailing icon; false always removes the trailing slot.` |
79
88
  | `props.expandIcon` | `React.ReactNode \| false; default "chevron-down"; Uses the default expand chevron when omitted, accepts a replacement node, and hides the slot when false.` |
89
+ | `props.triggerProps` | `React.ComponentProps<"button">; default null; Forwards advanced trigger attributes and is spread last, so matching attributes override the top-level trigger defaults.` |
80
90
  | `densities` | `compact`, `default`, `comfortable` |
81
91
  | `itemTypes` | `default`, `icon`, `avatar`, `company`, `payment`, `dot` |
82
92
  | `selectionTypes` | `default`, `checkbox` |
83
93
  | `sizes` | `lg`, `md`, `comfortable` |
84
94
  | `states` | `default`, `filled`, `error`, `disabled` |
85
- | `tokens` | `--bh-bg-accent-blue-default`, `--bh-bg-accent-blue-soft`, `--bh-bg-accent-purple-soft`, `--bh-bg-accent-red-default`, `--bh-bg-always-white`, `--bh-bg-brand-default`, `--bh-bg-danger-default`, `--bh-bg-default`, `--bh-bg-disabled`, `--bh-bg-info-default`, `--bh-bg-neutral-default`, `--bh-bg-success-default`, `--bh-bg-warning-default`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-accent-blue-strong`, `--bh-content-accent-purple-default`, `--bh-content-danger-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-color`, `--bh-content-subtle`, `--bh-font-weight-semibold`, `--bh-icon-size-default`, `--bh-icon-stroke-200`, `--bh-icon-stroke-220`, `--bh-interactive-checkbox-default`, `--bh-opacity-60`, `--bh-radius-full`, `--bh-select-avatar-font-size`, `--bh-select-check-color`, `--bh-select-checkbox-border`, `--bh-select-checkbox-icon-size`, `--bh-select-checkbox-radius`, `--bh-select-checkbox-size`, `--bh-select-company-icon-size`, `--bh-select-content-gap`, `--bh-select-field-gap`, `--bh-select-icon-size`, `--bh-select-icon-slot-size`, `--bh-select-item-check-offset`, `--bh-select-item-content-padding`, `--bh-select-item-gap`, `--bh-select-item-hover-bg`, `--bh-select-item-min-height`, `--bh-select-item-padding-x`, `--bh-select-item-radius`, `--bh-select-item-selected-padding-end`, `--bh-select-item-value-padding-x`, `--bh-select-label-gap`, `--bh-select-media-inset`, `--bh-select-menu-bg`, `--bh-select-menu-list-gap`, `--bh-select-menu-offset`, `--bh-select-menu-padding-y`, `--bh-select-menu-radius`, `--bh-select-menu-width`, `--bh-select-payment-height`, `--bh-select-payment-mark-offset`, `--bh-select-payment-mark-size`, `--bh-select-payment-radius`, `--bh-select-payment-width`, `--bh-select-status-dot-size`, `--bh-select-trigger-bg`, `--bh-select-trigger-bg-disabled`, `--bh-select-trigger-bg-soft`, `--bh-select-trigger-border`, `--bh-select-trigger-border-current`, `--bh-select-trigger-border-disabled`, `--bh-select-trigger-border-error`, `--bh-select-trigger-border-focus`, `--bh-select-trigger-gap`, `--bh-select-trigger-icon-color`, `--bh-select-trigger-padding-x`, `--bh-select-trigger-padding-y-comfortable`, `--bh-select-trigger-padding-y-lg`, `--bh-select-trigger-padding-y-md`, `--bh-select-trigger-radius`, `--bh-select-value-padding-inline`, `--bh-select-width`, `--bh-space-md-8`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-font-weight`, `--bh-text-body-2xs-regular-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-xs-medium-font-size`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-medium-letter-spacing`, `--bh-text-body-xs-medium-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-z-popover`, `--shadow-component-default`, `--shadow-select-checkbox`, `--shadow-select-danger-focus-ring`, `--shadow-select-focus-ring`, `--shadow-select-menu`, `--shadow-select-trigger` |
95
+ | `tokens` | `--bh-bg-accent-blue-default`, `--bh-bg-accent-blue-soft`, `--bh-bg-accent-purple-soft`, `--bh-bg-accent-red-default`, `--bh-bg-always-white`, `--bh-bg-brand-default`, `--bh-bg-danger-default`, `--bh-bg-default`, `--bh-bg-disabled`, `--bh-bg-info-default`, `--bh-bg-neutral-default`, `--bh-bg-success-default`, `--bh-bg-warning-default`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-accent-blue-strong`, `--bh-content-accent-purple-default`, `--bh-content-danger-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-font-weight-semibold`, `--bh-icon-size-default`, `--bh-icon-stroke-200`, `--bh-icon-stroke-220`, `--bh-interactive-checkbox-content`, `--bh-interactive-checkbox-default`, `--bh-opacity-60`, `--bh-radius-full`, `--bh-select-avatar-font-size`, `--bh-select-check-color`, `--bh-select-checkbox-border`, `--bh-select-checkbox-icon-size`, `--bh-select-checkbox-radius`, `--bh-select-checkbox-size`, `--bh-select-company-icon-size`, `--bh-select-content-gap`, `--bh-select-field-gap`, `--bh-select-icon-size`, `--bh-select-icon-slot-size`, `--bh-select-item-check-offset`, `--bh-select-item-content-padding`, `--bh-select-item-gap`, `--bh-select-item-hover-bg`, `--bh-select-item-min-height`, `--bh-select-item-padding-x`, `--bh-select-item-radius`, `--bh-select-item-selected-padding-end`, `--bh-select-item-value-padding-x`, `--bh-select-label-gap`, `--bh-select-media-inset`, `--bh-select-menu-bg`, `--bh-select-menu-list-gap`, `--bh-select-menu-offset`, `--bh-select-menu-padding-y`, `--bh-select-menu-radius`, `--bh-select-menu-width`, `--bh-select-payment-height`, `--bh-select-payment-mark-offset`, `--bh-select-payment-mark-size`, `--bh-select-payment-radius`, `--bh-select-payment-width`, `--bh-select-status-dot-size`, `--bh-select-trigger-bg`, `--bh-select-trigger-bg-disabled`, `--bh-select-trigger-bg-soft`, `--bh-select-trigger-border`, `--bh-select-trigger-border-current`, `--bh-select-trigger-border-disabled`, `--bh-select-trigger-border-error`, `--bh-select-trigger-border-focus`, `--bh-select-trigger-gap`, `--bh-select-trigger-icon-color`, `--bh-select-trigger-padding-x`, `--bh-select-trigger-padding-y-comfortable`, `--bh-select-trigger-padding-y-lg`, `--bh-select-trigger-padding-y-md`, `--bh-select-trigger-radius`, `--bh-select-value-padding-inline`, `--bh-select-width`, `--bh-space-md-8`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-font-weight`, `--bh-text-body-2xs-regular-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-xs-medium-font-size`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-medium-letter-spacing`, `--bh-text-body-xs-medium-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-z-popover`, `--elevation-component-default`, `--elevation-select-checkbox`, `--elevation-select-danger-focus-ring`, `--elevation-select-focus-ring`, `--elevation-select-menu`, `--elevation-select-trigger` |
86
96
  | `variants` | `default`, `soft` |
87
97
  <!-- registry-contract:end -->
@@ -78,5 +78,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
78
78
  | `features` | `focus trap`, `outside dismissal`, `Escape dismissal`, `logical sides`, `scrollable body`, `footer actions` |
79
79
  | `sides` | `start`, `end`, `top`, `bottom` |
80
80
  | `sizes` | `sm`, `default`, `lg` |
81
- | `tokens` | `--bh-bg-backdrop`, `--bh-bg-raised`, `--bh-border-subtle`, `--bh-content-default`, `--bh-content-subtle`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-hover`, `--bh-radius-2xl-12`, `--bh-radius-full`, `--bh-space-12xl-128`, `--bh-space-16xl-256`, `--bh-space-17xl-288`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-8xl-48`, `--bh-space-9xl-64`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-xs-4`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--bh-text-heading-sm-semibold-font-size`, `--bh-text-heading-sm-semibold-font-weight`, `--bh-text-heading-sm-semibold-letter-spacing`, `--bh-text-heading-sm-semibold-line-height`, `--bh-z-overlay`, `--shadow-button-focus`, `--shadow-component-default` |
81
+ | `tokens` | `--bh-bg-backdrop`, `--bh-bg-raised`, `--bh-border-subtle`, `--bh-content-default`, `--bh-content-subtle`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-hover`, `--bh-radius-2xl-12`, `--bh-radius-full`, `--bh-space-12xl-128`, `--bh-space-16xl-256`, `--bh-space-17xl-288`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-8xl-48`, `--bh-space-9xl-64`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-xs-4`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--bh-text-heading-sm-semibold-font-size`, `--bh-text-heading-sm-semibold-font-weight`, `--bh-text-heading-sm-semibold-letter-spacing`, `--bh-text-heading-sm-semibold-line-height`, `--bh-z-overlay`, `--elevation-button-focus`, `--elevation-component-default` |
82
82
  <!-- registry-contract:end -->
@@ -0,0 +1,67 @@
1
+ # Shimmer Text
2
+
3
+ `ShimmerText` is text with a soft brightness valley sweeping through it, for labels of work in progress such as a sync that is running or a file being read. The AI conversation components share it: the Thinking State label, the Thinking Reasoning header, the active Task List row, and the Agent Input while it enhances a prompt all use it, so they move the same way.
4
+
5
+ Use it to mark a moving part inside ordinary copy. It is presentational; announce progress through your own semantics or through Thinking State.
6
+
7
+ ## API
8
+
9
+ ```tsx
10
+ import { ShimmerText } from "@/components/ui/shimmer-text"
11
+
12
+ <p>
13
+ Searching the workspace for <ShimmerText tone="default">billing/invoices.ts</ShimmerText>
14
+ </p>
15
+
16
+ <ShimmerText>Reading 12 files</ShimmerText>
17
+ <ShimmerText animated={false}>Read 12 files</ShimmerText>
18
+ ```
19
+
20
+ | Prop | Type | Default | Description |
21
+ | --- | --- | --- | --- |
22
+ | `animated` | `boolean` | `true` | Runs the brightness sweep. Set false to keep the same colors without motion. |
23
+ | `tone` | `"subtle" \| "default"` | `"subtle"` | `subtle` reads as secondary copy; `default` matches primary text. |
24
+
25
+ Any other `span` prop passes through. The `data-slot="shimmer-text"` attribute is what the styles key on, so it cannot be overridden; wrap the primitive when you need your own hook.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npx banhaten add shimmer-text
31
+ ```
32
+
33
+ ## Examples
34
+
35
+ Primary registry example: `examples/shimmer-text-demo.tsx`.
36
+
37
+ ## Token Contract
38
+
39
+ The shimmer is built from existing content tokens: the base color is `--bh-content-subtle` (or `--bh-content-default` for the `default` tone) and the passing valley is `--bh-content-disabled` (or `--bh-content-subtle`). The sweep runs on `--bh-shimmer-text-duration` with the `--bh-easing-standard` foundation easing. Text size and weight are inherited.
40
+
41
+ Covered source files: `components/shimmer-text.tsx` and `components/shimmer-text.css`.
42
+
43
+ ## RTL Rules
44
+
45
+ - The text inherits document direction.
46
+ - The shine sweeps from the reading start to the reading end, so RTL text uses mirrored keyframes automatically.
47
+
48
+ ## Accessibility
49
+
50
+ The glyphs stay real text, so screen readers read them normally. The shimmer is purely decorative: under `prefers-reduced-motion` it renders as static text in the base color with no gradient, and the resting color keeps the base text contrast.
51
+
52
+ <!-- registry-contract:start -->
53
+ ## Registry Contract
54
+
55
+ This table is generated from `registry/index.json`; edit the component-owned source under `registry/catalog/components/`, not this table or the compiled index.
56
+ `supportedExports` is the exhaustive runtime import contract grouped by source module. `parts` is an ordered, non-exhaustive anatomy list.
57
+
58
+ | Field | Values |
59
+ | --- | --- |
60
+ | `supportedExports` | `components/shimmer-text.tsx: ShimmerText` |
61
+ | `parts` | `ShimmerText` |
62
+ | `registryDependencies` | None |
63
+ | `props.animated` | `boolean; default "true"; Runs the brightness sweep; set false to keep the same colors without motion.` |
64
+ | `props.tone` | `"subtle" \| "default"; default "subtle"; Secondary or primary text color pair for the sweep.` |
65
+ | `tokens` | `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-easing-standard`, `--bh-shimmer-text-duration` |
66
+ | `tones` | `subtle`, `default` |
67
+ <!-- registry-contract:end -->
@@ -1,6 +1,6 @@
1
1
  # Sidebar
2
2
 
3
- Sidebar is the complete Figma-aligned application navigation system. It provides a 280px expanded shell and 48px icon rail, brand and workspace headers, grouped and nested menus, tree navigation, badges and actions, user footers, tooltips, controlled state, and logical RTL placement.
3
+ Sidebar is the complete Figma-aligned application navigation system. It provides a 900px minimum height, 280px expanded shell and 48px icon rail, staged expand/collapse motion, brand and workspace headers, grouped and nested menus, tree navigation, badges and actions, user footers with hover identity previews, tooltips, controlled state, and logical RTL placement.
4
4
 
5
5
  All sidebar parts render inside `SidebarProvider`. The provider owns controlled or uncontrolled collapse state and supplies the tooltip context used by icon-only menu rows.
6
6
 
@@ -48,7 +48,7 @@ All sidebar parts render inside `SidebarProvider`. The provider owns controlled
48
48
  | `SidebarHeader`, `SidebarBody`, `SidebarFooter` | layout regions |
49
49
  | `SidebarHeaderBrand`, `SidebarHeaderWorkspaceSwitch` | brand and workspace header compositions |
50
50
  | `SidebarHeaderAction` | compact search or custom header action |
51
- | `SidebarFooterUser` | supporting-text or bordered user switcher |
51
+ | `SidebarFooterUser` | supporting-text, bordered, or inset card user switcher with a collapsed hover/focus identity preview |
52
52
  | `SidebarGroup`, `SidebarGroupLabel`, `SidebarGroupAction` | grouped navigation, disclosure, and actions |
53
53
  | `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton` | default and avatar menu structure with optional slots |
54
54
  | `SidebarMenuBadge` | neutral count badge for a menu row |
@@ -62,9 +62,69 @@ All sidebar parts render inside `SidebarProvider`. The provider owns controlled
62
62
 
63
63
  Use `SidebarHeaderBrand` for product navigation. Use `SidebarHeaderWorkspaceSwitch` with `variant="actions"` for the compact search/collapse header or `variant="card"` for the bordered workspace card.
64
64
 
65
+ Brand and workspace headers keep a visible `SidebarTrigger` beneath their identity control while the rail is collapsed, so the same header can always expand again. Pass `collapseAction={false}` only when another persistent expand control is present.
66
+
67
+ When the rail is collapsed, `SidebarFooterUser` reveals the user's name and supporting text beside the avatar on hover or keyboard focus. Set `showIdentityPreview={false}` to disable that behavior. To make the row selectable, wrap it with `MenuRoot` and `MenuTrigger asChild`, then render account actions in a portalled `MenuContent`; the live playground example demonstrates this composition.
68
+
69
+ ## Sizes
70
+
71
+ `SidebarProvider` accepts `size="default" | "comfortable"`. The default size is the 32px Figma menu-item set. The comfortable size matches application shells that use 40px navigation rows:
72
+
73
+ | | `default` | `comfortable` |
74
+ | --- | --- | --- |
75
+ | Menu row height | 32px | 40px (`--bh-sidebar-comfortable-item-height`) |
76
+ | Gap between rows | 0 | 8px (`--bh-sidebar-comfortable-item-gap`) |
77
+ | Row label | body-sm 14/20 | body-md 15/24 |
78
+ | Icon to label | 6px gap + 4px label padding | 6px gap + 2px label padding |
79
+ | Header, body and footer inline padding | 10px / 12px / 10px | 16px (`--bh-sidebar-comfortable-padding`) |
80
+ | Rail edge | 1px end border | 1px stroke inside all four sides, an outline inset by its width (`--bh-sidebar-border`) |
81
+ | Header to first menu, between body sections, and above the footer | header and footer padding | 24px (`--bh-sidebar-comfortable-section-gap`) |
82
+ | Brand name | body-xl semibold 20/30 | heading-md semibold 24/28 |
83
+ | Group label row | 24px with 4px bottom padding, 12px/6px group padding | 20px, no group padding, 8px to the first row |
84
+
85
+ Comfortable rules apply only while the sidebar is expanded; the 48px collapsed rail is identical at both sizes. In comfortable sidebars the body is a flex column, so a section can be pinned to the bottom with `className="mt-auto"`.
86
+
87
+ ```tsx
88
+ <SidebarProvider size="comfortable">
89
+ <Sidebar>
90
+ <SidebarHeader>
91
+ <SidebarHeaderBrand
92
+ brand="ATS360"
93
+ collapseAction={false}
94
+ logo={false}
95
+ searchAction={false}
96
+ subtitle="Recruiting"
97
+ />
98
+ </SidebarHeader>
99
+ <SidebarBody>
100
+ <SidebarMenu>{/* 40px rows */}</SidebarMenu>
101
+ <SidebarGroup className="mt-auto">
102
+ <SidebarGroupLabel>WORKSPACE</SidebarGroupLabel>
103
+ <SidebarMenu>{/* label-only rows */}</SidebarMenu>
104
+ </SidebarGroup>
105
+ </SidebarBody>
106
+ <SidebarFooter divider={false}>
107
+ <SidebarFooterUser name="Omar Ali" supportText="Talent acquisition" variant="card" />
108
+ </SidebarFooter>
109
+ </Sidebar>
110
+ </SidebarProvider>
111
+ ```
112
+
113
+ `SidebarHeaderBrand` takes `logo={false}` for a text-only brand and `subtitle` for a stacked product or module line in body-sm `content-subtle`. A collapsed text-only brand shows only its header actions.
114
+
115
+ `SidebarFooterUser variant="card"` renders the signed-in user as an inset `--bh-sidebar-user-card-bg` block with 8px padding, a body-md medium name and a body-sm subtle role. It shows no avatar or chevron unless `avatar` or `trailingIcon` is passed. Any footer variant accepts `avatar={false}` and `trailingIcon={false}` to hide those slots while expanded. Pair the card with `SidebarFooter divider={false}` to drop the top rule.
116
+
117
+ Group label text uses `--bh-sidebar-group-label-content`, which defaults to `--bh-content-muted`. A product theme can point it at `--bh-content-subtle` without restyling the label.
118
+
65
119
  ## Token Contract
66
120
 
67
- Sidebar uses generated `--bh-sidebar-*` tokens for expanded and collapsed widths, row heights, header actions, compact controls, workspace cards, tree indentation, padding, backgrounds, borders, and menu states. Expanded and collapsed navigation share the same 18px glyph in a centered 20px slot. Do not replace these dimensions with local literals in product compositions.
121
+ Sidebar uses generated `--bh-sidebar-*` tokens for its 900px minimum height, expanded and collapsed widths, row heights, header actions, compact controls, workspace cards, tree indentation, user-preview placement, motion duration and easing, padding, backgrounds, borders, and menu states. Expanded and collapsed navigation share the same 18px glyph in a centered 20px slot. Do not replace these dimensions with local literals in product compositions.
122
+
123
+ ## Motion
124
+
125
+ The shell, header, menu rows, nested lists, and footer share one tokenized choreography. The width boundary uses `--bh-sidebar-motion-duration` with `--bh-sidebar-motion-easing`; labels and secondary actions use the faster `--bh-sidebar-motion-duration-content`. Content exits as collapse begins and returns after `--bh-sidebar-motion-delay-content`, while icons stay on the logical-start track instead of jumping to the center of the still-expanded shell.
126
+
127
+ Nested menus and group labels contract through layout rather than unmounting on the first frame. The rail remains clipped until its width transition finishes, then permits the footer identity preview to extend outside the shell. The global reduced-motion rule and component-level `motion-reduce` variants remove the choreography when the user requests less motion.
68
128
 
69
129
  ## RTL
70
130
 
@@ -74,7 +134,7 @@ Use `dir="rtl"` on `SidebarProvider` and `side="start"` or `side="end"` for logi
74
134
 
75
135
  Provide a text child or explicit `aria-label` for every menu button. Collapsed rows reuse that label in a tooltip. Active menu and tree rows expose `aria-current="page"`; expandable tree rows expose `aria-expanded`; `SidebarToggle` announces both its action and pressed collapse state.
76
136
 
77
- Level 2 rows, group labels, and tree groups unmount while collapsed so keyboard users do not encounter invisible controls. Use native links through `asChild` when a row navigates, and keep button rendering for in-place actions.
137
+ Level 2 rows, group labels, and tree groups remain mounted long enough to animate, but become `aria-hidden` and inert while collapsed so keyboard and assistive-technology users do not encounter invisible controls. Use native links through `asChild` when a row navigates, and keep button rendering for in-place actions.
78
138
 
79
139
  ## Install
80
140
 
@@ -108,8 +168,15 @@ This table is generated from `registry/index.json`; edit the component-owned sou
108
168
  | `registryDependencies` | `tooltip` |
109
169
  | `props.collapsed` | `boolean; default false; Controls the provider-owned 280px expanded or 48px rail state. Pair with onCollapsedChange for controlled usage.` |
110
170
  | `props.defaultCollapsed` | `boolean; default false; Sets the initial rail state when SidebarProvider owns collapse state internally.` |
171
+ | `props.size` | `"default" \| "comfortable"; default "default"; Set on SidebarProvider. comfortable uses 40px rows 8px apart with body-md labels, 16px shell padding, a 24px section gap, a heading-md brand, and 20px group labels; the collapsed rail is unchanged.` |
172
+ | `props.subtitle` | `React.ReactNode; default "undefined"; Adds a body-sm content-subtle line under the SidebarHeaderBrand name and switches the brand to a stacked layout.` |
173
+ | `props.logo` | `React.ReactNode \| false; default "Sparkles glyph"; Replaces the SidebarHeaderBrand mark; false renders a text-only brand with no logo slot.` |
174
+ | `props.divider` | `boolean; default true; Draws the SidebarFooter top rule; false removes it for inset footer cards.` |
175
+ | `props.avatar` | `React.ReactNode \| false; default "person glyph"; Replaces the SidebarFooterUser avatar; false hides the slot while expanded. The collapsed rail always shows a glyph.` |
176
+ | `props.trailingIcon` | `React.ReactNode \| false; default "part-specific"; Trailing glyph on menu rows and the footer user; false hides the footer user's chevron.` |
177
+ | `props.showIdentityPreview` | `boolean; default true; Reveals the collapsed footer user's name and supporting text beside the rail on hover or keyboard focus.` |
111
178
  | `props.side` | `"start" \| "end"; default "start"; Places Sidebar on the logical start or end edge and selects the matching logical border.` |
112
- | `props.variant` | `SidebarMenuButtonVariant \| SidebarWorkspaceVariant \| "default" \| "supporting-text"; default "part-specific"; Selects default/avatar/nested menu rows, action/card workspace headers, or default/supporting-text footers on the part that owns it.` |
179
+ | `props.variant` | `SidebarMenuButtonVariant \| SidebarWorkspaceVariant \| "default" \| "supporting-text" \| "card"; default "part-specific"; Selects default/avatar/nested menu rows, action/card workspace headers, or default/supporting-text/card footers on the part that owns it. The card footer is an inset bg-subtle block with a body-md name and body-sm role and no avatar or chevron unless passed.` |
113
180
  | `props.active` | `boolean; default false; Applies the selected menu or tree treatment and exposes aria-current="page".` |
114
181
  | `props.state` | `"default" \| "hover" \| "active" \| "focus"; default "default"; Deterministic interaction state for documentation matrices; product hover and focus remain native CSS interactions.` |
115
182
  | `props.showLine` | `boolean; default false; Selects the neutral/brand line-guided Level 2 treatment on SidebarMenuSubButton.` |
@@ -117,9 +184,9 @@ This table is generated from `registry/index.json`; edit the component-owned sou
117
184
  | `props.expandable` | `boolean; default false; Adds a disclosure affordance to group labels or tree-group headings.` |
118
185
  | `props.asChild` | `boolean; default false; Renders menu, tree, or inset content through Radix Slot while preserving sidebar layout and semantics.` |
119
186
  | `directions` | `ltr`, `rtl` |
120
- | `features` | `280px expanded and 48px collapsed shells`, `controlled collapse`, `uncontrolled collapse`, `brand header with search and collapse actions`, `workspace action and card headers`, `default, avatar, Level 2, and Level 2 line menu rows`, `supporting text, count badge, and trailing action slots`, `group labels with disclosure and icon actions`, `four-level tree navigation`, `supporting-text and bordered user footers`, `icon-only rail with tooltips`, `logical side placement and RTL mirroring` |
187
+ | `features` | `280px expanded and 48px collapsed shells`, `comfortable size with 40px rows, 8px row gap, and body-md labels`, `controlled collapse`, `uncontrolled collapse`, `brand header with search and collapse actions`, `text-only brand with a stacked subtitle`, `workspace action and card headers`, `visible collapsed-header expand action`, `default, avatar, Level 2, and Level 2 line menu rows`, `supporting text, count badge, and trailing action slots`, `group labels with disclosure, icon actions, and a tokenized label color`, `four-level tree navigation`, `supporting-text, bordered, and inset card user footers`, `divider-free footer`, `collapsed footer identity preview on hover and keyboard focus`, `tokenized staged expand and collapse motion`, `reduced-motion fallback`, `icon-only rail with tooltips`, `logical side placement and RTL mirroring` |
121
188
  | `sides` | `start`, `end` |
122
189
  | `states` | `default`, `hover`, `active`, `focus`, `expanded`, `collapsed` |
123
- | `tokens` | `--bh-bg-brand-default`, `--bh-bg-default`, `--bh-bg-default-hover`, `--bh-bg-neutral-subtle`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-border-width-strong`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-control-sm`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-active`, `--bh-interactive-ghost-default`, `--bh-interactive-ghost-hover`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-sidebar-active-guide-height`, `--bh-sidebar-bg`, `--bh-sidebar-bg-collapsed`, `--bh-sidebar-border`, `--bh-sidebar-brand-height`, `--bh-sidebar-compact-control-size`, `--bh-sidebar-header-action-size`, `--bh-sidebar-icon-size`, `--bh-sidebar-icon-size-collapsed`, `--bh-sidebar-icon-slot-size`, `--bh-sidebar-item-bg-active`, `--bh-sidebar-item-bg-hover`, `--bh-sidebar-item-content`, `--bh-sidebar-item-content-active`, `--bh-sidebar-item-height`, `--bh-sidebar-item-height-collapsed`, `--bh-sidebar-item-padding-x`, `--bh-sidebar-level-2-indent-width`, `--bh-sidebar-padding`, `--bh-sidebar-rail-width`, `--bh-sidebar-tree-indent-step`, `--bh-sidebar-tree-item-height`, `--bh-sidebar-width`, `--bh-sidebar-width-collapsed`, `--bh-sidebar-workspace-card-height`, `--bh-sidebar-workspace-trigger-max-width`, `--bh-sidebar-workspace-trigger-width`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-8xl-48`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-font-weight`, `--bh-text-body-2xs-regular-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-3xs-semibold-font-size`, `--bh-text-body-3xs-semibold-font-weight`, `--bh-text-body-3xs-semibold-letter-spacing`, `--bh-text-body-3xs-semibold-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xl-semibold-font-size`, `--bh-text-body-xl-semibold-font-weight`, `--bh-text-body-xl-semibold-letter-spacing`, `--bh-text-body-xl-semibold-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-text-body-xs-semibold-font-weight`, `--shadow-button-focus` |
124
- | `variants` | `default menu`, `avatar menu`, `Level 2 menu`, `Level 2 line menu`, `workspace actions`, `workspace card`, `default footer`, `supporting-text footer` |
190
+ | `tokens` | `--bh-bg-brand-default`, `--bh-bg-default`, `--bh-bg-default-hover`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-border-width-strong`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-control-sm`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-active`, `--bh-interactive-ghost-default`, `--bh-interactive-ghost-hover`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-radius-xl-10`, `--bh-sidebar-active-guide-height`, `--bh-sidebar-bg`, `--bh-sidebar-bg-collapsed`, `--bh-sidebar-border`, `--bh-sidebar-brand-height`, `--bh-sidebar-comfortable-brand-padding`, `--bh-sidebar-comfortable-group-label-height`, `--bh-sidebar-comfortable-item-gap`, `--bh-sidebar-comfortable-item-height`, `--bh-sidebar-comfortable-label-padding`, `--bh-sidebar-comfortable-padding`, `--bh-sidebar-comfortable-section-gap`, `--bh-sidebar-compact-control-size`, `--bh-sidebar-group-label-content`, `--bh-sidebar-header-action-size`, `--bh-sidebar-icon-size`, `--bh-sidebar-icon-size-collapsed`, `--bh-sidebar-icon-slot-size`, `--bh-sidebar-item-bg-active`, `--bh-sidebar-item-bg-hover`, `--bh-sidebar-item-content`, `--bh-sidebar-item-content-active`, `--bh-sidebar-item-height`, `--bh-sidebar-item-height-collapsed`, `--bh-sidebar-item-padding-x`, `--bh-sidebar-level-2-indent-width`, `--bh-sidebar-min-height`, `--bh-sidebar-motion-delay-content`, `--bh-sidebar-motion-duration`, `--bh-sidebar-motion-duration-content`, `--bh-sidebar-motion-easing`, `--bh-sidebar-padding`, `--bh-sidebar-rail-width`, `--bh-sidebar-tree-indent-step`, `--bh-sidebar-tree-item-height`, `--bh-sidebar-user-card-bg`, `--bh-sidebar-user-card-bg-hover`, `--bh-sidebar-user-preview-gap`, `--bh-sidebar-user-preview-height`, `--bh-sidebar-user-preview-width`, `--bh-sidebar-width`, `--bh-sidebar-width-collapsed`, `--bh-sidebar-workspace-card-height`, `--bh-sidebar-workspace-trigger-max-width`, `--bh-sidebar-workspace-trigger-width`, `--bh-space-10xl-80`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-8xl-48`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-font-weight`, `--bh-text-body-2xs-regular-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-3xs-semibold-font-size`, `--bh-text-body-3xs-semibold-font-weight`, `--bh-text-body-3xs-semibold-letter-spacing`, `--bh-text-body-3xs-semibold-line-height`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xl-semibold-font-size`, `--bh-text-body-xl-semibold-font-weight`, `--bh-text-body-xl-semibold-letter-spacing`, `--bh-text-body-xl-semibold-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-text-body-xs-semibold-font-weight`, `--bh-text-heading-md-semibold-font-size`, `--bh-text-heading-md-semibold-font-weight`, `--bh-text-heading-md-semibold-letter-spacing`, `--bh-text-heading-md-semibold-line-height`, `--bh-z-overlay`, `--elevation-button-focus`, `--elevation-component-default` |
191
+ | `variants` | `default menu`, `avatar menu`, `Level 2 menu`, `Level 2 line menu`, `workspace actions`, `workspace card`, `default footer`, `supporting-text footer`, `card footer`, `comfortable size` |
125
192
  <!-- registry-contract:end -->