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
@@ -49,7 +49,7 @@ const Menubar = React.forwardRef<
49
49
  data-slot="menubar"
50
50
  ref={ref}
51
51
  className={cn(
52
- "flex h-[var(--bh-space-7xl-40)] items-center gap-[var(--bh-space-xs-4)] rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-xs-4)] shadow-[var(--shadow-component-default)]",
52
+ "flex h-[var(--bh-space-7xl-40)] items-center gap-[var(--bh-space-xs-4)] rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-xs-4)] shadow-[var(--elevation-component-default)]",
53
53
  className
54
54
  )}
55
55
  {...props}
@@ -75,7 +75,7 @@ const MenubarTrigger = React.forwardRef<
75
75
  })
76
76
 
77
77
  const menuContentClassName =
78
- "z-[var(--bh-z-popover)] min-w-[var(--bh-menu-width)] overflow-hidden rounded-[var(--bh-radius-xl-10)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-xs-4)] text-[var(--bh-content-default)] shadow-[var(--shadow-menu)]"
78
+ "z-[var(--bh-z-popover)] min-w-[var(--bh-menu-width)] overflow-hidden rounded-[var(--bh-radius-xl-10)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-xs-4)] text-[var(--bh-content-default)] shadow-[var(--elevation-menu)]"
79
79
 
80
80
  const menuItemClassName =
81
81
  "relative flex min-h-[var(--bh-menu-item-default-height)] cursor-default select-none items-center gap-[var(--bh-space-md-8)] rounded-[var(--bh-radius-md-6)] px-[var(--bh-space-md-8)] py-[var(--bh-space-sm-6)] text-start text-[length:var(--bh-text-body-sm-regular-font-size)] font-[var(--bh-text-body-sm-regular-font-weight)] leading-[var(--bh-text-body-sm-regular-line-height)] tracking-[var(--bh-text-body-sm-regular-letter-spacing)] text-[var(--bh-content-default)] outline-none data-[disabled]:pointer-events-none data-[disabled]:text-[var(--bh-content-disabled)] data-[highlighted]:bg-[var(--bh-menu-item-hover-bg)]"
@@ -38,8 +38,8 @@ type ConfirmModalMessages = {
38
38
  }
39
39
 
40
40
  const modalSurfaceBaseClassName = [
41
- "flex max-h-[calc(100vh-(var(--bh-space-8xl-48)*2))] w-[var(--bh-modal-width)] max-w-[calc(100vw-(var(--bh-space-4xl-20)*2))] flex-col overflow-hidden rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--shadow-modal)] outline-none",
42
- "[--bh-modal-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-modal-border),var(--shadow-2xl)]",
41
+ "flex max-h-[calc(100vh-(var(--bh-space-8xl-48)*2))] w-[var(--bh-modal-width)] max-w-[calc(100vw-(var(--bh-space-4xl-20)*2))] flex-col overflow-hidden rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--elevation-modal)] outline-none",
42
+ "[--bh-modal-border:var(--bh-border-subtle)] [--elevation-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-modal-border),var(--elevation-06)]",
43
43
  ]
44
44
 
45
45
  const modalSizeVariants = {
@@ -334,7 +334,7 @@ const ModalCloseButtonSurface = React.forwardRef<
334
334
  ref={ref}
335
335
  type={type}
336
336
  className={cn(
337
- "absolute end-[var(--bh-space-3xl-16)] top-1/2 inline-flex size-[var(--bh-space-6xl-32)] -translate-y-1/2 items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-content-subtle)] outline-none transition-[background-color,box-shadow,color] hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--shadow-button-focus)]",
337
+ "absolute end-[var(--bh-space-3xl-16)] top-1/2 inline-flex size-[var(--bh-space-6xl-32)] -translate-y-1/2 items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-content-subtle)] outline-none transition-[background-color,box-shadow,color] hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--elevation-button-focus)]",
338
338
  className
339
339
  )}
340
340
  {...props}
@@ -46,9 +46,9 @@ const nativeSelectRootVariants = cva(
46
46
  const nativeSelectControlVariants = cva(
47
47
  [
48
48
  "relative grid w-full rounded-[var(--bh-select-trigger-radius)]",
49
- "[--bh-select-trigger-border-current:var(--bh-select-trigger-border)] [--shadow-select-trigger:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-current,var(--bh-select-trigger-border)),var(--shadow-component-default)]",
50
- "bg-[var(--bh-select-trigger-bg)] shadow-[var(--shadow-select-trigger)] transition-[background-color,box-shadow]",
51
- "focus-within:[--bh-select-trigger-border-current:var(--bh-select-trigger-border-focus)] focus-within:shadow-[var(--shadow-select-focus-ring)]",
49
+ "[--bh-select-trigger-border-current:var(--bh-select-trigger-border)] [--elevation-select-trigger:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-current,var(--bh-select-trigger-border)),var(--elevation-component-default)]",
50
+ "bg-[var(--bh-select-trigger-bg)] shadow-[var(--elevation-select-trigger)] transition-[background-color,box-shadow]",
51
+ "focus-within:[--bh-select-trigger-border-current:var(--bh-select-trigger-border-focus)] focus-within:shadow-[var(--elevation-select-focus-ring)]",
52
52
  ],
53
53
  {
54
54
  variants: {
@@ -60,7 +60,7 @@ const nativeSelectControlVariants = cva(
60
60
  default: "",
61
61
  filled: "",
62
62
  error:
63
- "[--bh-select-trigger-border-current:var(--bh-select-trigger-border-error)] shadow-[var(--shadow-select-trigger)] focus-within:shadow-[var(--shadow-select-danger-focus-ring)]",
63
+ "[--bh-select-trigger-border-current:var(--bh-select-trigger-border-error)] shadow-[var(--elevation-select-trigger)] focus-within:shadow-[var(--elevation-select-danger-focus-ring)]",
64
64
  disabled:
65
65
  "pointer-events-none bg-[var(--bh-select-trigger-bg-disabled)] shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-disabled)]",
66
66
  },
@@ -90,7 +90,7 @@ const NavigationMenuContent = React.forwardRef<
90
90
  data-slot="navigation-menu-content"
91
91
  ref={ref}
92
92
  className={cn(
93
- "start-0 top-0 w-full rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-3xl-16)] text-[var(--bh-content-default)] shadow-[var(--shadow-popover)] md:absolute md:w-[var(--bh-space-19xl-384)]",
93
+ "start-0 top-0 w-full rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-3xl-16)] text-[var(--bh-content-default)] shadow-[var(--elevation-popover)] md:absolute md:w-[var(--bh-space-19xl-384)]",
94
94
  className
95
95
  )}
96
96
  {...props}
@@ -125,7 +125,7 @@ const NavigationMenuViewport = React.forwardRef<
125
125
  data-slot="navigation-menu-viewport"
126
126
  ref={ref}
127
127
  className={cn(
128
- "origin-top-center relative mt-[var(--bh-space-xs-4)] h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] shadow-[var(--shadow-popover)] md:w-[var(--radix-navigation-menu-viewport-width)]",
128
+ "origin-top-center relative mt-[var(--bh-space-xs-4)] h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] shadow-[var(--elevation-popover)] md:w-[var(--radix-navigation-menu-viewport-width)]",
129
129
  className
130
130
  )}
131
131
  {...props}
@@ -109,7 +109,7 @@ const paginationControlVariants = cva(
109
109
  "text-[length:var(--bh-pagination-font-size)] font-[var(--bh-pagination-label-font-weight)]",
110
110
  "leading-[var(--bh-pagination-line-height)] tracking-[var(--bh-pagination-letter-spacing)]",
111
111
  "outline-none transition-[background-color,border-color,color,box-shadow]",
112
- "focus-visible:shadow-[var(--shadow-pagination-focus)]",
112
+ "focus-visible:shadow-[var(--elevation-pagination-focus)]",
113
113
  "disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)]",
114
114
  "[&_svg]:pointer-events-none [&_svg]:shrink-0",
115
115
  "rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
@@ -118,7 +118,7 @@ const paginationControlVariants = cva(
118
118
  variants: {
119
119
  variant: {
120
120
  soft:
121
- "bg-[var(--bh-pagination-control-soft-bg)] shadow-[var(--shadow-pagination-control)] hover:bg-[var(--bh-pagination-control-soft-hover)] active:bg-[var(--bh-pagination-control-soft-pressed)]",
121
+ "bg-[var(--bh-pagination-control-soft-bg)] shadow-[var(--elevation-pagination-control)] hover:bg-[var(--bh-pagination-control-soft-hover)] active:bg-[var(--bh-pagination-control-soft-pressed)]",
122
122
  ghost:
123
123
  "bg-[var(--bh-pagination-control-ghost-bg)] hover:bg-[var(--bh-pagination-control-ghost-hover)] active:bg-[var(--bh-pagination-control-ghost-pressed)]",
124
124
  },
@@ -164,9 +164,9 @@ const paginationItemVariants = cva(
164
164
  variants: {
165
165
  active: {
166
166
  true:
167
- "border-[var(--bh-pagination-active-border)] bg-[var(--bh-pagination-active-bg)] text-[var(--bh-pagination-active-content)] shadow-[var(--shadow-pagination-active)]",
167
+ "border-[var(--bh-pagination-active-border)] bg-[var(--bh-pagination-active-bg)] text-[var(--bh-pagination-active-content)] shadow-[var(--elevation-pagination-active)]",
168
168
  false:
169
- "bg-[var(--bh-pagination-page-bg)] text-[var(--bh-pagination-page-content)] hover:bg-[var(--bh-pagination-page-hover)] active:bg-[var(--bh-pagination-page-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] focus-visible:shadow-[var(--shadow-pagination-focus)]",
169
+ "bg-[var(--bh-pagination-page-bg)] text-[var(--bh-pagination-page-content)] hover:bg-[var(--bh-pagination-page-hover)] active:bg-[var(--bh-pagination-page-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] focus-visible:shadow-[var(--elevation-pagination-focus)]",
170
170
  },
171
171
  },
172
172
  defaultVariants: {
@@ -24,7 +24,7 @@ const popoverContentVariants = cva(
24
24
  [
25
25
  "z-[var(--bh-z-popover)] grid max-w-[var(--bh-popover-max-width)] gap-[var(--bh-popover-content-gap)]",
26
26
  "rounded-[var(--bh-popover-radius)] bg-[var(--bh-popover-bg)] p-[var(--bh-popover-padding)]",
27
- "text-start text-[var(--bh-content-default)] shadow-[var(--shadow-popover)] outline-none",
27
+ "text-start text-[var(--bh-content-default)] shadow-[var(--elevation-popover)] outline-none",
28
28
  "font-[var(--bh-font-family)] tracking-[var(--bh-text-base-letter-spacing)]",
29
29
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
30
30
  ],
@@ -185,7 +185,7 @@ const PopoverTrigger = React.forwardRef<
185
185
  className={cn(
186
186
  "inline-flex min-w-0 items-center justify-center gap-[var(--bh-popover-trigger-gap)]",
187
187
  "rounded-[var(--bh-control-default)] text-start outline-none transition-[background-color,border-color,box-shadow]",
188
- "focus-visible:shadow-[var(--shadow-button-focus)] disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)]",
188
+ "focus-visible:shadow-[var(--elevation-button-focus)] disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)]",
189
189
  className
190
190
  )}
191
191
  data-slot="popover-trigger"
@@ -290,7 +290,7 @@ const PopoverClose = React.forwardRef<HTMLButtonElement, PopoverCloseProps>(
290
290
  className={cn(
291
291
  "inline-flex size-[var(--bh-popover-close-size)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)]",
292
292
  "border-0 bg-transparent p-0 text-[var(--bh-content-subtle)] outline-none transition-[background-color,box-shadow,color]",
293
- "hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--shadow-button-focus)]",
293
+ "hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--elevation-button-focus)]",
294
294
  className
295
295
  )}
296
296
  data-slot="popover-close"
@@ -186,7 +186,7 @@ function ProgressSlider({
186
186
  <SliderPrimitive.Thumb
187
187
  aria-label={props["aria-label"]}
188
188
  aria-valuetext={getProgressAriaValueText(ariaValueText, currentValue)}
189
- className="relative z-[var(--bh-z-overlay)] flex h-[var(--bh-progress-slider-height-current)] w-[var(--bh-progress-slider-handle-width-current)] shrink-0 items-center justify-center rounded-[var(--bh-progress-slider-radius)] bg-transparent outline-none transition-[box-shadow] focus-visible:shadow-[var(--shadow-progress-slider-thumb-focus)]"
189
+ className="relative z-[var(--bh-z-overlay)] flex h-[var(--bh-progress-slider-height-current)] w-[var(--bh-progress-slider-handle-width-current)] shrink-0 items-center justify-center rounded-[var(--bh-progress-slider-radius)] bg-transparent outline-none transition-[box-shadow] focus-visible:shadow-[var(--elevation-progress-slider-thumb-focus)]"
190
190
  data-slot="progress-slider-thumb"
191
191
  >
192
192
  <span
@@ -21,15 +21,15 @@ const radioCardVariants = cva(
21
21
  [
22
22
  "group/radio-card relative inline-block w-[var(--bh-radio-card-width)] max-w-full shrink-0",
23
23
  "rounded-[var(--bh-radius-xl-10)] p-[var(--bh-space-3xl-16)]",
24
- "bg-[var(--bh-radio-card-bg)] text-start shadow-[var(--shadow-radio-card-current)] outline-none",
24
+ "bg-[var(--bh-radio-card-bg)] text-start shadow-[var(--elevation-radio-card-current)] outline-none",
25
25
  "transition-[background-color,box-shadow]",
26
26
  "[--bh-radio-card-bg:var(--bh-bg-raised)]",
27
27
  "[--bh-radio-card-border:var(--bh-border-default)]",
28
- "[--shadow-radio-card-current:var(--shadow-radio-card)]",
28
+ "[--elevation-radio-card-current:var(--elevation-radio-card)]",
29
29
  "has-[:focus-visible]:[--bh-radio-card-border:var(--bh-border-brand-strong)]",
30
30
  "has-[:disabled]:pointer-events-none",
31
31
  "has-[:disabled]:[--bh-radio-card-border:var(--bh-border-disabled)]",
32
- "has-[:disabled]:[--shadow-radio-card-current:var(--shadow-radio-card-disabled)]",
32
+ "has-[:disabled]:[--elevation-radio-card-current:var(--elevation-radio-card-disabled)]",
33
33
  "has-[:disabled]:[--bh-radio-card-bg:var(--bh-bg-raised)]",
34
34
  ],
35
35
  {
@@ -40,7 +40,7 @@ const radioCardVariants = cva(
40
40
  active:
41
41
  "[--bh-radio-card-border:var(--bh-border-brand-strong)] [--bh-radio-card-border-width:var(--bh-radio-card-border-width-active)]",
42
42
  disabled:
43
- "pointer-events-none [--bh-radio-card-bg:var(--bh-bg-raised)] [--bh-radio-card-border:var(--bh-border-disabled)] [--shadow-radio-card-current:var(--shadow-radio-card-disabled)]",
43
+ "pointer-events-none [--bh-radio-card-bg:var(--bh-bg-raised)] [--bh-radio-card-border:var(--bh-border-disabled)] [--elevation-radio-card-current:var(--elevation-radio-card-disabled)]",
44
44
  },
45
45
  },
46
46
  defaultVariants: {
@@ -528,12 +528,12 @@ function RadioCardIndicator({
528
528
  >
529
529
  <span
530
530
  className={cn(
531
- "flex size-[var(--bh-space-3xl-16)] items-center justify-center rounded-[var(--bh-radius-full)] [--shadow-radio-control:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-radio-control-border,var(--bh-border-input)),var(--shadow-component-default)]",
531
+ "flex size-[var(--bh-space-3xl-16)] items-center justify-center rounded-[var(--bh-radius-full)] [--elevation-radio-control:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-radio-control-border,var(--bh-border-input)),var(--elevation-component-default)]",
532
532
  checked
533
- ? "bg-[var(--bh-interactive-radiobutton-default)] shadow-[var(--shadow-radio-control)] [--bh-radio-control-border:transparent]"
533
+ ? "bg-[var(--bh-interactive-radiobutton-default)] shadow-[var(--elevation-radio-control)] [--bh-radio-control-border:transparent]"
534
534
  : disabled
535
535
  ? "bg-[var(--bh-bg-raised)] shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)]"
536
- : "bg-[var(--bh-bg-raised)] shadow-[var(--shadow-radio-control)] [--bh-radio-control-border:var(--bh-border-input)]"
536
+ : "bg-[var(--bh-bg-raised)] shadow-[var(--elevation-radio-control)] [--bh-radio-control-border:var(--bh-border-input)]"
537
537
  )}
538
538
  >
539
539
  {checked ? (
@@ -15,12 +15,12 @@ const radioRoot = cva(
15
15
 
16
16
  const radioControl = cva([
17
17
  "pointer-events-none flex size-[var(--bh-space-3xl-16)] items-center justify-center rounded-[var(--bh-radius-full)]",
18
- "bg-[var(--bh-bg-default)] shadow-[var(--shadow-radio-control)] transition-[background-color,box-shadow]",
18
+ "bg-[var(--bh-bg-default)] shadow-[var(--elevation-radio-control)] transition-[background-color,box-shadow]",
19
19
  "[--bh-radio-control-border:var(--bh-border-input)]",
20
20
  "group-data-[invalid=true]/radio:[--bh-radio-control-border:var(--bh-border-danger-strong)]",
21
21
  "group-hover/radio-field:[--bh-radio-control-border:var(--bh-border-brand-strong)]",
22
22
  "peer-checked:bg-[var(--bh-interactive-radiobutton-default)] peer-checked:[--bh-radio-control-border:transparent]",
23
- "peer-focus-visible:shadow-[var(--shadow-radio-control),var(--shadow-button-focus)]",
23
+ "peer-focus-visible:shadow-[var(--elevation-radio-control),var(--elevation-button-focus)]",
24
24
  "peer-disabled:bg-[var(--bh-bg-disabled)] peer-disabled:shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)]",
25
25
  ])
26
26
 
@@ -68,7 +68,7 @@ function ResizablePanel({
68
68
  return (
69
69
  <Panel
70
70
  className={cn(
71
- "outline-none focus-visible:shadow-[var(--shadow-component-focus-ring)]",
71
+ "outline-none focus-visible:shadow-[var(--elevation-component-focus-ring)]",
72
72
  className
73
73
  )}
74
74
  elementRef={setElementRef}
@@ -86,7 +86,7 @@ function ResizableHandle({
86
86
  <Separator
87
87
  data-slot="resizable-handle"
88
88
  className={cn(
89
- "relative flex w-[var(--bh-border-width-default)] items-center justify-center bg-[var(--bh-border-subtle)] outline-none transition-[background-color,box-shadow] after:absolute after:inset-y-0 after:start-1/2 after:w-[var(--bh-space-5xl-24)] after:-translate-x-1/2 focus-visible:shadow-[var(--shadow-button-focus)] aria-[orientation=horizontal]:h-[var(--bh-border-width-default)] aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:start-0 aria-[orientation=horizontal]:after:h-[var(--bh-space-5xl-24)] aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:-translate-y-1/2 aria-[orientation=horizontal]:after:translate-x-0",
89
+ "relative flex w-[var(--bh-border-width-default)] items-center justify-center bg-[var(--bh-border-subtle)] outline-none transition-[background-color,box-shadow] after:absolute after:inset-y-0 after:start-1/2 after:w-[var(--bh-space-5xl-24)] after:-translate-x-1/2 focus-visible:shadow-[var(--elevation-button-focus)] aria-[orientation=horizontal]:h-[var(--bh-border-width-default)] aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:start-0 aria-[orientation=horizontal]:after:h-[var(--bh-space-5xl-24)] aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:-translate-y-1/2 aria-[orientation=horizontal]:after:translate-x-0",
90
90
  className
91
91
  )}
92
92
  {...props}
@@ -0,0 +1,37 @@
1
+ /* Rolling digits: the old glyph slides out as the new one slides in. */
2
+ [data-slot="rolling-count"] {
3
+ font-variant-numeric: tabular-nums;
4
+ unicode-bidi: isolate;
5
+ }
6
+
7
+ [data-slot="rolling-count-digits"] {
8
+ display: inline-flex;
9
+ align-items: baseline;
10
+ }
11
+
12
+ [data-slot="rolling-digit"] {
13
+ block-size: 1em;
14
+ display: inline-block;
15
+ line-height: 1em;
16
+ overflow: hidden;
17
+ }
18
+
19
+ [data-slot="rolling-digit-track"] {
20
+ display: flex;
21
+ flex-direction: column;
22
+ transition: transform var(--bh-duration-slow) var(--bh-easing-standard);
23
+ }
24
+
25
+ [data-slot="rolling-digit-track"] > span {
26
+ block-size: 1em;
27
+ line-height: 1em;
28
+ }
29
+
30
+ [data-slot="rolling-digit-track"][data-direction="up"][data-shifted="true"],
31
+ [data-slot="rolling-digit-track"][data-direction="down"][data-shifted="false"] {
32
+ transform: translateY(-1em);
33
+ }
34
+
35
+ [data-slot="rolling-digit-track"][data-direction="down"][data-shifted="true"] {
36
+ transform: translateY(0);
37
+ }
@@ -0,0 +1,110 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { useMotionEnd } from "@/hooks/use-motion-end"
7
+
8
+ import "./rolling-count.css"
9
+
10
+ /** One character slot that rolls the old glyph out and the new one in. */
11
+ function RollingDigit({ char }: { char: string }) {
12
+ const previous = React.useRef(char)
13
+ const [roll, setRoll] = React.useState<{
14
+ direction: "up" | "down"
15
+ from: string
16
+ to: string
17
+ } | null>(null)
18
+ const [shifted, setShifted] = React.useState(false)
19
+ const trackRef = React.useRef<HTMLSpanElement>(null)
20
+
21
+ // Swap back to the plain glyph once the roll transition has finished.
22
+ useMotionEnd(trackRef, roll !== null && shifted, () => setRoll(null))
23
+
24
+ React.useEffect(() => {
25
+ if (char === previous.current) return
26
+ const from = previous.current
27
+ previous.current = char
28
+ const fromNumber = Number.parseInt(from, 10)
29
+ const toNumber = Number.parseInt(char, 10)
30
+ // Digits roll like an odometer: 9 → 0 keeps rolling up, 0 → 9 rolls down.
31
+ const numeric = Number.isFinite(fromNumber) && Number.isFinite(toNumber)
32
+ const wrapsUp = numeric && fromNumber === 9 && toNumber === 0
33
+ const wrapsDown = numeric && fromNumber === 0 && toNumber === 9
34
+ const direction =
35
+ wrapsDown || (numeric && !wrapsUp && toNumber < fromNumber)
36
+ ? "down"
37
+ : "up"
38
+ setRoll({ direction, from, to: char })
39
+ setShifted(false)
40
+ let second = 0
41
+ const first = window.requestAnimationFrame(() => {
42
+ second = window.requestAnimationFrame(() => setShifted(true))
43
+ })
44
+ return () => {
45
+ window.cancelAnimationFrame(first)
46
+ window.cancelAnimationFrame(second)
47
+ }
48
+ }, [char])
49
+
50
+ if (!roll) {
51
+ return <span data-slot="rolling-digit">{char}</span>
52
+ }
53
+
54
+ const top = roll.direction === "down" ? roll.to : roll.from
55
+ const bottom = roll.direction === "down" ? roll.from : roll.to
56
+
57
+ return (
58
+ <span data-slot="rolling-digit">
59
+ <span
60
+ data-direction={roll.direction}
61
+ data-shifted={shifted ? "true" : "false"}
62
+ data-slot="rolling-digit-track"
63
+ ref={trackRef}
64
+ >
65
+ <span>{top}</span>
66
+ <span>{bottom}</span>
67
+ </span>
68
+ </span>
69
+ )
70
+ }
71
+
72
+ type RollingCountProps = Omit<React.ComponentProps<"span">, "children"> & {
73
+ value: string
74
+ }
75
+
76
+ /**
77
+ * Tabular figures where each changed character rolls into place. Assistive
78
+ * technology reads the plain value; the rolling glyphs are presentational.
79
+ */
80
+ const RollingCount = React.forwardRef<HTMLSpanElement, RollingCountProps>(
81
+ function RollingCount(
82
+ { "aria-label": ariaLabel, className, dir = "ltr", value, ...props },
83
+ ref
84
+ ) {
85
+ // Digits stay in reading order for numbers even inside RTL copy; each
86
+ // glyph is its own box, so without an explicit direction bidi reorders
87
+ // them around the separator.
88
+ return (
89
+ <span
90
+ data-slot="rolling-count"
91
+ dir={dir}
92
+ ref={ref}
93
+ className={cn("inline-flex items-baseline", className)}
94
+ {...props}
95
+ >
96
+ <span className="sr-only">{ariaLabel ?? value}</span>
97
+ <span aria-hidden="true" data-slot="rolling-count-digits">
98
+ {/* Keyed from the end so a value that grows a digit (9/11 → 10/11)
99
+ rolls the existing glyphs in place instead of shifting them. */}
100
+ {Array.from(value).map((char, index, chars) => (
101
+ <RollingDigit char={char} key={chars.length - 1 - index} />
102
+ ))}
103
+ </span>
104
+ </span>
105
+ )
106
+ }
107
+ )
108
+
109
+ export { RollingCount }
110
+ export type { RollingCountProps }
@@ -48,7 +48,7 @@ const ScrollArea = React.forwardRef<
48
48
  data-slot="scroll-area-viewport"
49
49
  {...viewportProps}
50
50
  tabIndex={viewportProps?.tabIndex ?? 0}
51
- className={cn("size-full rounded-[inherit] outline-none focus-visible:shadow-[var(--shadow-component-focus-ring)]", viewportProps?.className)}
51
+ className={cn("size-full rounded-[inherit] outline-none focus-visible:shadow-[var(--elevation-component-focus-ring)]", viewportProps?.className)}
52
52
  >
53
53
  {children}
54
54
  </ScrollAreaPrimitive.Viewport>
@@ -31,8 +31,8 @@ const selectTrigger = cva(
31
31
  "group/select-trigger relative flex w-full items-center gap-[var(--bh-select-trigger-gap)] rounded-[var(--bh-select-trigger-radius)]",
32
32
  "select-none px-[var(--bh-select-trigger-padding-x)] text-start outline-none",
33
33
  "transition-[background-color,box-shadow]",
34
- "[--bh-select-trigger-border-current:var(--bh-select-trigger-border)] [--shadow-select-trigger:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-current,var(--bh-select-trigger-border)),var(--shadow-component-default)]",
35
- "focus-visible:[--bh-select-trigger-border-current:var(--bh-select-trigger-border-focus)] focus-visible:shadow-[var(--shadow-select-focus-ring)]",
34
+ "[--bh-select-trigger-border-current:var(--bh-select-trigger-border)] [--elevation-select-trigger:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-current,var(--bh-select-trigger-border)),var(--elevation-component-default)]",
35
+ "focus-visible:[--bh-select-trigger-border-current:var(--bh-select-trigger-border-focus)] focus-visible:shadow-[var(--elevation-select-focus-ring)]",
36
36
  "aria-invalid:[--bh-select-trigger-border-current:var(--bh-select-trigger-border-error)]",
37
37
  "disabled:pointer-events-none disabled:bg-[var(--bh-select-trigger-bg-disabled)] disabled:shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-disabled)]",
38
38
  "[&_svg]:pointer-events-none [&_svg]:shrink-0",
@@ -46,7 +46,7 @@ const selectTrigger = cva(
46
46
  },
47
47
  variant: {
48
48
  default:
49
- "bg-[var(--bh-select-trigger-bg)] shadow-[var(--shadow-select-trigger)]",
49
+ "bg-[var(--bh-select-trigger-bg)] shadow-[var(--elevation-select-trigger)]",
50
50
  soft:
51
51
  "bg-[var(--bh-select-trigger-bg-soft)] shadow-none",
52
52
  },
@@ -54,7 +54,7 @@ const selectTrigger = cva(
54
54
  default: "",
55
55
  filled: "",
56
56
  error:
57
- "[--bh-select-trigger-border-current:var(--bh-select-trigger-border-error)] shadow-[var(--shadow-select-trigger)] focus-visible:shadow-[var(--shadow-select-danger-focus-ring)]",
57
+ "[--bh-select-trigger-border-current:var(--bh-select-trigger-border-error)] shadow-[var(--elevation-select-trigger)] focus-visible:shadow-[var(--elevation-select-danger-focus-ring)]",
58
58
  disabled:
59
59
  "pointer-events-none bg-[var(--bh-select-trigger-bg-disabled)] shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-disabled)]",
60
60
  },
@@ -71,7 +71,7 @@ const selectMenu = cva(
71
71
  [
72
72
  "w-[var(--radix-popper-anchor-width,var(--bh-select-menu-width))] max-w-[var(--radix-popper-available-width,100vw)] overflow-hidden rounded-[var(--bh-select-menu-radius)]",
73
73
  "select-none bg-[var(--bh-select-menu-bg)] py-[var(--bh-select-menu-padding-y)]",
74
- "shadow-[var(--shadow-select-menu)]",
74
+ "shadow-[var(--elevation-select-menu)]",
75
75
  ].join(" ")
76
76
  )
77
77
 
@@ -167,6 +167,8 @@ type SelectProps = Omit<React.ComponentProps<"div">, "children" | "dir"> & {
167
167
  }
168
168
 
169
169
  const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
170
+ "aria-label": ariaLabel,
171
+ "aria-labelledby": ariaLabelledBy,
170
172
  children,
171
173
  className,
172
174
  defaultSelectValue,
@@ -224,9 +226,11 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
224
226
  const shouldRenderMenu = React.Children.count(children) > 0
225
227
  const selectId = triggerId || generatedId
226
228
  const helperId = `${selectId}-helper`
227
- const triggerAccessibleLabel = shouldRenderLabel
228
- ? undefined
229
- : getAccessibleText(selectedValue) ?? getAccessibleText(selectedPlaceholder)
229
+ const triggerAccessibleLabel =
230
+ ariaLabel ??
231
+ (!shouldRenderLabel && !ariaLabelledBy
232
+ ? getAccessibleText(selectedLabel)
233
+ : undefined)
230
234
  const rootOpenProps =
231
235
  open === undefined ? { onOpenChange } : { open, onOpenChange }
232
236
 
@@ -265,6 +269,7 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
265
269
  <SelectPrimitive.Trigger asChild>
266
270
  <SelectTrigger
267
271
  aria-label={triggerAccessibleLabel}
272
+ aria-labelledby={ariaLabelledBy}
268
273
  aria-describedby={shouldRenderHelperText ? helperId : undefined}
269
274
  aria-haspopup="listbox"
270
275
  aria-invalid={isError || undefined}
@@ -882,7 +887,7 @@ function SelectCheckboxIndicator({
882
887
  <span
883
888
  data-slot="select-checkbox-control"
884
889
  className={cn(
885
- "pointer-events-none absolute left-1/2 top-1/2 size-[var(--bh-select-checkbox-size)] -translate-x-1/2 -translate-y-1/2 rounded-[var(--bh-select-checkbox-radius)] shadow-[var(--shadow-select-checkbox)] [--bh-select-checkbox-border:var(--bh-border-input)] [--shadow-select-checkbox:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-checkbox-border,var(--bh-border-input)),var(--shadow-component-default)]",
890
+ "pointer-events-none absolute left-1/2 top-1/2 size-[var(--bh-select-checkbox-size)] -translate-x-1/2 -translate-y-1/2 rounded-[var(--bh-select-checkbox-radius)] shadow-[var(--elevation-select-checkbox)] [--bh-select-checkbox-border:var(--bh-border-input)] [--elevation-select-checkbox:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-checkbox-border,var(--bh-border-input)),var(--elevation-component-default)]",
886
891
  checked
887
892
  ? "bg-[var(--bh-interactive-checkbox-default)] [--bh-select-checkbox-border:transparent]"
888
893
  : "bg-[var(--bh-bg-default)]",
@@ -891,7 +896,7 @@ function SelectCheckboxIndicator({
891
896
  )}
892
897
  />
893
898
  {checked ? (
894
- <span className="absolute left-1/2 top-1/2 flex size-[var(--bh-select-checkbox-icon-size)] -translate-x-1/2 -translate-y-1/2 items-center justify-center text-[var(--bh-content-on-color)]">
899
+ <span className="absolute left-1/2 top-1/2 flex size-[var(--bh-select-checkbox-icon-size)] -translate-x-1/2 -translate-y-1/2 items-center justify-center text-[var(--bh-interactive-checkbox-content)]">
895
900
  <SelectIcon name="check-fill" />
896
901
  </span>
897
902
  ) : null}
@@ -13,7 +13,7 @@ type SheetSize = "sm" | "default" | "lg"
13
13
  const sheetContentVariants = cva(
14
14
  [
15
15
  "fixed z-[var(--bh-z-overlay)] flex flex-col overflow-hidden bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)]",
16
- "shadow-[var(--shadow-component-default)] outline-none",
16
+ "shadow-[var(--elevation-component-default)] outline-none",
17
17
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
18
18
  ],
19
19
  {
@@ -255,7 +255,7 @@ const SheetCloseButton = React.forwardRef<
255
255
  ref={ref}
256
256
  type={type}
257
257
  className={cn(
258
- "absolute end-[var(--bh-space-3xl-16)] top-[var(--bh-space-3xl-16)] inline-flex size-[var(--bh-space-6xl-32)] items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-content-subtle)] outline-none transition-[background-color,box-shadow,color] hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--shadow-button-focus)]",
258
+ "absolute end-[var(--bh-space-3xl-16)] top-[var(--bh-space-3xl-16)] inline-flex size-[var(--bh-space-6xl-32)] items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-content-subtle)] outline-none transition-[background-color,box-shadow,color] hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:shadow-[var(--elevation-button-focus)]",
259
259
  className
260
260
  )}
261
261
  {...props}
@@ -0,0 +1,69 @@
1
+ [data-slot="shimmer-text"] {
2
+ --shimmer-text-color: var(--bh-content-subtle);
3
+ --shimmer-text-valley: var(--bh-content-disabled);
4
+
5
+ color: var(--shimmer-text-color);
6
+ }
7
+
8
+ [data-slot="shimmer-text"][data-tone="default"] {
9
+ --shimmer-text-color: var(--bh-content-default);
10
+ --shimmer-text-valley: var(--bh-content-subtle);
11
+ }
12
+
13
+ /* The shine is a wide gradient clipped to the glyphs and slid across them. */
14
+ [data-slot="shimmer-text"][data-animated="true"] {
15
+ animation: bh-shimmer-text var(--bh-shimmer-text-duration)
16
+ var(--bh-easing-standard) infinite;
17
+ background-clip: text;
18
+ -webkit-background-clip: text;
19
+ background-image: linear-gradient(
20
+ 90deg,
21
+ var(--shimmer-text-color) 0%,
22
+ var(--shimmer-text-color) 30%,
23
+ var(--shimmer-text-valley) 45%,
24
+ var(--shimmer-text-valley) 55%,
25
+ var(--shimmer-text-color) 70%,
26
+ var(--shimmer-text-color) 100%
27
+ );
28
+ background-size: 300% 100%;
29
+ color: transparent;
30
+ -webkit-text-fill-color: transparent;
31
+ }
32
+
33
+ [dir="rtl"] [data-slot="shimmer-text"][data-animated="true"],
34
+ [data-slot="shimmer-text"][dir="rtl"][data-animated="true"] {
35
+ animation-name: bh-shimmer-text-rtl;
36
+ }
37
+
38
+ @keyframes bh-shimmer-text {
39
+ 0%,
40
+ 18% {
41
+ background-position: 100% 0;
42
+ }
43
+
44
+ 82%,
45
+ 100% {
46
+ background-position: 0% 0;
47
+ }
48
+ }
49
+
50
+ @keyframes bh-shimmer-text-rtl {
51
+ 0%,
52
+ 18% {
53
+ background-position: 0% 0;
54
+ }
55
+
56
+ 82%,
57
+ 100% {
58
+ background-position: 100% 0;
59
+ }
60
+ }
61
+
62
+ @media (prefers-reduced-motion: reduce) {
63
+ [data-slot="shimmer-text"][data-animated="true"] {
64
+ animation: none;
65
+ background-image: none;
66
+ color: var(--shimmer-text-color);
67
+ -webkit-text-fill-color: var(--shimmer-text-color);
68
+ }
69
+ }
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ import "./shimmer-text.css"
8
+
9
+ type ShimmerTextTone = "subtle" | "default"
10
+
11
+ type ShimmerTextProps = React.ComponentProps<"span"> & {
12
+ /** Set false to freeze the sweep while keeping the same color contract. */
13
+ animated?: boolean
14
+ /** `subtle` reads as secondary copy; `default` matches primary text. */
15
+ tone?: ShimmerTextTone
16
+ }
17
+
18
+ /**
19
+ * Text with a soft brightness valley sweeping through it. The sweep direction
20
+ * follows the inherited writing direction, so Arabic labels shine start-to-end.
21
+ */
22
+ const ShimmerText = React.forwardRef<HTMLSpanElement, ShimmerTextProps>(
23
+ function ShimmerText(
24
+ { animated = true, className, tone = "subtle", ...props },
25
+ ref
26
+ ) {
27
+ // The slot is what the shimmer styles key on, so it is pinned after the
28
+ // spread: a consumer that needs its own hook wraps the primitive instead.
29
+ return (
30
+ <span
31
+ data-animated={animated ? "true" : "false"}
32
+ data-tone={tone}
33
+ ref={ref}
34
+ className={cn("inline-block min-w-0", className)}
35
+ {...props}
36
+ data-slot="shimmer-text"
37
+ />
38
+ )
39
+ }
40
+ )
41
+
42
+ export { ShimmerText }
43
+ export type { ShimmerTextProps, ShimmerTextTone }