banhaten 0.2.0 → 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 (101) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +1 -1
  3. package/docs/design-system/component-implementation-ownership-audit.md +244 -0
  4. package/docs/design-system/component-quality-audit-2026-08-20.md +223 -0
  5. package/docs/design-system/component-showcase-consistency-report.md +2 -0
  6. package/docs/design-system/components/README.md +1 -0
  7. package/docs/design-system/components/activity-feed.md +2 -0
  8. package/docs/design-system/components/activity-heatmap.md +73 -0
  9. package/docs/design-system/components/alert-dialog.md +1 -1
  10. package/docs/design-system/components/alert.md +2 -2
  11. package/docs/design-system/components/avatar.md +1 -1
  12. package/docs/design-system/components/badge.md +1 -1
  13. package/docs/design-system/components/banner.md +5 -1
  14. package/docs/design-system/components/breadcrumbs.md +3 -0
  15. package/docs/design-system/components/button-group.md +1 -0
  16. package/docs/design-system/components/button.md +1 -1
  17. package/docs/design-system/components/card.md +11 -3
  18. package/docs/design-system/components/catalog-components.md +3 -0
  19. package/docs/design-system/components/date-picker.md +1 -1
  20. package/docs/design-system/components/file-upload.md +4 -0
  21. package/docs/design-system/components/modal.md +1 -1
  22. package/docs/design-system/components/navigation-menu.md +1 -1
  23. package/docs/design-system/components/onboarding-step-list-item.md +1 -0
  24. package/docs/design-system/components/page-header.md +3 -1
  25. package/docs/design-system/components/pagination.md +1 -0
  26. package/docs/design-system/components/resizable.md +1 -1
  27. package/docs/design-system/components/scroll-area.md +7 -3
  28. package/docs/design-system/components/sidebar.md +3 -1
  29. package/docs/design-system/components/slideout.md +4 -1
  30. package/docs/design-system/components/steps.md +7 -2
  31. package/docs/design-system/components/table.md +25 -7
  32. package/docs/design-system/components/tag.md +1 -1
  33. package/docs/design-system/components/timeline.md +4 -1
  34. package/docs/design-system/components/toggle.md +1 -1
  35. package/docs/design-system/components/toolbar.md +1 -1
  36. package/docs/design-system/components/tooltip.md +1 -1
  37. package/docs/design-system/density-and-elevation.md +18 -1
  38. package/docs/design-system/gap-promotion-report.md +1 -67
  39. package/docs/design-system/launch-comparison-shadcn.md +10 -10
  40. package/package.json +3 -2
  41. package/registry/components/alert-dialog.tsx +1 -1
  42. package/registry/components/alert.tsx +15 -11
  43. package/registry/components/avatar.tsx +7 -3
  44. package/registry/components/badge.tsx +17 -17
  45. package/registry/components/button-group.tsx +3 -1
  46. package/registry/components/button.tsx +2 -2
  47. package/registry/components/card.tsx +19 -3
  48. package/registry/components/carousel.tsx +14 -0
  49. package/registry/components/command.tsx +37 -3
  50. package/registry/components/date-picker.tsx +67 -31
  51. package/registry/components/expanded/ActivityFeed.tsx +3 -7
  52. package/registry/components/expanded/ActivityHeatmap.tsx +64 -0
  53. package/registry/components/expanded/Banner.tsx +0 -1
  54. package/registry/components/expanded/Breadcrumbs.tsx +3 -0
  55. package/registry/components/expanded/CatalogComponentsShowcase.tsx +3 -4
  56. package/registry/components/expanded/CatalogDivider.tsx +3 -2
  57. package/registry/components/expanded/FileUpload.tsx +7 -5
  58. package/registry/components/expanded/OnboardingStepListItem.tsx +0 -2
  59. package/registry/components/expanded/PageHeader.tsx +35 -44
  60. package/registry/components/expanded/Slideout.tsx +18 -13
  61. package/registry/components/expanded/Steps.tsx +147 -9
  62. package/registry/components/expanded/Table.tsx +50 -18
  63. package/registry/components/expanded/Timeline.tsx +11 -24
  64. package/registry/components/expanded/activityFeed.css +8 -8
  65. package/registry/components/expanded/activityHeatmap.css +66 -0
  66. package/registry/components/expanded/banner.css +21 -11
  67. package/registry/components/expanded/breadcrumbs.css +1 -1
  68. package/registry/components/expanded/fileUpload.css +1 -1
  69. package/registry/components/expanded/onboardingStepListItem.css +2 -2
  70. package/registry/components/expanded/pageHeader.css +14 -11
  71. package/registry/components/expanded/slideout.css +3 -7
  72. package/registry/components/expanded/steps.css +75 -12
  73. package/registry/components/expanded/table.css +58 -67
  74. package/registry/components/expanded/timeline.css +17 -16
  75. package/registry/components/field.tsx +61 -8
  76. package/registry/components/menu.tsx +56 -6
  77. package/registry/components/modal.tsx +1 -1
  78. package/registry/components/navigation-menu.tsx +1 -1
  79. package/registry/components/pagination.css +36 -0
  80. package/registry/components/pagination.tsx +8 -6
  81. package/registry/components/progress-slider.tsx +1 -1
  82. package/registry/components/resizable.tsx +36 -2
  83. package/registry/components/scroll-area.tsx +30 -6
  84. package/registry/components/select.tsx +34 -11
  85. package/registry/components/sidebar.tsx +11 -5
  86. package/registry/components/social-button.tsx +12 -5
  87. package/registry/components/table-elements.tsx +128 -0
  88. package/registry/components/tabs.tsx +5 -10
  89. package/registry/components/tag.tsx +1 -1
  90. package/registry/components/toggle.tsx +2 -2
  91. package/registry/components/toolbar.tsx +5 -5
  92. package/registry/components/tooltip.tsx +1 -1
  93. package/registry/examples/avatar-demo.tsx +7 -4
  94. package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
  95. package/registry/examples/expanded/breadcrumbs-demo.tsx +3 -3
  96. package/registry/examples/pagination-demo.tsx +5 -5
  97. package/registry/examples/sidebar-demo.tsx +4 -2
  98. package/registry/index.json +392 -33
  99. package/registry/styles/globals.css +68 -42
  100. package/registry.json +40 -3
  101. package/tokens/banhaten.tokens.json +165 -55
@@ -2,7 +2,7 @@
2
2
 
3
3
  This file is generated from background scenario gap metadata. A candidate appears when the same normalized need occurs in at least three distinct backgrounds. Heuristics cover common shells, navigation, tables, steps, composers, timelines, assets, and elevation; use `promotionKey` on a gap when an explicit cross-background grouping is more accurate.
4
4
 
5
- Scanned 194 gaps across 36 backgrounds. Found 8 promotion candidates at the 3-background threshold.
5
+ Scanned 100 gaps across 38 backgrounds. Found 4 promotion candidates at the 3-background threshold.
6
6
 
7
7
  ## Application/workspace shell
8
8
 
@@ -58,46 +58,6 @@ Backgrounds: `ai-growth-marketing`, `ai-node-detail`, `bauhow-room-planning`, `c
58
58
  | `shipment-list` | carrier brand mark (`shipment-carrier-brand-mark`) | icon asset | approximation |
59
59
  | `sku-management` | product and assistant icon set (`sku-product-icons`) | icon asset | approximation |
60
60
 
61
- ## Sidebar navigation
62
-
63
- Key: `sidebar-navigation` · 11 backgrounds
64
-
65
- Backgrounds: `acme-finance-advisor`, `ai-growth-marketing`, `audit-log`, `bauhow-room-planning`, `data-source-browser`, `debt-payoff`, `marcohr-people`, `people-add-member`, `shipment-list`, `sku-management`, `stripe-demo-account`
66
-
67
- | Background | Gap | Kind | Status |
68
- | --- | --- | --- | --- |
69
- | `acme-finance-advisor` | compact app sidebar navigation (`acme-sidebar-navigation`) | component | missing |
70
- | `ai-growth-marketing` | report sidebar navigation (`report-sidebar-navigation`) | component | missing |
71
- | `audit-log` | audit sidebar navigation (`audit-sidebar-navigation`) | component | missing |
72
- | `bauhow-room-planning` | planning sidebar navigation (`room-planning-sidebar-navigation`) | component | missing |
73
- | `data-source-browser` | sidebar navigation with pinned list (`sidebar-navigation-with-pinned-list`) | component | missing |
74
- | `debt-payoff` | planning sidebar navigation (`planning-sidebar-navigation`) | component | missing |
75
- | `marcohr-people` | HR sidebar navigation (`marcohr-sidebar-navigation`) | component | missing |
76
- | `people-add-member` | HR sidebar navigation (`people-sidebar-navigation`) | component | missing |
77
- | `shipment-list` | shipment sidebar navigation (`shipment-sidebar-navigation`) | component | missing |
78
- | `sku-management` | cropped app sidebar navigation (`sku-sidebar-navigation`) | component | missing |
79
- | `stripe-demo-account` | payments sidebar navigation (`payments-sidebar-navigation`) | component | missing |
80
-
81
- ## Data table pattern
82
-
83
- Key: `data-table-pattern` · 10 backgrounds
84
-
85
- Backgrounds: `audit-log`, `debt-payoff`, `inference-providers`, `marcohr-people`, `people-add-member`, `recent-orders`, `school-assignments`, `share-of-voice`, `sku-management`, `stripe-demo-account`
86
-
87
- | Background | Gap | Kind | Status |
88
- | --- | --- | --- | --- |
89
- | `audit-log` | audit activity table (`audit-activity-table`) | component | missing |
90
- | `debt-payoff` | debt table and pagination (`debt-table-and-pagination`) | component | approximation |
91
- | `inference-providers` | expandable provider configuration table (`expandable-provider-table`) | component | missing |
92
- | `marcohr-people` | employee policy table (`marcohr-employee-table`) | component | approximation |
93
- | `people-add-member` | people table with status rows (`people-table-and-status-rows`) | component | approximation |
94
- | `recent-orders` | orders table controls and rows (`orders-table-region`) | component | approximation |
95
- | `school-assignments` | assignments table (`assignments-table`) | component | missing |
96
- | `share-of-voice` | analytics table density (`share-of-voice-analytics-table`) | component | approximation |
97
- | `sku-management` | SKU data table and pagination (`sku-data-table`) | component | approximation |
98
- | `stripe-demo-account` | metric table cards (`metric-table-cards`) | component | approximation |
99
- | `stripe-demo-account` | sales tabs and table (`sales-tabs-and-table`) | component | approximation |
100
-
101
61
  ## Elevation/blur semantic effects
102
62
 
103
63
  Key: `elevation-effects` · 10 backgrounds
@@ -135,32 +95,6 @@ Backgrounds: `delivery-tracking`, `investor-profile`, `mcp-settings`, `profile-m
135
95
  | `school-assignments` | teacher avatar (`teacher-avatar`) | image asset | missing |
136
96
  | `stripe-demo-account` | Stripe mark and account avatar (`stripe-brand-and-avatar-assets`) | image asset | approximation |
137
97
 
138
- ## Progress/step indicator
139
-
140
- Key: `progress-step-indicator` · 5 backgrounds
141
-
142
- Backgrounds: `accounting-workspace-setup`, `benefits-enrollment`, `mcp-settings`, `quran-audio-add-employee`, `refund-simulation`
143
-
144
- | Background | Gap | Kind | Status |
145
- | --- | --- | --- | --- |
146
- | `accounting-workspace-setup` | step progress indicator (`setup-progress-indicator`) | component | approximation |
147
- | `benefits-enrollment` | enrollment progress header (`enrollment-progress-header`) | component | missing |
148
- | `mcp-settings` | numbered setup steps (`numbered-setup-steps`) | component | missing |
149
- | `quran-audio-add-employee` | three-step employee progress indicator (`quran-audio-progress-stepper`) | component | approximation |
150
- | `refund-simulation` | refund progress timeline (`refund-progress-timeline`) | component | missing |
151
-
152
- ## Composer pattern
153
-
154
- Key: `composer-pattern` · 3 backgrounds
155
-
156
- Backgrounds: `acme-finance-advisor`, `email-compose`, `order-shipment-timeline`
157
-
158
- | Background | Gap | Kind | Status |
159
- | --- | --- | --- | --- |
160
- | `acme-finance-advisor` | advisor prompt composer (`acme-advisor-composer`) | component | approximation |
161
- | `email-compose` | compose modal shell (`compose-modal-shell`) | layout/composition pattern | missing |
162
- | `order-shipment-timeline` | note composer (`note-composer`) | component | missing |
163
-
164
98
  ## Review rule
165
99
 
166
100
  A report entry is a prioritization signal, not automatic authorization to add a primitive. Review the mapped backgrounds, define the smallest stable API, migrate at least one real background, and remove the resolved gap metadata only after behavior, RTL, accessibility, and visual checks pass.
@@ -1,12 +1,12 @@
1
1
  # Launch Comparison: Banhaten and shadcn/ui
2
2
 
3
- Reviewed against the official shadcn/ui CLI, registry, theming, and RTL documentation on 2026-07-10. shadcn/ui is the distribution and documentation benchmark—not Banhaten’s visual source.
3
+ Benchmark references reviewed against the official shadcn/ui CLI, registry, theming, and RTL documentation on 2026-07-10; Banhaten implementation status refreshed on 2026-08-20. shadcn/ui is the distribution and documentation benchmark—not Banhaten’s visual source.
4
4
 
5
5
  ## Outcome
6
6
 
7
7
  Banhaten is a launch candidate with a distinct product contract: Arabic and English-first typography, inherited RTL by default, seven brand themes, three radius modes, stable primitives separated from larger Patterns, and a hard rule that distributable components consume canonical Banhaten tokens.
8
8
 
9
- The only intentionally unresolved visual exception is color contrast. The reviewed default-theme correction composes blue brand roles with the gray neutral ramp; the remaining source color pairs are documented rather than changed automatically. The Color Contrast page evaluates 742 theme/mode scenarios, reports 274 failures and 468 passes, and records the pair, score, threshold, state, swatches, recommendation, and best existing foreground candidate.
9
+ The former color-contrast exception is closed. The reviewed semantic-role corrections preserve the Banhaten palette while selecting valid foreground/background roles across themes and states. The Color Contrast page now evaluates 910 theme/mode scenarios and reports **910 passes and 0 failures**, with every pair, score, threshold, state, and swatch retained as inspectable evidence.
10
10
 
11
11
  ## Benchmark Summary
12
12
 
@@ -14,7 +14,7 @@ The only intentionally unresolved visual exception is color contrast. The review
14
14
  | --- | --- | --- | --- |
15
15
  | Distribution | Source registry and CLI installation | Canonical Banhaten registry plus generated shadcn-compatible `registry.json` | Verified |
16
16
  | Component ownership | Copied source is consumer-owned | Receipt-backed generated ownership; local drift blocks updates unless explicitly forced | Stronger safety |
17
- | Registry dependencies | Dependency closure and typed registry items | Dependency graph, schema validation, mirror checks, and isolated compilation of all 67 items | Verified |
17
+ | Registry dependencies | Dependency closure and typed registry items | Dependency graph, schema validation, mirror checks, and isolated compilation of all 68 items | Verified |
18
18
  | CLI | `init`, `add`, `view`, `docs`, migrations, presets | `init`, `add`, real `diff`, safe `update`, `list`, `search`, full `docs`, and `view` | Launch scope verified |
19
19
  | Framework setup | Multiple framework templates | Vite auto-configuration and framework-neutral/Next-style source installation | Verified |
20
20
  | Package managers | npm, pnpm, Yarn, Bun | Detection and install hints tested for all four | Verified |
@@ -23,7 +23,7 @@ The only intentionally unresolved visual exception is color contrast. The review
23
23
  | RTL | First-class RTL and install-time transformations | Authored logical layout, inherited direction hook/provider, Arabic font, explicit message catalogs, and RTL keyboard tests | Banhaten-specific |
24
24
  | Documentation | Live preview/code, component examples, API and CLI docs | Live registry previews, exact example source, extracted TypeScript props, token contract, accessibility contract, RTL, files, navigation/token search, and copy actions | Verified |
25
25
  | Components vs blocks | Components and larger blocks | Stable Components and broader Patterns; experiments and visual QA isolated under Lab | Verified |
26
- | Accessibility | Primitive semantics and per-component guidance | Native/Radix semantics, focus handling, dialog traps, tabs contract, reduced motion, forced colors, axe, Chromium/Firefox/WebKit tests | Verified except documented colors |
26
+ | Accessibility | Primitive semantics and per-component guidance | Native/Radix semantics, focus handling, dialog traps, tabs contract, reduced motion, forced colors, 910/910 contrast, strict route Axe, and Chromium/Firefox/WebKit tests | Verified |
27
27
  | Release governance | Changelog and frequent CLI/schema evolution | Changelog, security, contributing, support matrix, stability tiers, deprecation, release gate, and roadmap | Verified |
28
28
 
29
29
  ## Full Finding Register
@@ -35,18 +35,18 @@ The only intentionally unresolved visual exception is color contrast. The review
35
35
  - Repaired Command Bar search/clear/submit behavior, Table disabled/action behavior, File Upload progress semantics, Spinner reduced-motion behavior, and Date Picker presets/month-year interaction.
36
36
  - Standardized direction resolution for Alert, Pagination, Input OTP, Carousel, and Toast through explicit props, `DirectionProvider`, nearest `[dir]`, then document direction.
37
37
  - Corrected Arabic Pagination copy and kept product strings explicit rather than silently localizing demo text.
38
- - Added focused behavior tests; the component suite currently runs 171 tests.
38
+ - Added focused behavior tests; the component suite currently runs 205 tests.
39
39
 
40
40
  ### Colors and tokens
41
41
 
42
- - Preserved every requested source color value and moved all failing combinations into the generated Color Contrast report.
42
+ - Repaired semantic foreground/background role selection across component states while preserving the canonical palette; all 910 generated contrast combinations now pass.
43
43
  - Split system, typography, effects, component variables, dark effects, and
44
44
  scoped aliases into owned `tokens/source/` fragments that compile to
45
45
  `tokens/banhaten.tokens.json`.
46
46
  - Generated live `var(...)` alias graphs rather than flattened semantic values and verified custom-property closure.
47
47
  - Added named categorical chart semantics, spinner/skeleton/motion tokens, exact icon stroke tokens, Item tokens, default border/transition wiring, reduced motion, and forced-color focus rules.
48
- - Generated exact component token metadata from source. All 67 registry components have non-empty token contracts.
49
- - Deduplicated theme CSS against the default cascade without changing values, reducing generated CSS from 466,671 bytes to 154,130 bytes.
48
+ - Generated exact component token metadata from source. All 68 registry components have non-empty, current token contracts.
49
+ - Deduplicated theme CSS against the default cascade and kept generated CSS at 156,729 bytes after the accessibility and component-token additions.
50
50
  - Documented the no-global-density decision and the semantic elevation hierarchy.
51
51
 
52
52
  ### Usage and composition
@@ -70,7 +70,7 @@ The only intentionally unresolved visual exception is color contrast. The review
70
70
  ### Documentation and presentation
71
71
 
72
72
  - Removed launch-blocking `noindex`, added description/social metadata, dynamic page titles, skip link, release status/version, and responsive searchable navigation.
73
- - Reframed the overview around quick start, token rules, safe updates, and the documented contrast exception.
73
+ - Reframed the overview around quick start, token rules, safe updates, and the now-enforced contrast contract.
74
74
  - Added focus trap, Escape, initial focus, and focus return to Customize and Component Matrix dialogs.
75
75
  - Completed Preview/Code tab IDs, `aria-controls`, `aria-labelledby`, roving focus, and keyboard navigation.
76
76
  - Renamed ambiguous “Copy Page” to “Copy page link” and added clipboard fallback.
@@ -79,7 +79,7 @@ The only intentionally unresolved visual exception is color contrast. The review
79
79
  ### Verification and release
80
80
 
81
81
  - Browser checks cover Chromium, Firefox, and WebKit; visual baselines stay on Chromium for deterministic snapshots.
82
- - Automated checks cover LTR/RTL behavior, portals, focus return, docs navigation, mobile navigation, token copy/search, live theme overrides, the frozen contrast totals, reduced motion, forced colors, and serious/critical axe findings outside the intentionally documented color rule.
82
+ - Automated checks cover LTR/RTL behavior, portals, focus return, docs navigation, mobile navigation, token copy/search, live theme overrides, the 910/910 contrast totals, reduced motion, forced colors, and strict route-wide Axe findings without a color-rule exception.
83
83
  - Bundle budgets, schema validation, token generation, token-only styling, registry mirrors, documentation coverage, CLI integration, isolated installs, package audit, and tarball contents are release gates.
84
84
 
85
85
  ## Deliberate Differences, Not Missing Copies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "banhaten",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Banhaten design system: an Arabic and English-first React design system with shadcn-style component installation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "build": "npm --prefix playground ci --include=dev --include=optional && npm --prefix playground run build",
35
35
  "build:railway": "npm --prefix playground ci --include=dev --include=optional && npm --prefix playground run build:railway",
36
36
  "start": "node playground/scripts/serve-static.mjs playground/dist",
37
- "check": "node --check src/cli/index.js && node --check src/cli/command-line.js && node --check src/cli/doctor.js && node --check src/cli/config-editors.js && node --check src/cli/source-scanner.js && node --check src/cli/jsonc.js && node --check src/cli/vite-config.js && node --check src/cli/install-plan.js && node --check src/cli/install-receipt.js && node --check src/cli/package-install-state.js && node --check src/cli/package-manager.js && node --check src/cli/package-plan.js && node --check src/cli/package-version.js && node --check scripts/compile-token-catalog.mjs && node --check scripts/sync-default-theme-neutral-values.mjs && node --check scripts/token-catalog-test.mjs && node --check scripts/registry-catalog.mjs && node --check scripts/generate-registry-index.mjs && node --check scripts/generate-shadcn-registry.mjs && node --check scripts/normalize-component-docs.mjs && node --check scripts/package-artifact.mjs && node --check scripts/check-package-contents.mjs && node --check scripts/select-react-compatibility.mjs && npm run contracts:check && npm run cli:test && npm run tokens:check && npm run registry:check && npm run docs:check && npm run package:check && npm run playground:build && npm run playground:test:browser && npm run playground:audit",
37
+ "check": "node --check src/cli/index.js && node --check src/cli/command-line.js && node --check src/cli/doctor.js && node --check src/cli/config-editors.js && node --check src/cli/source-scanner.js && node --check src/cli/jsonc.js && node --check src/cli/vite-config.js && node --check src/cli/install-plan.js && node --check src/cli/install-receipt.js && node --check src/cli/package-install-state.js && node --check src/cli/package-manager.js && node --check src/cli/package-plan.js && node --check src/cli/package-version.js && node --check scripts/compile-token-catalog.mjs && node --check scripts/sync-default-theme-neutral-values.mjs && node --check scripts/token-catalog-test.mjs && node --check scripts/registry-catalog.mjs && node --check scripts/generate-registry-index.mjs && node --check scripts/generate-shadcn-registry.mjs && node --check scripts/normalize-component-docs.mjs && node --check scripts/package-artifact.mjs && node --check scripts/check-package-contents.mjs && node --check scripts/select-react-compatibility.mjs && npm run contracts:check && npm run cli:test && npm run tokens:check && npm run components:ownership:check && npm run registry:check && npm run docs:check && npm run package:check && npm run playground:build && npm run playground:test:browser && npm run playground:audit",
38
38
  "contracts:check": "node scripts/validate-contracts.mjs",
39
39
  "cli:test": "node scripts/cli-argument-test.mjs && node scripts/cli-dependency-test.mjs && node scripts/cli-install-plan-test.mjs && node scripts/cli-integration-test.mjs && node scripts/cli-doctor-test.mjs",
40
40
  "playground:build": "npm --prefix playground run build",
@@ -49,6 +49,7 @@
49
49
  "registry:exports": "node scripts/check-isolated-registry-installs.mjs --write-exports",
50
50
  "registry:sync": "node scripts/check-registry-mirrors.mjs --write",
51
51
  "components:tokens:check": "node scripts/check-component-token-usage.mjs",
52
+ "components:ownership:check": "npm --prefix playground run test:ownership",
52
53
  "components:tokens:metadata": "node scripts/sync-component-token-metadata.mjs",
53
54
  "docs:check": "node scripts/check-component-docs.mjs && node scripts/normalize-component-docs.mjs",
54
55
  "docs:normalize": "node scripts/normalize-component-docs.mjs --write",
@@ -16,7 +16,7 @@ const alertDialogContentVariants = cva(
16
16
  "fixed left-1/2 top-1/2 z-[var(--bh-z-overlay)] flex max-h-[calc(100vh-(var(--bh-space-8xl-48)*2))] w-[var(--bh-alert-dialog-width)]",
17
17
  "max-w-[calc(100vw-(var(--bh-space-4xl-20)*2))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden",
18
18
  "rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--shadow-modal)] outline-none",
19
- "[--bh-alert-dialog-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-alert-dialog-border),var(--shadow-component-default)]",
19
+ "[--bh-alert-dialog-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-alert-dialog-border),var(--shadow-lg)]",
20
20
  ],
21
21
  {
22
22
  variants: {
@@ -11,6 +11,7 @@ import {
11
11
 
12
12
  import { cn } from "@/lib/utils"
13
13
  import { useResolvedDirection } from "@/hooks/use-direction"
14
+ import { Button } from "@/components/ui/button"
14
15
 
15
16
  const alertVariants = cva(
16
17
  [
@@ -22,13 +23,13 @@ const alertVariants = cva(
22
23
  variants: {
23
24
  status: {
24
25
  danger:
25
- "[--alert-bg:var(--bh-bg-danger-subtle)] [--alert-border:var(--bh-border-danger-subtle)] [--alert-title:var(--bh-content-danger-strong)] [--alert-description:var(--bh-content-danger-default)] [--alert-icon:var(--bh-content-danger-default)]",
26
+ "[--alert-bg:var(--bh-bg-danger-subtle)] [--alert-border:var(--bh-border-danger-subtle)] [--alert-title:var(--bh-content-danger-strong)] [--alert-description:var(--bh-content-danger-strong)] [--alert-icon:var(--bh-content-danger-default)]",
26
27
  success:
27
- "[--alert-bg:var(--bh-bg-success-subtle)] [--alert-border:var(--bh-border-success-subtle)] [--alert-title:var(--bh-content-success-strong)] [--alert-description:var(--bh-content-success-default)] [--alert-icon:var(--bh-content-success-default)]",
28
+ "[--alert-bg:var(--bh-bg-success-subtle)] [--alert-border:var(--bh-border-success-subtle)] [--alert-title:var(--bh-content-success-strong)] [--alert-description:var(--bh-content-success-strong)] [--alert-icon:var(--bh-content-success-default)]",
28
29
  warning:
29
- "[--alert-bg:var(--bh-bg-warning-subtle)] [--alert-border:var(--bh-border-warning-subtle)] [--alert-title:var(--bh-content-warning-strong)] [--alert-description:var(--bh-content-warning-default)] [--alert-icon:var(--bh-content-warning-default)]",
30
+ "[--alert-bg:var(--bh-bg-warning-subtle)] [--alert-border:var(--bh-border-warning-subtle)] [--alert-title:var(--bh-content-warning-strong)] [--alert-description:var(--bh-content-warning-strong)] [--alert-icon:var(--bh-content-warning-default)]",
30
31
  info:
31
- "[--alert-bg:var(--bh-bg-info-subtle)] [--alert-border:var(--bh-border-info-subtle)] [--alert-title:var(--bh-content-brand-strong)] [--alert-description:var(--bh-content-brand-default)] [--alert-icon:var(--bh-content-brand-default)]",
32
+ "[--alert-bg:var(--bh-bg-info-subtle)] [--alert-border:var(--bh-border-info-subtle)] [--alert-title:var(--bh-content-brand-strong)] [--alert-description:var(--bh-content-brand-strong)] [--alert-icon:var(--bh-content-brand-default)]",
32
33
  neutral:
33
34
  "[--alert-bg:var(--bh-bg-neutral-subtle)] [--alert-border:var(--bh-border-subtle)] [--alert-title:var(--bh-content-default)] [--alert-description:var(--bh-content-default)] [--alert-icon:var(--bh-content-default)]",
34
35
  },
@@ -194,7 +195,7 @@ const Alert = React.forwardRef<HTMLElement, AlertProps>(function Alert(
194
195
  <p
195
196
  data-slot="alert-description"
196
197
  dir="auto"
197
- className="m-0 w-full break-words text-[length:var(--bh-text-body-sm-regular-font-size)] font-[var(--bh-font-weight-regular)] leading-[var(--bh-text-body-sm-regular-line-height)] tracking-[var(--bh-text-body-sm-regular-letter-spacing)] text-[var(--alert-description)] opacity-[var(--bh-opacity-80)]"
198
+ className="m-0 w-full break-words text-[length:var(--bh-text-body-sm-regular-font-size)] font-[var(--bh-font-weight-regular)] leading-[var(--bh-text-body-sm-regular-line-height)] tracking-[var(--bh-text-body-sm-regular-letter-spacing)] text-[var(--alert-description)]"
198
199
  >
199
200
  {description}
200
201
  </p>
@@ -212,15 +213,17 @@ const Alert = React.forwardRef<HTMLElement, AlertProps>(function Alert(
212
213
  )}
213
214
 
214
215
  {visibleCloseButton && (
215
- <button
216
+ <Button
216
217
  data-slot="alert-close"
217
218
  aria-label={resolvedCloseLabel}
218
- className="inline-flex size-[var(--bh-alert-icon-size)] shrink-0 cursor-pointer items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-content-subtle)] outline-none focus-visible:shadow-[var(--shadow-button-focus)]"
219
+ className="size-[var(--bh-alert-icon-size)] shrink-0 rounded-[var(--bh-radius-full)] p-0 text-[var(--bh-content-subtle)]"
219
220
  onClick={onClose}
221
+ size="icon-xs"
220
222
  type="button"
223
+ variant="ghost"
221
224
  >
222
225
  <CloseIcon />
223
- </button>
226
+ </Button>
224
227
  )}
225
228
  </section>
226
229
  )
@@ -237,15 +240,16 @@ function AlertActions({
237
240
  className="flex shrink-0 items-center gap-[var(--bh-space-3xl-16)]"
238
241
  >
239
242
  {actions.map((action, index) => (
240
- <button
243
+ <Button
241
244
  data-slot="alert-action"
242
- className="inline-flex shrink-0 cursor-pointer items-center justify-center gap-[var(--bh-space-xxs-2)] border-0 bg-transparent p-0 text-start text-[length:var(--bh-text-body-md-underline-font-size)] font-[var(--bh-text-body-md-underline-font-weight)] leading-[var(--bh-text-body-md-underline-line-height)] tracking-[var(--bh-text-body-md-underline-letter-spacing)] text-[var(--bh-content-default)] underline outline-none [text-underline-position:from-font] focus-visible:shadow-[var(--shadow-button-focus)]"
245
+ className="h-auto shrink-0 gap-[var(--bh-space-xxs-2)] p-0 text-start text-[length:var(--bh-text-body-md-underline-font-size)] font-[var(--bh-text-body-md-underline-font-weight)] leading-[var(--bh-text-body-md-underline-line-height)] tracking-[var(--bh-text-body-md-underline-letter-spacing)] text-[var(--bh-content-default)] underline [text-underline-position:from-font]"
243
246
  key={index}
244
247
  onClick={action.onClick}
245
248
  type="button"
249
+ variant="ghost"
246
250
  >
247
251
  {action.label}
248
- </button>
252
+ </Button>
249
253
  ))}
250
254
  </div>
251
255
  )
@@ -50,7 +50,7 @@ const avatarVariants = cva(
50
50
  )
51
51
 
52
52
  const avatarFallbackVariants = cva(
53
- "flex size-full items-center justify-center rounded-[var(--bh-avatar-radius,var(--bh-radius-full))] bg-[var(--bh-bg-neutral-subtle)] text-[length:var(--bh-avatar-fallback-size,var(--bh-text-body-sm-medium-font-size))] font-[var(--bh-font-weight-semibold)] leading-none text-[var(--bh-content-subtle)] whitespace-nowrap",
53
+ "flex size-full items-center justify-center rounded-[var(--bh-avatar-radius,var(--bh-radius-full))] bg-[var(--bh-bg-neutral-subtle)] text-[length:var(--bh-avatar-fallback-size,var(--bh-text-body-sm-medium-font-size))] font-[var(--bh-font-weight-semibold)] leading-none text-[var(--bh-content-default)] whitespace-nowrap",
54
54
  {
55
55
  variants: {
56
56
  size: avatarSizeVariants,
@@ -62,7 +62,7 @@ const avatarFallbackVariants = cva(
62
62
  )
63
63
 
64
64
  const avatarIconVariants = cva(
65
- "flex size-full items-center justify-center rounded-[var(--bh-avatar-radius,var(--bh-radius-full))] bg-[var(--bh-bg-neutral-subtle)] text-[var(--bh-content-subtle)]",
65
+ "flex size-full items-center justify-center rounded-[var(--bh-avatar-radius,var(--bh-radius-full))] bg-[var(--bh-bg-neutral-subtle)] text-[var(--bh-content-default)]",
66
66
  {
67
67
  variants: {
68
68
  size: avatarSizeVariants,
@@ -147,7 +147,9 @@ const avatarActionVariants = cva(
147
147
  )
148
148
 
149
149
  function Avatar({
150
+ "aria-label": ariaLabel,
150
151
  className,
152
+ role,
151
153
  size,
152
154
  shape,
153
155
  ...props
@@ -155,9 +157,11 @@ function Avatar({
155
157
  VariantProps<typeof avatarVariants>) {
156
158
  return (
157
159
  <AvatarPrimitive.Root
160
+ aria-label={ariaLabel}
158
161
  data-slot="avatar"
159
162
  data-size={size ?? "default"}
160
163
  data-shape={shape ?? "circular"}
164
+ role={role ?? (ariaLabel ? "img" : undefined)}
161
165
  className={cn(avatarVariants({ size, shape, className }))}
162
166
  {...props}
163
167
  />
@@ -307,7 +311,7 @@ function AvatarGroupCount({
307
311
  data-slot="avatar-group-count"
308
312
  className={cn(
309
313
  avatarVariants({ size }),
310
- "-ms-[var(--bh-space-md-8)] bg-[var(--bh-bg-neutral-subtle)] text-[length:var(--bh-avatar-fallback-size,var(--bh-text-body-sm-medium-font-size))] font-[var(--bh-font-weight-semibold)] leading-none text-[var(--bh-content-subtle)] ring-[var(--bh-space-xxs-2)] ring-[var(--bh-bg-default)]",
314
+ "-ms-[var(--bh-space-md-8)] bg-[var(--bh-bg-neutral-subtle)] text-[length:var(--bh-avatar-fallback-size,var(--bh-text-body-sm-medium-font-size))] font-[var(--bh-font-weight-semibold)] leading-none text-[var(--bh-content-default)] ring-[var(--bh-space-xxs-2)] ring-[var(--bh-bg-default)]",
311
315
  className
312
316
  )}
313
317
  {...props}
@@ -192,13 +192,13 @@ const badgeVariants = cva(
192
192
  badgeStyle: "outline",
193
193
  color: "neutral",
194
194
  class:
195
- "border-[var(--bh-border-strong)] text-[var(--bh-content-subtle)] [--badge-dot-color:var(--bh-content-subtle)] [--badge-split-border-color:var(--bh-border-default)]",
195
+ "border-[var(--bh-border-strong)] text-[var(--bh-content-default)] [--badge-dot-color:var(--bh-content-default)] [--badge-split-border-color:var(--bh-border-default)]",
196
196
  },
197
197
  {
198
198
  badgeStyle: "solid",
199
199
  color: "neutral",
200
200
  class:
201
- "bg-[var(--bh-bg-neutral-default)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-subtle)]",
201
+ "bg-[var(--bh-bg-neutral-bold)] text-[var(--bh-content-on-neutral)] [--badge-dot-color:var(--bh-content-on-neutral)] [--badge-split-border-color:var(--bh-border-subtle)]",
202
202
  },
203
203
  {
204
204
  badgeStyle: "light",
@@ -210,13 +210,13 @@ const badgeVariants = cva(
210
210
  badgeStyle: "outline",
211
211
  color: "blue",
212
212
  class:
213
- "border-[var(--bh-border-accent-blue-strong)] text-[var(--bh-content-accent-blue-default)] [--badge-dot-color:var(--bh-content-accent-blue-default)] [--badge-split-border-color:var(--bh-border-accent-blue-subtle)]",
213
+ "border-[var(--bh-border-accent-blue-strong)] text-[var(--bh-content-accent-blue-strong)] [--badge-dot-color:var(--bh-content-accent-blue-strong)] [--badge-split-border-color:var(--bh-border-accent-blue-subtle)]",
214
214
  },
215
215
  {
216
216
  badgeStyle: "solid",
217
217
  color: "blue",
218
218
  class:
219
- "bg-[var(--bh-bg-accent-blue-default)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-blue-subtle)]",
219
+ "bg-[var(--bh-bg-accent-blue-bold)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-blue-subtle)]",
220
220
  },
221
221
  {
222
222
  badgeStyle: "light",
@@ -228,13 +228,13 @@ const badgeVariants = cva(
228
228
  badgeStyle: "outline",
229
229
  color: "green",
230
230
  class:
231
- "border-[var(--bh-border-accent-green-strong)] text-[var(--bh-content-accent-green-default)] [--badge-dot-color:var(--bh-content-accent-green-default)] [--badge-split-border-color:var(--bh-border-accent-green-subtle)]",
231
+ "border-[var(--bh-border-accent-green-strong)] text-[var(--bh-content-accent-green-strong)] [--badge-dot-color:var(--bh-content-accent-green-strong)] [--badge-split-border-color:var(--bh-border-accent-green-subtle)]",
232
232
  },
233
233
  {
234
234
  badgeStyle: "solid",
235
235
  color: "green",
236
236
  class:
237
- "bg-[var(--bh-bg-accent-green-default)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-green-subtle)]",
237
+ "bg-[var(--bh-bg-accent-green-default)] text-[var(--bh-content-on-light)] [--badge-dot-color:var(--bh-content-on-light)] [--badge-split-border-color:var(--bh-border-accent-green-subtle)]",
238
238
  },
239
239
  {
240
240
  badgeStyle: "light",
@@ -246,13 +246,13 @@ const badgeVariants = cva(
246
246
  badgeStyle: "outline",
247
247
  color: "amber",
248
248
  class:
249
- "border-[var(--bh-border-accent-amber-strong)] text-[var(--bh-content-accent-amber-default)] [--badge-dot-color:var(--bh-content-accent-amber-default)] [--badge-split-border-color:var(--bh-border-accent-amber-subtle)]",
249
+ "border-[var(--bh-border-accent-amber-strong)] text-[var(--bh-content-accent-amber-strong)] [--badge-dot-color:var(--bh-content-accent-amber-strong)] [--badge-split-border-color:var(--bh-border-accent-amber-subtle)]",
250
250
  },
251
251
  {
252
252
  badgeStyle: "solid",
253
253
  color: "amber",
254
254
  class:
255
- "bg-[var(--bh-bg-accent-amber-default)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-amber-subtle)]",
255
+ "bg-[var(--bh-bg-accent-amber-default)] text-[var(--bh-content-on-light)] [--badge-dot-color:var(--bh-content-on-light)] [--badge-split-border-color:var(--bh-border-accent-amber-subtle)]",
256
256
  },
257
257
  {
258
258
  badgeStyle: "light",
@@ -264,7 +264,7 @@ const badgeVariants = cva(
264
264
  badgeStyle: "outline",
265
265
  color: "danger",
266
266
  class:
267
- "border-[var(--bh-border-accent-red-strong)] text-[var(--bh-content-accent-red-default)] [--badge-dot-color:var(--bh-content-accent-red-default)] [--badge-split-border-color:var(--bh-border-accent-red-subtle)]",
267
+ "border-[var(--bh-border-accent-red-strong)] text-[var(--bh-content-accent-red-strong)] [--badge-dot-color:var(--bh-content-accent-red-strong)] [--badge-split-border-color:var(--bh-border-accent-red-subtle)]",
268
268
  },
269
269
  {
270
270
  badgeStyle: "solid",
@@ -282,7 +282,7 @@ const badgeVariants = cva(
282
282
  badgeStyle: "outline",
283
283
  color: "purple",
284
284
  class:
285
- "border-[var(--bh-border-accent-purple-strong)] text-[var(--bh-content-accent-purple-default)] [--badge-dot-color:var(--bh-content-accent-purple-default)] [--badge-split-border-color:var(--bh-border-accent-purple-subtle)]",
285
+ "border-[var(--bh-border-accent-purple-strong)] text-[var(--bh-content-accent-purple-strong)] [--badge-dot-color:var(--bh-content-accent-purple-strong)] [--badge-split-border-color:var(--bh-border-accent-purple-subtle)]",
286
286
  },
287
287
  {
288
288
  badgeStyle: "solid",
@@ -300,13 +300,13 @@ const badgeVariants = cva(
300
300
  badgeStyle: "outline",
301
301
  color: "fuchsia",
302
302
  class:
303
- "border-[var(--bh-border-accent-fuchsia-strong)] text-[var(--bh-content-accent-fuchsia-default)] [--badge-dot-color:var(--bh-content-accent-fuchsia-default)] [--badge-split-border-color:var(--bh-border-accent-fuchsia-subtle)]",
303
+ "border-[var(--bh-border-accent-fuchsia-strong)] text-[var(--bh-content-accent-fuchsia-strong)] [--badge-dot-color:var(--bh-content-accent-fuchsia-strong)] [--badge-split-border-color:var(--bh-border-accent-fuchsia-subtle)]",
304
304
  },
305
305
  {
306
306
  badgeStyle: "solid",
307
307
  color: "fuchsia",
308
308
  class:
309
- "bg-[var(--bh-bg-accent-fuchsia-default)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-fuchsia-subtle)]",
309
+ "bg-[var(--bh-bg-accent-fuchsia-default)] text-[var(--bh-content-on-light)] [--badge-dot-color:var(--bh-content-on-light)] [--badge-split-border-color:var(--bh-border-accent-fuchsia-subtle)]",
310
310
  },
311
311
  {
312
312
  badgeStyle: "light",
@@ -318,13 +318,13 @@ const badgeVariants = cva(
318
318
  badgeStyle: "outline",
319
319
  color: "rose",
320
320
  class:
321
- "border-[var(--bh-border-accent-rose-strong)] text-[var(--bh-content-accent-rose-default)] [--badge-dot-color:var(--bh-content-accent-rose-default)] [--badge-split-border-color:var(--bh-border-accent-rose-subtle)]",
321
+ "border-[var(--bh-border-accent-rose-strong)] text-[var(--bh-content-accent-rose-strong)] [--badge-dot-color:var(--bh-content-accent-rose-strong)] [--badge-split-border-color:var(--bh-border-accent-rose-subtle)]",
322
322
  },
323
323
  {
324
324
  badgeStyle: "solid",
325
325
  color: "rose",
326
326
  class:
327
- "bg-[var(--bh-bg-accent-rose-default)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-rose-subtle)]",
327
+ "bg-[var(--bh-bg-accent-rose-bold)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-rose-subtle)]",
328
328
  },
329
329
  {
330
330
  badgeStyle: "light",
@@ -336,13 +336,13 @@ const badgeVariants = cva(
336
336
  badgeStyle: "outline",
337
337
  color: "sky",
338
338
  class:
339
- "border-[var(--bh-border-accent-sky-strong)] text-[var(--bh-content-accent-sky-default)] [--badge-dot-color:var(--bh-content-accent-sky-default)] [--badge-split-border-color:var(--bh-border-accent-sky-subtle)]",
339
+ "border-[var(--bh-border-accent-sky-strong)] text-[var(--bh-content-accent-sky-strong)] [--badge-dot-color:var(--bh-content-accent-sky-strong)] [--badge-split-border-color:var(--bh-border-accent-sky-subtle)]",
340
340
  },
341
341
  {
342
342
  badgeStyle: "solid",
343
343
  color: "sky",
344
344
  class:
345
- "bg-[var(--bh-bg-accent-sky-base)] text-[var(--bh-content-on-color)] [--badge-dot-color:var(--bh-content-on-color)] [--badge-split-border-color:var(--bh-border-accent-sky-subtle)]",
345
+ "bg-[var(--bh-bg-accent-sky-base)] text-[var(--bh-content-on-light)] [--badge-dot-color:var(--bh-content-on-light)] [--badge-split-border-color:var(--bh-border-accent-sky-subtle)]",
346
346
  },
347
347
  {
348
348
  badgeStyle: "light",
@@ -354,7 +354,7 @@ const badgeVariants = cva(
354
354
  badgeStyle: "outline",
355
355
  color: "golden",
356
356
  class:
357
- "border-[var(--bh-border-accent-golden-strong)] text-[var(--bh-content-accent-golden-default)] [--badge-dot-color:var(--bh-content-accent-golden-default)] [--badge-split-border-color:var(--bh-border-accent-golden-subtle)]",
357
+ "border-[var(--bh-border-accent-golden-strong)] text-[var(--bh-content-accent-golden-strong)] [--badge-dot-color:var(--bh-content-accent-golden-strong)] [--badge-split-border-color:var(--bh-border-accent-golden-subtle)]",
358
358
  },
359
359
  {
360
360
  badgeStyle: "solid",
@@ -32,7 +32,7 @@ type ToggleGroupContextValue = {
32
32
  const toggleGroupContext = React.createContext<ToggleGroupContextValue | null>(null)
33
33
 
34
34
  const buttonGroupVariants = cva(
35
- "inline-flex w-fit items-center overflow-hidden rounded-[var(--bh-button-group-radius)] bg-[var(--bh-button-group-bg)] text-[var(--bh-button-group-content)] shadow-[var(--shadow-button-group)] [--shadow-button-group:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-button-group-border),var(--shadow-component-default)]",
35
+ "inline-flex w-fit max-w-full items-center overflow-x-auto overflow-y-hidden rounded-[var(--bh-button-group-radius)] bg-[var(--bh-button-group-bg)] text-[var(--bh-button-group-content)] shadow-[var(--shadow-button-group)] outline-none focus-visible:shadow-[var(--shadow-button-group-focus)] [--shadow-button-group:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-button-group-border),var(--shadow-component-default)]",
36
36
  {
37
37
  variants: {
38
38
  size: {
@@ -200,6 +200,7 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(function
200
200
  mode = "default",
201
201
  children,
202
202
  role = "group",
203
+ tabIndex = 0,
203
204
  ...props
204
205
  }, ref) {
205
206
  const resolvedSize = resolveButtonGroupSize(density, size)
@@ -222,6 +223,7 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(function
222
223
  data-slot="button-group"
223
224
  ref={ref}
224
225
  role={role}
226
+ tabIndex={tabIndex}
225
227
  className={cn(buttonGroupVariants({ size: contextValue.size }), className)}
226
228
  {...props}
227
229
  >
@@ -18,9 +18,9 @@ const buttonVariants = cva(
18
18
  destructive:
19
19
  "bg-[var(--bh-interactive-danger-default)] text-[var(--bh-content-on-color)] shadow-[var(--shadow-button-raised)] hover:bg-[var(--bh-interactive-danger-hover)] active:bg-[var(--bh-interactive-danger-pressed)] focus-visible:shadow-[var(--shadow-button-danger-focus)] disabled:bg-[var(--bh-interactive-danger-disabled)] disabled:text-[var(--bh-content-disabled)]",
20
20
  success:
21
- "bg-[var(--bh-bg-success-default)] text-[var(--bh-content-on-color)] shadow-[var(--shadow-button-raised)] hover:bg-[var(--bh-bg-success-bold)] active:bg-[var(--bh-bg-success-strong)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
21
+ "bg-[var(--bh-interactive-success-default)] text-[var(--bh-content-on-success)] shadow-[var(--shadow-button-raised)] hover:bg-[var(--bh-interactive-success-hover)] active:bg-[var(--bh-interactive-success-pressed)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
22
22
  warning:
23
- "bg-[var(--bh-bg-warning-default)] text-[var(--bh-content-on-light)] shadow-[var(--shadow-button-raised)] hover:bg-[var(--bh-bg-warning-bold)] active:bg-[var(--bh-bg-warning-strong)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
23
+ "bg-[var(--bh-interactive-warning-default)] text-[var(--bh-content-on-warning)] shadow-[var(--shadow-button-raised)] hover:bg-[var(--bh-interactive-warning-hover)] active:bg-[var(--bh-interactive-warning-pressed)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
24
24
  secondary:
25
25
  "bg-[var(--bh-interactive-secondary-default)] text-[var(--bh-content-default)] shadow-[var(--shadow-button-secondary)] [--bh-button-secondary-border:var(--bh-border-default)] hover:bg-[var(--bh-interactive-secondary-hover)] active:bg-[var(--bh-interactive-secondary-pressed)] focus-visible:[--bh-button-secondary-focus-border:var(--bh-border-brand-strong)] focus-visible:shadow-[var(--shadow-button-secondary-focus)] disabled:border-[length:var(--bh-border-width-default)] disabled:border-[var(--bh-border-disabled)] disabled:bg-[var(--bh-interactive-secondary-disabled)] disabled:text-[var(--bh-content-disabled)]",
26
26
  soft:
@@ -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,
@@ -362,7 +376,7 @@ function CardBody({
362
376
  className={cn(
363
377
  "flex min-w-0 flex-col",
364
378
  horizontal
365
- ? "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)]"
366
380
  : "w-full gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-content-padding)]"
367
381
  )}
368
382
  >
@@ -566,7 +580,7 @@ function getCardMediaClassName(
566
580
  breakpoint: CardBreakpoint
567
581
  ) {
568
582
  if (orientation === "horizontal" && breakpoint === "desktop") {
569
- 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)]"
570
584
  }
571
585
 
572
586
  return "h-[var(--bh-card-media-height)] w-full"
@@ -611,6 +625,7 @@ export {
611
625
  CardActions,
612
626
  CardFooter,
613
627
  CardMedia,
628
+ CardSurface,
614
629
  cardVariants,
615
630
  type CardActionsProps,
616
631
  type CardBreakpoint,
@@ -618,5 +633,6 @@ export {
618
633
  type CardMediaProps,
619
634
  type CardOrientation,
620
635
  type CardProps,
636
+ type CardSurfaceProps,
621
637
  type CardType,
622
638
  }
@@ -17,16 +17,22 @@ type CarouselPlugin = UseCarouselParameters[1]
17
17
  type CarouselOrientation = "horizontal" | "vertical"
18
18
 
19
19
  type CarouselMessages = {
20
+ carouselLabel: string
21
+ carouselRoleDescription: string
20
22
  nextSlide: string
21
23
  previousSlide: string
22
24
  }
23
25
 
24
26
  const defaultCarouselMessages: CarouselMessages = {
27
+ carouselLabel: "Carousel",
28
+ carouselRoleDescription: "carousel",
25
29
  nextSlide: "Next slide",
26
30
  previousSlide: "Previous slide",
27
31
  }
28
32
 
29
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",
30
36
  nextSlide: "\u0627\u0644\u0634\u0631\u064a\u062d\u0629 \u0627\u0644\u062a\u0627\u0644\u064a\u0629",
31
37
  previousSlide: "\u0627\u0644\u0634\u0631\u064a\u062d\u0629 \u0627\u0644\u0633\u0627\u0628\u0642\u0629",
32
38
  }
@@ -58,6 +64,9 @@ const CarouselContext = React.createContext<CarouselContextValue | null>(null)
58
64
  const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
59
65
  function Carousel(
60
66
  {
67
+ "aria-label": ariaLabel,
68
+ "aria-labelledby": ariaLabelledBy,
69
+ "aria-roledescription": ariaRoleDescription,
61
70
  children,
62
71
  className,
63
72
  dir,
@@ -138,6 +147,11 @@ const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
138
147
  dir={direction}
139
148
  ref={setRootRef}
140
149
  role="region"
150
+ aria-label={ariaLabelledBy ? undefined : (ariaLabel ?? messages.carouselLabel)}
151
+ aria-labelledby={ariaLabelledBy}
152
+ aria-roledescription={
153
+ ariaRoleDescription ?? messages.carouselRoleDescription
154
+ }
141
155
  className={cn("relative", className)}
142
156
  {...props}
143
157
  >