banhaten 0.1.3 → 0.3.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 (275) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/CONTRIBUTING.md +44 -0
  3. package/README.md +65 -13
  4. package/SECURITY.md +15 -0
  5. package/banhaten.config.example.json +0 -2
  6. package/docs/design-system/README.md +9 -0
  7. package/docs/design-system/adoption-quality-remediation.md +87 -0
  8. package/docs/design-system/agent-usage.md +13 -0
  9. package/docs/design-system/appearance-presets.md +6 -1
  10. package/docs/design-system/component-docs-pattern.md +81 -0
  11. package/docs/design-system/component-implementation-ownership-audit.md +244 -0
  12. package/docs/design-system/component-quality-audit-2026-08-20.md +223 -0
  13. package/docs/design-system/component-showcase-consistency-report.md +2 -0
  14. package/docs/design-system/component-token-consistency-audit.md +13 -3
  15. package/docs/design-system/components/README.md +4 -2
  16. package/docs/design-system/components/accordion.md +16 -0
  17. package/docs/design-system/components/activity-feed.md +17 -1
  18. package/docs/design-system/components/activity-heatmap.md +73 -0
  19. package/docs/design-system/components/alert-dialog.md +17 -0
  20. package/docs/design-system/components/alert.md +46 -14
  21. package/docs/design-system/components/aspect-ratio.md +15 -0
  22. package/docs/design-system/components/attribute.md +16 -0
  23. package/docs/design-system/components/autocomplete.md +19 -0
  24. package/docs/design-system/components/avatar.md +18 -0
  25. package/docs/design-system/components/badge.md +19 -0
  26. package/docs/design-system/components/banner.md +19 -1
  27. package/docs/design-system/components/breadcrumbs.md +17 -0
  28. package/docs/design-system/components/button-group.md +35 -0
  29. package/docs/design-system/components/button.md +18 -0
  30. package/docs/design-system/components/card.md +26 -1
  31. package/docs/design-system/components/carousel.md +20 -3
  32. package/docs/design-system/components/catalog-components.md +18 -1
  33. package/docs/design-system/components/chart.md +15 -0
  34. package/docs/design-system/components/checkbox.md +16 -0
  35. package/docs/design-system/components/collapsible.md +15 -0
  36. package/docs/design-system/components/command.md +15 -0
  37. package/docs/design-system/components/context-menu.md +15 -0
  38. package/docs/design-system/components/date-picker.md +21 -1
  39. package/docs/design-system/components/divider.md +16 -0
  40. package/docs/design-system/components/empty-state.md +17 -0
  41. package/docs/design-system/components/field.md +17 -0
  42. package/docs/design-system/components/file-upload.md +18 -0
  43. package/docs/design-system/components/hover-card.md +15 -0
  44. package/docs/design-system/components/input-group.md +17 -0
  45. package/docs/design-system/components/input-otp.md +18 -0
  46. package/docs/design-system/components/input.md +43 -7
  47. package/docs/design-system/components/item.md +72 -0
  48. package/docs/design-system/components/kbd.md +18 -0
  49. package/docs/design-system/components/label.md +16 -0
  50. package/docs/design-system/components/menu.md +17 -0
  51. package/docs/design-system/components/menubar.md +15 -0
  52. package/docs/design-system/components/modal.md +25 -4
  53. package/docs/design-system/components/native-select.md +18 -0
  54. package/docs/design-system/components/navigation-menu.md +15 -0
  55. package/docs/design-system/components/onboarding-step-list-item.md +16 -1
  56. package/docs/design-system/components/page-header.md +17 -1
  57. package/docs/design-system/components/pagination.md +31 -8
  58. package/docs/design-system/components/popover.md +19 -0
  59. package/docs/design-system/components/progress-slider.md +18 -0
  60. package/docs/design-system/components/progress.md +17 -0
  61. package/docs/design-system/components/radio-card.md +16 -0
  62. package/docs/design-system/components/radio-group.md +17 -0
  63. package/docs/design-system/components/resizable.md +15 -0
  64. package/docs/design-system/components/scroll-area.md +20 -1
  65. package/docs/design-system/components/segmented-control.md +71 -0
  66. package/docs/design-system/components/select.md +44 -7
  67. package/docs/design-system/components/sheet.md +17 -0
  68. package/docs/design-system/components/sidebar.md +17 -0
  69. package/docs/design-system/components/skeleton.md +18 -0
  70. package/docs/design-system/components/slideout.md +17 -0
  71. package/docs/design-system/components/slider.md +20 -0
  72. package/docs/design-system/components/social-button.md +21 -3
  73. package/docs/design-system/components/spinner.md +16 -0
  74. package/docs/design-system/components/steps.md +19 -0
  75. package/docs/design-system/components/table.md +36 -4
  76. package/docs/design-system/components/tabs.md +17 -0
  77. package/docs/design-system/components/tag.md +17 -0
  78. package/docs/design-system/components/textarea.md +16 -0
  79. package/docs/design-system/components/timeline.md +17 -0
  80. package/docs/design-system/components/toast.md +17 -0
  81. package/docs/design-system/components/toggle.md +17 -0
  82. package/docs/design-system/components/toolbar.md +21 -1
  83. package/docs/design-system/components/tooltip.md +18 -1
  84. package/docs/design-system/density-and-elevation.md +61 -0
  85. package/docs/design-system/direction-and-localization.md +23 -0
  86. package/docs/design-system/form-recipes.md +73 -0
  87. package/docs/design-system/gap-promotion-policy.md +21 -0
  88. package/docs/design-system/gap-promotion-report.md +100 -0
  89. package/docs/design-system/installation.md +68 -0
  90. package/docs/design-system/launch-comparison-shadcn.md +98 -0
  91. package/docs/design-system/registry-and-cli.md +30 -7
  92. package/docs/design-system/release-policy.md +30 -0
  93. package/docs/design-system/roadmap.md +14 -0
  94. package/docs/design-system/support-matrix.md +23 -0
  95. package/docs/design-system/tabs.md +1 -1
  96. package/docs/design-system/token-governance.md +23 -1
  97. package/package.json +30 -15
  98. package/registry/component-contract.json +40 -0
  99. package/registry/components/alert-dialog.tsx +2 -2
  100. package/registry/components/alert.tsx +101 -76
  101. package/registry/components/autocomplete.tsx +23 -9
  102. package/registry/components/avatar.tsx +14 -10
  103. package/registry/components/badge.tsx +18 -18
  104. package/registry/components/button-group.tsx +324 -5
  105. package/registry/components/button.tsx +38 -17
  106. package/registry/components/card.tsx +87 -62
  107. package/registry/components/carousel.tsx +67 -5
  108. package/registry/components/chart.tsx +36 -3
  109. package/registry/components/checkbox.tsx +3 -3
  110. package/registry/components/collapsible.tsx +1 -1
  111. package/registry/components/command.tsx +38 -4
  112. package/registry/components/context-menu.tsx +1 -1
  113. package/registry/components/date-picker.tsx +312 -135
  114. package/registry/components/expanded/ActivityFeed.tsx +9 -22
  115. package/registry/components/expanded/ActivityHeatmap.tsx +64 -0
  116. package/registry/components/expanded/Banner.tsx +3 -4
  117. package/registry/components/expanded/Breadcrumbs.tsx +8 -5
  118. package/registry/components/expanded/CatalogComponentsShowcase.tsx +16 -17
  119. package/registry/components/expanded/CatalogDivider.tsx +4 -3
  120. package/registry/components/expanded/EmptyState.tsx +1 -1
  121. package/registry/components/expanded/FileUpload.tsx +16 -7
  122. package/registry/components/expanded/OnboardingStepListItem.tsx +2 -6
  123. package/registry/components/expanded/PageHeader.tsx +47 -56
  124. package/registry/components/expanded/Slideout.tsx +29 -424
  125. package/registry/components/expanded/Steps.tsx +151 -13
  126. package/registry/components/expanded/Table.tsx +161 -82
  127. package/registry/components/expanded/Timeline.tsx +14 -27
  128. package/registry/components/expanded/activityFeed.css +9 -9
  129. package/registry/components/expanded/activityHeatmap.css +66 -0
  130. package/registry/components/expanded/banner.css +21 -11
  131. package/registry/components/expanded/breadcrumbs.css +2 -2
  132. package/registry/components/expanded/divider.css +1 -1
  133. package/registry/components/expanded/emptyState.css +1 -1
  134. package/registry/components/expanded/fileUpload.css +7 -7
  135. package/registry/components/expanded/onboardingStepListItem.css +2 -2
  136. package/registry/components/expanded/pageHeader.css +14 -11
  137. package/registry/components/expanded/slideout.css +10 -690
  138. package/registry/components/expanded/steps.css +78 -15
  139. package/registry/components/expanded/table.css +60 -69
  140. package/registry/components/expanded/timeline.css +19 -18
  141. package/registry/components/field.tsx +61 -8
  142. package/registry/components/input-content.ts +1 -0
  143. package/registry/components/input-otp.tsx +13 -3
  144. package/registry/components/input.tsx +68 -43
  145. package/registry/components/item.tsx +189 -0
  146. package/registry/components/kbd.tsx +111 -4
  147. package/registry/components/menu.tsx +87 -251
  148. package/registry/components/menubar.tsx +1 -1
  149. package/registry/components/modal.tsx +143 -103
  150. package/registry/components/native-select.tsx +2 -2
  151. package/registry/components/navigation-menu.tsx +1 -1
  152. package/registry/components/pagination.css +36 -0
  153. package/registry/components/pagination.tsx +88 -64
  154. package/registry/components/popover.tsx +162 -664
  155. package/registry/components/progress-slider.tsx +1 -1
  156. package/registry/components/radio-card.tsx +116 -87
  157. package/registry/components/resizable.tsx +36 -2
  158. package/registry/components/scroll-area.tsx +30 -6
  159. package/registry/components/segmented-control.tsx +87 -0
  160. package/registry/components/select-content.tsx +2 -2
  161. package/registry/components/select.tsx +86 -27
  162. package/registry/components/sheet.tsx +1 -1
  163. package/registry/components/sidebar.tsx +12 -6
  164. package/registry/components/skeleton.css +0 -3
  165. package/registry/components/social-button.tsx +59 -30
  166. package/registry/components/spinner.tsx +18 -55
  167. package/registry/components/table-elements.tsx +128 -0
  168. package/registry/components/tabs.css +1 -1
  169. package/registry/components/tabs.tsx +13 -14
  170. package/registry/components/tag.tsx +3 -3
  171. package/registry/components/textarea.tsx +3 -3
  172. package/registry/components/toast.tsx +16 -11
  173. package/registry/components/toggle.tsx +4 -4
  174. package/registry/components/toolbar.tsx +94 -22
  175. package/registry/components/tooltip.tsx +2 -2
  176. package/registry/examples/alert-demo.tsx +25 -5
  177. package/registry/examples/attribute-demo.tsx +1 -1
  178. package/registry/examples/autocomplete-demo.tsx +3 -3
  179. package/registry/examples/avatar-demo.tsx +14 -11
  180. package/registry/examples/badge-demo.tsx +1 -1
  181. package/registry/examples/button-demo.tsx +6 -6
  182. package/registry/examples/button-group-demo.tsx +23 -2
  183. package/registry/examples/card-demo.tsx +46 -8
  184. package/registry/examples/carousel-demo.tsx +20 -2
  185. package/registry/examples/chart-demo.tsx +1 -1
  186. package/registry/examples/checkbox-demo.tsx +5 -5
  187. package/registry/examples/collection-filter-bar-demo.tsx +135 -0
  188. package/registry/examples/command-demo.tsx +3 -3
  189. package/registry/examples/{expanded/command-bar-demo.tsx → command-palette-demo.tsx} +5 -15
  190. package/registry/examples/context-menu-demo.tsx +1 -1
  191. package/registry/examples/control-density-contract-demo.tsx +61 -0
  192. package/registry/examples/date-picker-demo.tsx +12 -12
  193. package/registry/examples/divider-demo.tsx +3 -3
  194. package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
  195. package/registry/examples/expanded/banner-demo.tsx +1 -1
  196. package/registry/examples/expanded/breadcrumbs-demo.tsx +4 -4
  197. package/registry/examples/expanded/empty-state-demo.tsx +1 -1
  198. package/registry/examples/expanded/file-upload-demo.tsx +1 -1
  199. package/registry/examples/expanded/onboarding-step-demo.tsx +1 -1
  200. package/registry/examples/expanded/slideout-demo.tsx +2 -2
  201. package/registry/examples/expanded/steps-demo.tsx +1 -1
  202. package/registry/examples/expanded/tabs-demo.tsx +1 -1
  203. package/registry/examples/expanded/timeline-demo.tsx +1 -1
  204. package/registry/examples/hover-card-demo.tsx +2 -2
  205. package/registry/examples/input-demo.tsx +6 -6
  206. package/registry/examples/input-otp-demo.tsx +4 -4
  207. package/registry/examples/item-demo.tsx +46 -0
  208. package/registry/examples/kbd-demo.tsx +1 -1
  209. package/registry/examples/label-demo.tsx +1 -1
  210. package/registry/examples/menu-demo.tsx +1 -1
  211. package/registry/examples/modal-demo.tsx +67 -9
  212. package/registry/examples/navigation-menu-demo.tsx +1 -1
  213. package/registry/examples/pagination-demo.tsx +10 -7
  214. package/registry/examples/popover-demo.tsx +19 -19
  215. package/registry/examples/progress-demo.tsx +1 -1
  216. package/registry/examples/progress-slider-demo.tsx +1 -1
  217. package/registry/examples/radio-card-demo.tsx +4 -4
  218. package/registry/examples/radio-group-demo.tsx +2 -2
  219. package/registry/examples/resizable-demo.tsx +1 -1
  220. package/registry/examples/results-toolbar-demo.tsx +44 -0
  221. package/registry/examples/scroll-area-demo.tsx +3 -3
  222. package/registry/examples/segmented-control-demo.tsx +39 -0
  223. package/registry/examples/select-demo.tsx +1 -1
  224. package/registry/examples/sidebar-demo.tsx +5 -3
  225. package/registry/examples/slider-demo.tsx +3 -3
  226. package/registry/examples/social-button-demo.tsx +18 -8
  227. package/registry/examples/spinner-demo.tsx +13 -13
  228. package/registry/examples/tabs-demo.tsx +1 -1
  229. package/registry/examples/tag-demo.tsx +1 -1
  230. package/registry/examples/textarea-demo.tsx +4 -4
  231. package/registry/examples/toast-demo.tsx +1 -1
  232. package/registry/examples/toggle-demo.tsx +6 -6
  233. package/registry/examples/tooltip-demo.tsx +4 -4
  234. package/registry/guidance/USAGE.md +27 -0
  235. package/registry/hooks/use-direction.ts +193 -13
  236. package/registry/index.json +5668 -375
  237. package/registry/styles/globals.css +2404 -3999
  238. package/registry.json +156 -149
  239. package/schema/config.schema.json +11 -8
  240. package/schema/registry.schema.json +150 -45
  241. package/schema/tokens.schema.json +134 -34
  242. package/src/cli/command-line.js +194 -0
  243. package/src/cli/config-editors.js +9 -0
  244. package/src/cli/doctor.js +399 -0
  245. package/src/cli/file-transaction.js +295 -0
  246. package/src/cli/global-css.js +153 -0
  247. package/src/cli/index.js +567 -1613
  248. package/src/cli/install-plan.js +592 -0
  249. package/src/cli/install-receipt.js +171 -0
  250. package/src/cli/jsonc.js +203 -0
  251. package/src/cli/package-install-state.js +254 -0
  252. package/src/cli/package-manager.js +238 -0
  253. package/src/cli/package-plan.js +185 -0
  254. package/src/cli/package-version.js +97 -0
  255. package/src/cli/registry-query.js +207 -0
  256. package/src/cli/reporting.js +55 -0
  257. package/src/cli/schema-validation.js +19 -0
  258. package/src/cli/source-scanner.js +686 -0
  259. package/src/cli/vite-config.js +478 -0
  260. package/tokens/banhaten.tokens.json +4444 -331
  261. package/docs/design-system/components/command-bar.md +0 -57
  262. package/registry/components/expanded/BannerBoard.tsx +0 -225
  263. package/registry/components/expanded/CatalogTag.tsx +0 -85
  264. package/registry/components/expanded/CommandBar.tsx +0 -386
  265. package/registry/components/expanded/IconExplorer.tsx +0 -612
  266. package/registry/components/expanded/TimelineShowcase.tsx +0 -158
  267. package/registry/components/expanded/commandBar.css +0 -470
  268. package/registry/components/expanded/iconExplorer.css +0 -764
  269. package/registry/components/expanded/iconPacks.ts +0 -866
  270. package/registry/components/expanded/tag.css +0 -148
  271. package/registry/examples/expanded/icons-demo.tsx +0 -5
  272. package/tokens/Color mode.zip +0 -0
  273. package/tokens/Numbers.zip +0 -0
  274. package/tokens/Radius.zip +0 -0
  275. package/tokens/Theme.zip +0 -0
@@ -58,7 +58,7 @@ const cardVariants = cva(
58
58
  orientation: "horizontal",
59
59
  breakpoint: "desktop",
60
60
  class:
61
- "min-h-[var(--bh-card-horizontal-min-height)] w-[min(100%,var(--bh-card-horizontal-width))]",
61
+ "min-h-[var(--bh-card-horizontal-min-height)] w-[min(100%,var(--bh-card-horizontal-width))] flex-wrap",
62
62
  },
63
63
  {
64
64
  orientation: "horizontal",
@@ -109,6 +109,20 @@ type CardActionsProps = React.ComponentProps<"div"> & {
109
109
  }
110
110
 
111
111
  type CardFooterProps = React.ComponentProps<"div">
112
+ type CardSurfaceProps = React.ComponentPropsWithoutRef<"article">
113
+
114
+ const CardSurface = React.forwardRef<HTMLElement, CardSurfaceProps>(
115
+ function CardSurface({ className, ...props }, ref) {
116
+ return (
117
+ <article
118
+ data-slot="card-surface"
119
+ ref={ref}
120
+ className={cn(cardVariants(), className)}
121
+ {...props}
122
+ />
123
+ )
124
+ }
125
+ )
112
126
 
113
127
  const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
114
128
  actions,
@@ -121,15 +135,15 @@ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
121
135
  moreButtonLabel = "Open card menu",
122
136
  onMoreClick,
123
137
  orientation = "vertical",
124
- primaryActionLabel = "Button",
138
+ primaryActionLabel,
125
139
  primaryActionProps,
126
- secondaryActionLabel = "Button",
140
+ secondaryActionLabel,
127
141
  secondaryActionProps,
128
- showDescription = true,
129
- showFooter = true,
130
- showImage = true,
131
- showMoreButton = true,
132
- showSubtitle = true,
142
+ showDescription,
143
+ showFooter,
144
+ showImage,
145
+ showMoreButton,
146
+ showSubtitle,
133
147
  subtitle,
134
148
  title,
135
149
  type = "leading-image",
@@ -142,13 +156,15 @@ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
142
156
  ? "leading-image"
143
157
  : type ?? "leading-image"
144
158
  const fullImage = cardOrientation === "vertical" && cardType === "full-image"
145
- const renderedTitle = title ?? "Card Title"
146
- const renderedSubtitle = subtitle ?? "Card Subtitle"
147
- const renderedDescription =
148
- description ??
149
- "Journey through time as we explore the mysteries of the Great Pyramids and the Sphinx."
159
+ const shouldShowDescription = showDescription ?? description !== undefined
160
+ const shouldShowFooter =
161
+ showFooter ??
162
+ Boolean(footer || actions || primaryActionLabel || secondaryActionLabel)
163
+ const shouldShowImage = showImage ?? Boolean(imageSrc)
164
+ const shouldShowMoreButton = showMoreButton ?? Boolean(onMoreClick)
165
+ const shouldShowSubtitle = showSubtitle ?? subtitle !== undefined
150
166
 
151
- const media = showImage ? (
167
+ const media = shouldShowImage ? (
152
168
  <CardMedia
153
169
  imageAlt={imageAlt}
154
170
  imageSrc={imageSrc}
@@ -160,7 +176,7 @@ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
160
176
  <CardBody
161
177
  actions={actions}
162
178
  breakpoint={cardBreakpoint}
163
- description={renderedDescription}
179
+ description={description}
164
180
  footer={footer}
165
181
  moreButtonLabel={moreButtonLabel}
166
182
  onMoreClick={onMoreClick}
@@ -169,12 +185,12 @@ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
169
185
  primaryActionProps={primaryActionProps}
170
186
  secondaryActionLabel={secondaryActionLabel}
171
187
  secondaryActionProps={secondaryActionProps}
172
- showDescription={showDescription}
173
- showFooter={showFooter && !fullImage}
174
- showMoreButton={showMoreButton}
175
- showSubtitle={showSubtitle}
176
- subtitle={renderedSubtitle}
177
- title={renderedTitle}
188
+ showDescription={shouldShowDescription}
189
+ showFooter={shouldShowFooter && !fullImage}
190
+ showMoreButton={shouldShowMoreButton}
191
+ showSubtitle={shouldShowSubtitle}
192
+ subtitle={subtitle}
193
+ title={title}
178
194
  />
179
195
  )
180
196
 
@@ -206,10 +222,10 @@ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
206
222
  breakpoint={cardBreakpoint}
207
223
  moreButtonLabel={moreButtonLabel}
208
224
  onMoreClick={onMoreClick}
209
- showMoreButton={showMoreButton}
210
- showSubtitle={showSubtitle}
211
- subtitle={renderedSubtitle}
212
- title={renderedTitle}
225
+ showMoreButton={shouldShowMoreButton}
226
+ showSubtitle={shouldShowSubtitle}
227
+ subtitle={subtitle}
228
+ title={title}
213
229
  />
214
230
  </>
215
231
  ) : cardOrientation === "horizontal" &&
@@ -275,9 +291,9 @@ const CardActions = React.forwardRef<HTMLDivElement, CardActionsProps>(
275
291
  function CardActions({
276
292
  children,
277
293
  className,
278
- primaryLabel = "Button",
294
+ primaryLabel,
279
295
  primaryProps,
280
- secondaryLabel = "Button",
296
+ secondaryLabel,
281
297
  secondaryProps,
282
298
  ...props
283
299
  }, ref) {
@@ -286,23 +302,28 @@ const CardActions = React.forwardRef<HTMLDivElement, CardActionsProps>(
286
302
  data-slot="card-actions"
287
303
  ref={ref}
288
304
  className={cn(
289
- "grid w-full max-w-[var(--bh-card-actions-max-width)] grid-cols-2 gap-[var(--bh-space-xl-12)]",
305
+ "grid w-full max-w-[var(--bh-card-actions-max-width)] gap-[var(--bh-space-xl-12)]",
306
+ primaryLabel && secondaryLabel ? "grid-cols-2" : "grid-cols-1",
290
307
  className
291
308
  )}
292
309
  {...props}
293
310
  >
294
311
  {children ?? (
295
312
  <>
296
- <Button {...primaryProps} className={cn("min-w-0", primaryProps?.className)}>
297
- {primaryLabel}
298
- </Button>
299
- <Button
300
- variant="soft"
301
- {...secondaryProps}
302
- className={cn("min-w-0", secondaryProps?.className)}
303
- >
304
- {secondaryLabel}
305
- </Button>
313
+ {primaryLabel ? (
314
+ <Button {...primaryProps} className={cn("min-w-0", primaryProps?.className)}>
315
+ {primaryLabel}
316
+ </Button>
317
+ ) : null}
318
+ {secondaryLabel ? (
319
+ <Button
320
+ variant="soft"
321
+ {...secondaryProps}
322
+ className={cn("min-w-0", secondaryProps?.className)}
323
+ >
324
+ {secondaryLabel}
325
+ </Button>
326
+ ) : null}
306
327
  </>
307
328
  )}
308
329
  </div>
@@ -331,21 +352,21 @@ function CardBody({
331
352
  }: {
332
353
  actions?: React.ReactNode
333
354
  breakpoint: CardBreakpoint
334
- description: React.ReactNode
355
+ description?: React.ReactNode
335
356
  footer?: React.ReactNode
336
357
  moreButtonLabel: string
337
358
  onMoreClick?: React.MouseEventHandler<HTMLButtonElement>
338
359
  orientation: CardOrientation
339
- primaryActionLabel: React.ReactNode
360
+ primaryActionLabel?: React.ReactNode
340
361
  primaryActionProps?: ButtonProps
341
- secondaryActionLabel: React.ReactNode
362
+ secondaryActionLabel?: React.ReactNode
342
363
  secondaryActionProps?: ButtonProps
343
364
  showDescription: boolean
344
365
  showFooter: boolean
345
366
  showMoreButton: boolean
346
367
  showSubtitle: boolean
347
- subtitle: React.ReactNode
348
- title: React.ReactNode
368
+ subtitle?: React.ReactNode
369
+ title?: React.ReactNode
349
370
  }) {
350
371
  const horizontal = orientation === "horizontal"
351
372
 
@@ -355,7 +376,7 @@ function CardBody({
355
376
  className={cn(
356
377
  "flex min-w-0 flex-col",
357
378
  horizontal
358
- ? "flex-1 gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-horizontal-padding)]"
379
+ ? "min-w-[min(100%,var(--bh-card-vertical-width))] flex-[1_1_var(--bh-card-vertical-width)] gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-horizontal-padding)]"
359
380
  : "w-full gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-content-padding)]"
360
381
  )}
361
382
  >
@@ -378,7 +399,7 @@ function CardBody({
378
399
  title={title}
379
400
  subtitleClassName={getSubtitleClassName(breakpoint)}
380
401
  />
381
- {showDescription ? (
402
+ {showDescription && description !== undefined ? (
382
403
  <p
383
404
  data-slot="card-description"
384
405
  dir="auto"
@@ -423,8 +444,8 @@ function CardFullImageContent({
423
444
  onMoreClick?: React.MouseEventHandler<HTMLButtonElement>
424
445
  showMoreButton: boolean
425
446
  showSubtitle: boolean
426
- subtitle: React.ReactNode
427
- title: React.ReactNode
447
+ subtitle?: React.ReactNode
448
+ title?: React.ReactNode
428
449
  }) {
429
450
  return (
430
451
  <div
@@ -475,20 +496,22 @@ function CardHeader({
475
496
  className="flex min-w-0 items-start gap-[var(--bh-space-3xl-16)]"
476
497
  >
477
498
  <div className="flex min-w-0 flex-1 flex-col gap-[var(--bh-space-xxs-2)]">
478
- <h3
479
- data-slot="card-title"
480
- dir="auto"
481
- className={cn(
482
- "min-w-0 break-words text-[length:var(--bh-text-heading-sm-semibold-font-size)]",
483
- "font-[var(--bh-text-heading-sm-semibold-font-weight)]",
484
- "leading-[var(--bh-text-heading-sm-semibold-line-height)]",
485
- "tracking-[var(--bh-text-heading-sm-semibold-letter-spacing)]",
486
- onImage ? "text-[var(--bh-content-on-color)]" : "text-[var(--bh-content-default)]"
487
- )}
488
- >
489
- {title}
490
- </h3>
491
- {showSubtitle ? (
499
+ {title !== undefined ? (
500
+ <h3
501
+ data-slot="card-title"
502
+ dir="auto"
503
+ className={cn(
504
+ "min-w-0 break-words text-[length:var(--bh-text-heading-sm-semibold-font-size)]",
505
+ "font-[var(--bh-text-heading-sm-semibold-font-weight)]",
506
+ "leading-[var(--bh-text-heading-sm-semibold-line-height)]",
507
+ "tracking-[var(--bh-text-heading-sm-semibold-letter-spacing)]",
508
+ onImage ? "text-[var(--bh-content-on-color)]" : "text-[var(--bh-content-default)]"
509
+ )}
510
+ >
511
+ {title}
512
+ </h3>
513
+ ) : null}
514
+ {showSubtitle && subtitle !== undefined ? (
492
515
  <p
493
516
  data-slot="card-subtitle"
494
517
  dir="auto"
@@ -557,7 +580,7 @@ function getCardMediaClassName(
557
580
  breakpoint: CardBreakpoint
558
581
  ) {
559
582
  if (orientation === "horizontal" && breakpoint === "desktop") {
560
- return "h-auto w-[var(--bh-card-horizontal-media-width)]"
583
+ return "min-h-[var(--bh-card-media-height)] w-[min(100%,var(--bh-card-horizontal-media-width))] shrink flex-[1_1_var(--bh-card-horizontal-media-width)]"
561
584
  }
562
585
 
563
586
  return "h-[var(--bh-card-media-height)] w-full"
@@ -602,6 +625,7 @@ export {
602
625
  CardActions,
603
626
  CardFooter,
604
627
  CardMedia,
628
+ CardSurface,
605
629
  cardVariants,
606
630
  type CardActionsProps,
607
631
  type CardBreakpoint,
@@ -609,5 +633,6 @@ export {
609
633
  type CardMediaProps,
610
634
  type CardOrientation,
611
635
  type CardProps,
636
+ type CardSurfaceProps,
612
637
  type CardType,
613
638
  }
@@ -8,6 +8,7 @@ import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
8
8
 
9
9
  import { Button } from "@/components/ui/button"
10
10
  import { cn } from "@/lib/utils"
11
+ import { useResolvedDirection, type Direction } from "@/hooks/use-direction"
11
12
 
12
13
  type CarouselApi = UseEmblaCarouselType[1]
13
14
  type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
@@ -15,7 +16,29 @@ type CarouselOptions = UseCarouselParameters[0]
15
16
  type CarouselPlugin = UseCarouselParameters[1]
16
17
  type CarouselOrientation = "horizontal" | "vertical"
17
18
 
19
+ type CarouselMessages = {
20
+ carouselLabel: string
21
+ carouselRoleDescription: string
22
+ nextSlide: string
23
+ previousSlide: string
24
+ }
25
+
26
+ const defaultCarouselMessages: CarouselMessages = {
27
+ carouselLabel: "Carousel",
28
+ carouselRoleDescription: "carousel",
29
+ nextSlide: "Next slide",
30
+ previousSlide: "Previous slide",
31
+ }
32
+
33
+ const arabicCarouselMessages: CarouselMessages = {
34
+ carouselLabel: "\u0639\u0627\u0631\u0636 \u0627\u0644\u0634\u0631\u0627\u0626\u062d",
35
+ carouselRoleDescription: "\u0639\u0627\u0631\u0636 \u0634\u0631\u0627\u0626\u062d",
36
+ nextSlide: "\u0627\u0644\u0634\u0631\u064a\u062d\u0629 \u0627\u0644\u062a\u0627\u0644\u064a\u0629",
37
+ previousSlide: "\u0627\u0644\u0634\u0631\u064a\u062d\u0629 \u0627\u0644\u0633\u0627\u0628\u0642\u0629",
38
+ }
39
+
18
40
  type CarouselProps = React.ComponentProps<"div"> & {
41
+ messages?: Partial<CarouselMessages>
19
42
  opts?: CarouselOptions
20
43
  orientation?: CarouselOrientation
21
44
  plugins?: CarouselPlugin
@@ -29,6 +52,8 @@ type CarouselContextValue = {
29
52
  api: CarouselApi
30
53
  canScrollNext: boolean
31
54
  canScrollPrev: boolean
55
+ direction: Direction
56
+ messages: CarouselMessages
32
57
  orientation: CarouselOrientation
33
58
  scrollNext: () => void
34
59
  scrollPrev: () => void
@@ -39,8 +64,13 @@ const CarouselContext = React.createContext<CarouselContextValue | null>(null)
39
64
  const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
40
65
  function Carousel(
41
66
  {
67
+ "aria-label": ariaLabel,
68
+ "aria-labelledby": ariaLabelledBy,
69
+ "aria-roledescription": ariaRoleDescription,
42
70
  children,
43
71
  className,
72
+ dir,
73
+ messages: messageOverrides,
44
74
  opts,
45
75
  orientation = "horizontal",
46
76
  plugins,
@@ -49,15 +79,33 @@ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
49
79
  },
50
80
  ref
51
81
  ) {
82
+ const rootRef = React.useRef<HTMLDivElement | null>(null)
83
+ const direction = useResolvedDirection(
84
+ dir === "ltr" || dir === "rtl" ? dir : undefined,
85
+ rootRef
86
+ )
87
+ const setRootRef = React.useCallback(
88
+ (node: HTMLDivElement | null) => {
89
+ rootRef.current = node
90
+ if (typeof ref === "function") ref(node)
91
+ else if (ref) ref.current = node
92
+ },
93
+ [ref]
94
+ )
52
95
  const [carouselRef, api] = useEmblaCarousel(
53
96
  {
54
97
  ...opts,
55
98
  axis: orientation === "horizontal" ? "x" : "y",
99
+ direction,
56
100
  },
57
101
  plugins
58
102
  )
59
103
  const [canScrollPrev, setCanScrollPrev] = React.useState(false)
60
104
  const [canScrollNext, setCanScrollNext] = React.useState(false)
105
+ const messages = React.useMemo(
106
+ () => ({ ...defaultCarouselMessages, ...messageOverrides }),
107
+ [messageOverrides]
108
+ )
61
109
 
62
110
  const onSelect = React.useCallback((nextApi: CarouselApi) => {
63
111
  if (!nextApi) return
@@ -86,6 +134,8 @@ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
86
134
  api,
87
135
  canScrollNext,
88
136
  canScrollPrev,
137
+ direction,
138
+ messages,
89
139
  orientation,
90
140
  scrollNext,
91
141
  scrollPrev,
@@ -94,8 +144,14 @@ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
94
144
  <div
95
145
  data-orientation={orientation}
96
146
  data-slot="carousel"
97
- ref={ref}
147
+ dir={direction}
148
+ ref={setRootRef}
98
149
  role="region"
150
+ aria-label={ariaLabelledBy ? undefined : (ariaLabel ?? messages.carouselLabel)}
151
+ aria-labelledby={ariaLabelledBy}
152
+ aria-roledescription={
153
+ ariaRoleDescription ?? messages.carouselRoleDescription
154
+ }
99
155
  className={cn("relative", className)}
100
156
  {...props}
101
157
  >
@@ -153,11 +209,13 @@ const CarouselItem = React.forwardRef<HTMLDivElement, CarouselItemProps>(
153
209
 
154
210
  const CarouselPrevious = React.forwardRef<HTMLButtonElement, CarouselButtonProps>(
155
211
  function CarouselPrevious({ className, variant = "outline", size = "icon", ...props }, ref) {
156
- const { canScrollPrev, orientation, scrollPrev } = useCarousel()
212
+ const { canScrollPrev, messages, orientation, scrollPrev } = useCarousel()
157
213
 
158
214
  return (
159
215
  <Button
160
- aria-label="Previous slide"
216
+ aria-label={
217
+ props["aria-label"] ?? messages.previousSlide
218
+ }
161
219
  className={cn(
162
220
  "absolute size-[var(--bh-space-7xl-40)] rounded-[var(--bh-radius-full)]",
163
221
  orientation === "horizontal"
@@ -181,11 +239,13 @@ const CarouselPrevious = React.forwardRef<HTMLButtonElement, CarouselButtonProps
181
239
 
182
240
  const CarouselNext = React.forwardRef<HTMLButtonElement, CarouselButtonProps>(
183
241
  function CarouselNext({ className, variant = "outline", size = "icon", ...props }, ref) {
184
- const { canScrollNext, orientation, scrollNext } = useCarousel()
242
+ const { canScrollNext, messages, orientation, scrollNext } = useCarousel()
185
243
 
186
244
  return (
187
245
  <Button
188
- aria-label="Next slide"
246
+ aria-label={
247
+ props["aria-label"] ?? messages.nextSlide
248
+ }
189
249
  className={cn(
190
250
  "absolute size-[var(--bh-space-7xl-40)] rounded-[var(--bh-radius-full)]",
191
251
  orientation === "horizontal"
@@ -216,6 +276,7 @@ function useCarousel() {
216
276
  }
217
277
 
218
278
  export {
279
+ arabicCarouselMessages,
219
280
  Carousel,
220
281
  CarouselContent,
221
282
  CarouselItem,
@@ -227,6 +288,7 @@ export type {
227
288
  CarouselButtonProps,
228
289
  CarouselContentProps,
229
290
  CarouselItemProps,
291
+ CarouselMessages,
230
292
  CarouselOptions,
231
293
  CarouselOrientation,
232
294
  CarouselPlugin,
@@ -37,6 +37,17 @@ type ChartLegendContentProps = React.ComponentProps<"div"> & {
37
37
  payload?: ChartPayloadItem[]
38
38
  }
39
39
 
40
+ const chartCategoricalTokens = [
41
+ "--bh-chart-categorical-brand",
42
+ "--bh-chart-categorical-success",
43
+ "--bh-chart-categorical-warning",
44
+ "--bh-chart-categorical-danger",
45
+ "--bh-chart-categorical-information",
46
+ "--bh-chart-categorical-purple",
47
+ "--bh-chart-categorical-aqua",
48
+ "--bh-chart-categorical-fuchsia",
49
+ ] as const
50
+
40
51
  function ChartContainer({
41
52
  children,
42
53
  className,
@@ -62,6 +73,26 @@ function ChartContainer({
62
73
  const ChartTooltip = RechartsTooltip
63
74
  const ChartLegend = RechartsLegend
64
75
 
76
+ /**
77
+ * Recharts clones the element passed as `content` and injects its own internal
78
+ * props (`iconSize`, `verticalAlign`, `chartWidth`, `itemSorter`, …). Those are
79
+ * not valid DOM attributes, so forward only what a presentational element can
80
+ * legitimately receive.
81
+ */
82
+ function forwardableDomProps(props: Record<string, unknown>) {
83
+ return Object.fromEntries(
84
+ Object.entries(props).filter(
85
+ ([key]) =>
86
+ key === "id" ||
87
+ key === "role" ||
88
+ key === "style" ||
89
+ key === "tabIndex" ||
90
+ key.startsWith("aria-") ||
91
+ key.startsWith("data-")
92
+ )
93
+ )
94
+ }
95
+
65
96
  function ChartTooltipContent({
66
97
  active,
67
98
  className,
@@ -79,7 +110,7 @@ function ChartTooltipContent({
79
110
  "grid min-w-[var(--bh-space-17xl-288)] gap-[var(--bh-space-sm-6)] rounded-[var(--bh-radius-lg-8)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-xl-12)] text-[length:var(--bh-text-body-xs-regular-font-size)] font-[var(--bh-text-body-xs-regular-font-weight)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)] text-[var(--bh-content-default)] shadow-[var(--shadow-popover)]",
80
111
  className
81
112
  )}
82
- {...props}
113
+ {...forwardableDomProps(props)}
83
114
  >
84
115
  {!hideLabel && label ? (
85
116
  <div className="font-[var(--bh-text-body-xs-medium-font-weight)]">
@@ -121,7 +152,7 @@ function ChartLegendContent({
121
152
  <div
122
153
  data-slot="chart-legend"
123
154
  className={cn("flex flex-wrap items-center justify-center gap-[var(--bh-space-3xl-16)]", className)}
124
- {...props}
155
+ {...forwardableDomProps(props)}
125
156
  >
126
157
  {payload.map((item, index) => (
127
158
  <div
@@ -146,7 +177,9 @@ function getChartStyle(config?: ChartConfig): React.CSSProperties {
146
177
  return Object.entries(config).reduce<React.CSSProperties>(
147
178
  (style, [key, value], index) => ({
148
179
  ...style,
149
- [`--color-${key}`]: value.color ?? `var(--bh-chart-${(index % 5) + 1})`,
180
+ [`--color-${key}`]:
181
+ value.color ??
182
+ `var(${chartCategoricalTokens[index % chartCategoricalTokens.length]})`,
150
183
  }),
151
184
  {}
152
185
  )
@@ -63,13 +63,13 @@ const Checkbox = React.forwardRef<
63
63
  aria-hidden="true"
64
64
  className="size-[var(--bh-checkbox-icon-size)] group-data-[state=indeterminate]/checkbox:hidden"
65
65
  focusable="false"
66
- strokeWidth={3}
66
+ strokeWidth="var(--bh-icon-stroke-300)"
67
67
  />
68
68
  <MinusIcon
69
69
  aria-hidden="true"
70
70
  className="hidden size-[var(--bh-checkbox-icon-size)] group-data-[state=indeterminate]/checkbox:block"
71
71
  focusable="false"
72
- strokeWidth={3}
72
+ strokeWidth="var(--bh-icon-stroke-300)"
73
73
  />
74
74
  </CheckboxPrimitive.Indicator>
75
75
  </CheckboxPrimitive.Root>
@@ -231,7 +231,7 @@ function CheckboxCardIndicator({
231
231
  aria-hidden="true"
232
232
  className="absolute top-1/2 left-1/2 size-[var(--bh-checkbox-icon-size)] -translate-x-1/2 -translate-y-1/2 text-[var(--bh-content-on-color)]"
233
233
  focusable="false"
234
- strokeWidth={3}
234
+ strokeWidth="var(--bh-icon-stroke-300)"
235
235
  />
236
236
  ) : null}
237
237
  </span>
@@ -38,7 +38,7 @@ const CollapsibleTrigger = React.forwardRef<
38
38
  <ChevronDownIcon
39
39
  aria-hidden="true"
40
40
  className="size-[var(--bh-space-3xl-16)] shrink-0 text-[var(--bh-content-subtle)] transition-transform group-data-[state=open]/collapsible-trigger:rotate-180"
41
- strokeWidth={2}
41
+ strokeWidth="var(--bh-icon-stroke-200)"
42
42
  />
43
43
  </CollapsiblePrimitive.Trigger>
44
44
  )
@@ -44,7 +44,7 @@ const CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(
44
44
  <SearchIcon
45
45
  aria-hidden="true"
46
46
  className="size-[var(--bh-space-5xl-24)] shrink-0 text-[var(--bh-content-muted)]"
47
- strokeWidth={2}
47
+ strokeWidth="var(--bh-icon-stroke-200)"
48
48
  />
49
49
  <CommandPrimitive.Input
50
50
  data-slot="command-input"
@@ -63,10 +63,27 @@ const CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(
63
63
 
64
64
  const CommandList = React.forwardRef<HTMLDivElement, CommandListProps>(
65
65
  function CommandList({ className, ...props }, ref) {
66
+ const listRef = React.useRef<HTMLDivElement | null>(null)
67
+ React.useLayoutEffect(() => {
68
+ listRef.current
69
+ ?.querySelector<HTMLElement>("[cmdk-list-sizer]")
70
+ ?.setAttribute("role", "presentation")
71
+ })
72
+
73
+ const setListRef = React.useCallback(
74
+ (node: HTMLDivElement | null) => {
75
+ listRef.current = node
76
+
77
+ if (typeof ref === "function") ref(node)
78
+ else if (ref) ref.current = node
79
+ },
80
+ [ref]
81
+ )
82
+
66
83
  return (
67
84
  <CommandPrimitive.List
68
85
  data-slot="command-list"
69
- ref={ref}
86
+ ref={setListRef}
70
87
  className={cn(
71
88
  "max-h-[calc(var(--bh-space-19xl-384)+var(--bh-space-8xl-48))] overflow-y-auto overflow-x-hidden p-[var(--bh-space-md-8)]",
72
89
  className
@@ -79,10 +96,25 @@ const CommandList = React.forwardRef<HTMLDivElement, CommandListProps>(
79
96
 
80
97
  const CommandEmpty = React.forwardRef<HTMLDivElement, CommandEmptyProps>(
81
98
  function CommandEmpty({ className, ...props }, ref) {
99
+ const emptyRef = React.useRef<HTMLDivElement | null>(null)
100
+ React.useLayoutEffect(() => {
101
+ emptyRef.current?.setAttribute("role", "option")
102
+ emptyRef.current?.setAttribute("aria-disabled", "true")
103
+ emptyRef.current?.setAttribute("aria-selected", "false")
104
+ })
105
+ const setEmptyRef = React.useCallback(
106
+ (node: HTMLDivElement | null) => {
107
+ emptyRef.current = node
108
+ if (typeof ref === "function") ref(node)
109
+ else if (ref) ref.current = node
110
+ },
111
+ [ref]
112
+ )
113
+
82
114
  return (
83
115
  <CommandPrimitive.Empty
84
116
  data-slot="command-empty"
85
- ref={ref}
117
+ ref={setEmptyRef}
86
118
  className={cn(
87
119
  "py-[var(--bh-space-8xl-48)] text-center 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-subtle)]",
88
120
  className
@@ -130,9 +162,11 @@ const CommandSeparator = React.forwardRef<
130
162
  CommandSeparatorProps
131
163
  >(function CommandSeparator({ className, ...props }, ref) {
132
164
  return (
133
- <CommandPrimitive.Separator
165
+ <div
134
166
  data-slot="command-separator"
135
167
  ref={ref}
168
+ role="presentation"
169
+ aria-hidden="true"
136
170
  className={cn("-mx-[var(--bh-space-md-8)] my-[var(--bh-space-md-8)] h-[var(--bh-border-width-default)] bg-[var(--bh-border-subtle)]", className)}
137
171
  {...props}
138
172
  />
@@ -53,7 +53,7 @@ const menuContentClassName =
53
53
  "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)]"
54
54
 
55
55
  const menuItemClassName =
56
- "relative flex min-h-[var(--bh-menu-item-min-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)]"
56
+ "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)]"
57
57
 
58
58
  const ContextMenuContent = React.forwardRef<
59
59
  React.ElementRef<typeof ContextMenuPrimitive.Content>,