banhaten 0.2.0 → 0.4.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 (120) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +5 -5
  3. package/docs/design-system/component-implementation-ownership-audit.md +245 -0
  4. package/docs/design-system/component-quality-audit-2026-08-20.md +225 -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 +3 -3
  11. package/docs/design-system/components/attribute.md +1 -1
  12. package/docs/design-system/components/avatar.md +2 -2
  13. package/docs/design-system/components/badge.md +1 -1
  14. package/docs/design-system/components/banner.md +6 -2
  15. package/docs/design-system/components/breadcrumbs.md +5 -2
  16. package/docs/design-system/components/button-group.md +3 -2
  17. package/docs/design-system/components/button.md +2 -2
  18. package/docs/design-system/components/card.md +11 -3
  19. package/docs/design-system/components/catalog-components.md +3 -0
  20. package/docs/design-system/components/date-picker.md +3 -1
  21. package/docs/design-system/components/file-upload.md +5 -1
  22. package/docs/design-system/components/input.md +8 -6
  23. package/docs/design-system/components/item.md +3 -1
  24. package/docs/design-system/components/kbd.md +11 -8
  25. package/docs/design-system/components/menu.md +2 -2
  26. package/docs/design-system/components/modal.md +1 -1
  27. package/docs/design-system/components/navigation-menu.md +1 -1
  28. package/docs/design-system/components/onboarding-step-list-item.md +3 -2
  29. package/docs/design-system/components/page-header.md +3 -1
  30. package/docs/design-system/components/pagination.md +3 -2
  31. package/docs/design-system/components/radio-card.md +1 -1
  32. package/docs/design-system/components/resizable.md +1 -1
  33. package/docs/design-system/components/scroll-area.md +7 -3
  34. package/docs/design-system/components/select.md +1 -1
  35. package/docs/design-system/components/sidebar.md +60 -18
  36. package/docs/design-system/components/slideout.md +4 -1
  37. package/docs/design-system/components/steps.md +7 -2
  38. package/docs/design-system/components/table.md +25 -7
  39. package/docs/design-system/components/tag.md +3 -3
  40. package/docs/design-system/components/timeline.md +4 -1
  41. package/docs/design-system/components/toggle.md +1 -1
  42. package/docs/design-system/components/toolbar.md +1 -1
  43. package/docs/design-system/components/tooltip.md +1 -1
  44. package/docs/design-system/density-and-elevation.md +23 -2
  45. package/docs/design-system/gap-promotion-report.md +1 -67
  46. package/docs/design-system/icons.md +6 -1
  47. package/docs/design-system/installation.md +1 -1
  48. package/docs/design-system/launch-comparison-shadcn.md +10 -10
  49. package/docs/design-system/token-governance.md +7 -8
  50. package/package.json +3 -2
  51. package/registry/components/alert-dialog.tsx +1 -1
  52. package/registry/components/alert.tsx +17 -13
  53. package/registry/components/attribute.tsx +8 -2
  54. package/registry/components/avatar.tsx +28 -20
  55. package/registry/components/badge.tsx +17 -17
  56. package/registry/components/button-group.tsx +4 -2
  57. package/registry/components/button.tsx +3 -3
  58. package/registry/components/card.tsx +19 -3
  59. package/registry/components/carousel.tsx +14 -0
  60. package/registry/components/command.tsx +37 -3
  61. package/registry/components/date-picker.tsx +83 -35
  62. package/registry/components/expanded/ActivityFeed.tsx +3 -7
  63. package/registry/components/expanded/ActivityHeatmap.tsx +64 -0
  64. package/registry/components/expanded/Banner.tsx +0 -1
  65. package/registry/components/expanded/Breadcrumbs.tsx +3 -0
  66. package/registry/components/expanded/CatalogComponentsShowcase.tsx +3 -4
  67. package/registry/components/expanded/CatalogDivider.tsx +3 -2
  68. package/registry/components/expanded/FileUpload.tsx +7 -5
  69. package/registry/components/expanded/OnboardingStepListItem.tsx +6 -7
  70. package/registry/components/expanded/PageHeader.tsx +35 -44
  71. package/registry/components/expanded/Slideout.tsx +18 -13
  72. package/registry/components/expanded/Steps.tsx +147 -9
  73. package/registry/components/expanded/Table.tsx +50 -18
  74. package/registry/components/expanded/Timeline.tsx +11 -24
  75. package/registry/components/expanded/activityFeed.css +8 -8
  76. package/registry/components/expanded/activityHeatmap.css +66 -0
  77. package/registry/components/expanded/banner.css +30 -15
  78. package/registry/components/expanded/breadcrumbs.css +5 -5
  79. package/registry/components/expanded/fileUpload.css +4 -4
  80. package/registry/components/expanded/onboardingStepListItem.css +16 -7
  81. package/registry/components/expanded/pageHeader.css +14 -11
  82. package/registry/components/expanded/slideout.css +3 -7
  83. package/registry/components/expanded/steps.css +75 -12
  84. package/registry/components/expanded/table.css +63 -69
  85. package/registry/components/expanded/timeline.css +17 -16
  86. package/registry/components/field.tsx +61 -8
  87. package/registry/components/input.tsx +12 -5
  88. package/registry/components/item.tsx +1 -1
  89. package/registry/components/kbd.tsx +109 -15
  90. package/registry/components/menu.tsx +57 -7
  91. package/registry/components/modal.tsx +1 -1
  92. package/registry/components/navigation-menu.tsx +1 -1
  93. package/registry/components/pagination.css +36 -0
  94. package/registry/components/pagination.tsx +20 -13
  95. package/registry/components/progress-slider.tsx +1 -1
  96. package/registry/components/radio-card.tsx +1 -2
  97. package/registry/components/resizable.tsx +36 -2
  98. package/registry/components/scroll-area.tsx +30 -6
  99. package/registry/components/select.tsx +35 -12
  100. package/registry/components/sidebar.tsx +940 -62
  101. package/registry/components/social-button.tsx +12 -5
  102. package/registry/components/table-elements.tsx +128 -0
  103. package/registry/components/tabs.tsx +5 -10
  104. package/registry/components/tag.tsx +2 -2
  105. package/registry/components/toggle.tsx +2 -2
  106. package/registry/components/toolbar.tsx +6 -6
  107. package/registry/components/tooltip.tsx +1 -1
  108. package/registry/examples/attribute-demo.tsx +1 -1
  109. package/registry/examples/avatar-demo.tsx +7 -4
  110. package/registry/examples/command-palette-demo.tsx +0 -1
  111. package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
  112. package/registry/examples/expanded/breadcrumbs-demo.tsx +3 -3
  113. package/registry/examples/kbd-demo.tsx +2 -1
  114. package/registry/examples/pagination-demo.tsx +5 -5
  115. package/registry/examples/sidebar-demo.tsx +59 -13
  116. package/registry/guidance/USAGE.md +1 -1
  117. package/registry/index.json +621 -60
  118. package/registry/styles/globals.css +211 -151
  119. package/registry.json +44 -6
  120. package/tokens/banhaten.tokens.json +325 -95
@@ -116,7 +116,7 @@ const arabicSocialButtonMessages: SocialButtonMessages = {
116
116
  }
117
117
 
118
118
  const socialButtonVariants = cva(
119
- "group/social-button relative inline-flex items-center justify-center whitespace-nowrap rounded-[var(--bh-social-button-radius)] bg-[var(--bh-social-button-bg)] text-[var(--bh-social-button-fg)] shadow-[var(--shadow-social-button)] outline-none transition-[background-color,box-shadow,color] [--bh-social-button-current-border:var(--bh-social-button-border)] [--shadow-social-button:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-social-button-current-border,var(--bh-social-button-border,transparent)),var(--shadow-component-default)] rtl:[--bh-social-button-width:var(--bh-social-button-width-rtl)] hover:bg-[var(--bh-social-button-bg-hover)] focus-visible:[--bh-social-button-current-border:var(--bh-social-button-focus-border)] focus-visible:shadow-[var(--shadow-social-button-focus)] disabled:pointer-events-none disabled:opacity-[var(--bh-opacity-50)] [&_svg]:pointer-events-none [&_svg]:shrink-0",
119
+ "group/social-button relative inline-flex max-w-full min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-social-button-radius)] bg-[var(--bh-social-button-bg)] text-[var(--bh-social-button-fg)] shadow-[var(--shadow-social-button)] outline-none transition-[background-color,box-shadow,color] [--bh-social-button-current-border:var(--bh-social-button-border)] [--shadow-social-button:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-social-button-current-border,var(--bh-social-button-border,transparent)),var(--shadow-component-default)] hover:bg-[var(--bh-social-button-bg-hover)] focus-visible:[--bh-social-button-current-border:var(--bh-social-button-focus-border)] focus-visible:shadow-[var(--shadow-social-button-focus)] disabled:pointer-events-none disabled:opacity-[var(--bh-opacity-50)] [&_svg]:pointer-events-none [&_svg]:shrink-0",
120
120
  {
121
121
  variants: {
122
122
  variant: {
@@ -184,7 +184,12 @@ const SocialButton = React.forwardRef<HTMLButtonElement, SocialButtonProps>(func
184
184
  ? String(visibleLabel)
185
185
  : defaultLabel
186
186
  const Comp = asChild ? Slot : "button"
187
- const cssVars = getSocialButtonVars(config, selectedVariant, isIcon)
187
+ const cssVars = getSocialButtonVars(
188
+ config,
189
+ selectedVariant,
190
+ isIcon,
191
+ resolvedDirection
192
+ )
188
193
  const content = (
189
194
  <>
190
195
  <SocialButtonIcon platform={platform} />
@@ -192,7 +197,8 @@ const SocialButton = React.forwardRef<HTMLButtonElement, SocialButtonProps>(func
192
197
  <span
193
198
  data-slot="social-button-label"
194
199
  dir="auto"
195
- className="min-w-0 pe-[var(--bh-social-button-label-padding-inline-end)]"
200
+ title={typeof visibleLabel === "string" ? visibleLabel : undefined}
201
+ className="min-w-0 overflow-hidden text-ellipsis pe-[var(--bh-social-button-label-padding-inline-end)]"
196
202
  >
197
203
  {visibleLabel}
198
204
  </span>
@@ -238,7 +244,8 @@ function isSimpleLabel(
238
244
  function getSocialButtonVars(
239
245
  config: SocialPlatformConfig,
240
246
  variant: SocialButtonVariant,
241
- isIcon: boolean
247
+ isIcon: boolean,
248
+ direction: "ltr" | "rtl"
242
249
  ) {
243
250
  const isSolid = variant === "solid"
244
251
  const border = isSolid
@@ -262,7 +269,7 @@ function getSocialButtonVars(
262
269
  "--bh-social-button-icon": isSolid ? config.solidIcon : config.outlineIcon,
263
270
  "--bh-social-button-width": isIcon
264
271
  ? "var(--bh-social-button-height)"
265
- : config.width.ltr,
272
+ : config.width[direction],
266
273
  "--bh-social-button-width-rtl": isIcon
267
274
  ? "var(--bh-social-button-height)"
268
275
  : config.width.rtl,
@@ -0,0 +1,128 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ type TableRootProps = React.ComponentPropsWithoutRef<"table">
8
+ type TableSectionProps = React.ComponentPropsWithoutRef<"thead">
9
+ type TableBodyProps = React.ComponentPropsWithoutRef<"tbody">
10
+ type TableRowElementProps = React.ComponentPropsWithoutRef<"tr">
11
+ type TableHeadProps = React.ComponentPropsWithoutRef<"th">
12
+ type TableCellProps = React.ComponentPropsWithoutRef<"td">
13
+ type DataTableSurfaceProps = React.ComponentPropsWithoutRef<"div">
14
+
15
+ const DataTableSurface = React.forwardRef<
16
+ HTMLDivElement,
17
+ DataTableSurfaceProps
18
+ >(function DataTableSurface({ className, role = "table", ...props }, ref) {
19
+ return (
20
+ <div
21
+ data-slot="data-table-surface"
22
+ ref={ref}
23
+ role={role}
24
+ className={cn("min-w-0", className)}
25
+ {...props}
26
+ />
27
+ )
28
+ })
29
+
30
+ const TableRoot = React.forwardRef<HTMLTableElement, TableRootProps>(
31
+ function TableRoot({ className, ...props }, ref) {
32
+ return (
33
+ <table
34
+ data-slot="table-root"
35
+ ref={ref}
36
+ className={cn("w-full border-collapse text-start", className)}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+ )
42
+
43
+ const TableHeader = React.forwardRef<
44
+ HTMLTableSectionElement,
45
+ TableSectionProps
46
+ >(function TableHeader({ className, ...props }, ref) {
47
+ return (
48
+ <thead
49
+ data-slot="table-header"
50
+ ref={ref}
51
+ className={cn("text-start", className)}
52
+ {...props}
53
+ />
54
+ )
55
+ })
56
+
57
+ const TableBody = React.forwardRef<HTMLTableSectionElement, TableBodyProps>(
58
+ function TableBody({ className, ...props }, ref) {
59
+ return (
60
+ <tbody
61
+ data-slot="table-body"
62
+ ref={ref}
63
+ className={className}
64
+ {...props}
65
+ />
66
+ )
67
+ }
68
+ )
69
+
70
+ const TableRowElement = React.forwardRef<
71
+ HTMLTableRowElement,
72
+ TableRowElementProps
73
+ >(function TableRowElement({ className, ...props }, ref) {
74
+ return (
75
+ <tr
76
+ data-slot="table-row"
77
+ ref={ref}
78
+ className={className}
79
+ {...props}
80
+ />
81
+ )
82
+ })
83
+
84
+ const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
85
+ function TableHead({ className, scope = "col", ...props }, ref) {
86
+ return (
87
+ <th
88
+ data-slot="table-head"
89
+ ref={ref}
90
+ className={cn("text-start", className)}
91
+ scope={scope}
92
+ {...props}
93
+ />
94
+ )
95
+ }
96
+ )
97
+
98
+ const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(
99
+ function TableCell({ className, ...props }, ref) {
100
+ return (
101
+ <td
102
+ data-slot="table-cell"
103
+ ref={ref}
104
+ className={className}
105
+ {...props}
106
+ />
107
+ )
108
+ }
109
+ )
110
+
111
+ export {
112
+ DataTableSurface,
113
+ TableBody,
114
+ TableCell,
115
+ TableHead,
116
+ TableHeader,
117
+ TableRoot,
118
+ TableRowElement,
119
+ }
120
+ export type {
121
+ DataTableSurfaceProps,
122
+ TableBodyProps,
123
+ TableCellProps,
124
+ TableHeadProps,
125
+ TableRootProps,
126
+ TableRowElementProps,
127
+ TableSectionProps,
128
+ }
@@ -154,7 +154,6 @@ function Tabs({
154
154
  defaultValue ??
155
155
  normalizedItems[defaultActiveIndex]?.value ??
156
156
  normalizedItems[0]?.value
157
- const hasPanels = normalizedItems.some((item) => item.content !== undefined)
158
157
 
159
158
  function handleValueChange(nextValue: string) {
160
159
  onValueChange?.(nextValue)
@@ -194,15 +193,11 @@ function Tabs({
194
193
  </TabsTrigger>
195
194
  ))}
196
195
  </TabsList>
197
- {hasPanels
198
- ? normalizedItems.map((item) =>
199
- item.content === undefined ? null : (
200
- <TabsContent key={`${item.value}-panel`} value={item.value}>
201
- {item.content}
202
- </TabsContent>
203
- )
204
- )
205
- : null}
196
+ {normalizedItems.map((item) => (
197
+ <TabsContent key={`${item.value}-panel`} value={item.value}>
198
+ {item.content ?? null}
199
+ </TabsContent>
200
+ ))}
206
201
  {children}
207
202
  </TabsRoot>
208
203
  )
@@ -348,7 +348,7 @@ function TagAvatar({
348
348
  <span
349
349
  data-slot="tag-avatar"
350
350
  className={cn(
351
- "flex size-[var(--bh-space-4xl-20)] shrink-0 items-center justify-center overflow-hidden rounded-[var(--bh-radius-full)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-neutral-default)] text-[length:var(--bh-text-body-3xs-semibold-font-size)] font-[var(--bh-text-body-3xs-semibold-font-weight)] leading-[var(--bh-text-body-3xs-semibold-line-height)] text-[var(--bh-content-on-color)]",
351
+ "flex size-[var(--bh-space-4xl-20)] shrink-0 items-center justify-center overflow-hidden rounded-[var(--bh-radius-full)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-neutral-bold)] text-[length:var(--bh-text-body-3xs-semibold-font-size)] font-[var(--bh-text-body-3xs-semibold-font-weight)] leading-[var(--bh-text-body-3xs-semibold-line-height)] text-[var(--bh-content-on-neutral)]",
352
352
  className
353
353
  )}
354
354
  {...props}
@@ -367,7 +367,7 @@ function TagIcon({
367
367
  <span
368
368
  data-slot="tag-icon"
369
369
  className={cn(
370
- "flex size-[var(--bh-space-4xl-20)] shrink-0 items-center justify-center text-current [&_svg]:size-[var(--bh-space-4xl-20)] [&_svg]:shrink-0",
370
+ "flex size-[var(--bh-icon-slot-size-default)] shrink-0 items-center justify-center text-current [&_svg]:size-[var(--bh-icon-size-default)] [&_svg]:shrink-0",
371
371
  className
372
372
  )}
373
373
  {...props}
@@ -78,7 +78,7 @@ const Toggle = React.forwardRef<
78
78
  >
79
79
  <SwitchPrimitive.Thumb
80
80
  data-slot="toggle-thumb"
81
- className="pointer-events-none flex size-[var(--bh-toggle-handle-size)] translate-x-0 items-center justify-center rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-switch-handle)] text-[var(--bh-content-on-light)] shadow-[var(--shadow-component-default)] transition-[background-color,transform] group-data-[disabled]/toggle:bg-[var(--bh-interactive-switch-handle-disabled)] group-data-[state=checked]/toggle:translate-x-[var(--bh-toggle-offset)] rtl:group-data-[state=checked]/toggle:-translate-x-[var(--bh-toggle-offset)]"
81
+ className="pointer-events-none flex size-[var(--bh-toggle-handle-size)] translate-x-0 items-center justify-center rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-switch-handle)] text-[var(--bh-content-on-light)] shadow-[var(--shadow-md)] transition-[background-color,transform] group-data-[disabled]/toggle:bg-[var(--bh-interactive-switch-handle-disabled)] group-data-[state=checked]/toggle:translate-x-[var(--bh-toggle-offset)] rtl:group-data-[state=checked]/toggle:-translate-x-[var(--bh-toggle-offset)]"
82
82
  >
83
83
  {showIcon ? (
84
84
  <span
@@ -190,7 +190,7 @@ const ToggleField = React.forwardRef<HTMLDivElement, ToggleFieldProps>(
190
190
  data-slot="toggle-field-description"
191
191
  dir="auto"
192
192
  id={descriptionId}
193
- className="w-full min-w-0 break-words 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-subtle)] group-data-[disabled=true]/toggle-field:text-[var(--bh-content-disabled)]"
193
+ className="w-full min-w-0 break-words 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-subtle)]"
194
194
  >
195
195
  {description}
196
196
  </span>
@@ -39,7 +39,7 @@ const toolbarVariants = cva(
39
39
  )
40
40
 
41
41
  const toolbarSectionVariants = cva(
42
- "flex min-w-0 items-center gap-[var(--bh-space-xl-12)]",
42
+ "flex max-w-full min-w-0 items-center gap-[var(--bh-space-xl-12)]",
43
43
  {
44
44
  variants: {
45
45
  align: {
@@ -49,8 +49,8 @@ const toolbarSectionVariants = cva(
49
49
  between: "justify-between",
50
50
  },
51
51
  grow: {
52
- true: "flex-1",
53
- false: "shrink-0",
52
+ true: "flex-[1_1_var(--bh-space-19xl-384)]",
53
+ false: "flex-[0_1_auto]",
54
54
  },
55
55
  stack: {
56
56
  true: "flex-col items-stretch",
@@ -58,20 +58,20 @@ const toolbarSectionVariants = cva(
58
58
  },
59
59
  wrap: {
60
60
  true: "flex-wrap",
61
- false: "",
61
+ false: "overflow-x-auto",
62
62
  },
63
63
  },
64
64
  defaultVariants: {
65
65
  align: "start",
66
66
  grow: false,
67
67
  stack: false,
68
- wrap: false,
68
+ wrap: true,
69
69
  },
70
70
  }
71
71
  )
72
72
 
73
73
  const toolbarButtonVariants = cva(
74
- "group/toolbar-button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--shadow-button-focus)] disabled:pointer-events-none disabled:shadow-none aria-invalid:border-[length:var(--bh-border-width-default)] aria-invalid:border-[var(--bh-border-danger-strong)] aria-invalid:shadow-[var(--shadow-button-danger-focus)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--bh-button-icon-size)] [&_svg[data-icon='inline-start']]:ms-[var(--bh-button-icon-offset)] [&_svg[data-icon='inline-end']]:me-[var(--bh-button-icon-offset)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
74
+ "group/toolbar-button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--shadow-button-focus)] disabled:pointer-events-none disabled:shadow-none aria-invalid:border-[length:var(--bh-border-width-default)] aria-invalid:border-[var(--bh-border-danger-strong)] aria-invalid:shadow-[var(--shadow-button-danger-focus)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--bh-button-icon-size)] [&_svg:not([class*='size-'])]:m-[calc((var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-start']]:ms-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-end']]:me-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
75
75
  {
76
76
  variants: {
77
77
  variant: {
@@ -38,7 +38,7 @@ const tooltipContentVariants = cva(
38
38
  dark:
39
39
  "[--bh-tooltip-bg:var(--bh-bg-always-dark)] [--bh-tooltip-border:transparent] [--bh-tooltip-close-fg:var(--bh-content-on-color)] [--bh-tooltip-fg:var(--bh-content-on-color)] [--bh-tooltip-shadow:none] [--bh-tooltip-shortcut-bg:var(--bh-bg-neutral-soft)] [--bh-tooltip-shortcut-border:var(--bh-border-inverse-subtle)]",
40
40
  default:
41
- "[--bh-tooltip-bg:var(--bh-bg-raised)] [--bh-tooltip-border:var(--bh-border-subtle)] [--bh-tooltip-close-fg:var(--bh-content-subtle)] [--bh-tooltip-fg:var(--bh-content-default)] [--bh-tooltip-shadow:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-tooltip-border),var(--shadow-component-default)] [--bh-tooltip-shortcut-bg:var(--bh-bg-neutral-subtle)] [--bh-tooltip-shortcut-border:var(--bh-border-subtle)]",
41
+ "[--bh-tooltip-bg:var(--bh-bg-raised)] [--bh-tooltip-border:var(--bh-border-subtle)] [--bh-tooltip-close-fg:var(--bh-content-subtle)] [--bh-tooltip-fg:var(--bh-content-default)] [--bh-tooltip-shadow:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-tooltip-border),var(--shadow-lg)] [--bh-tooltip-shortcut-bg:var(--bh-bg-neutral-subtle)] [--bh-tooltip-shortcut-border:var(--bh-border-subtle)]",
42
42
  },
43
43
  size: {
44
44
  sm:
@@ -54,7 +54,7 @@ export function AttributeDemo() {
54
54
  </AttributeItem>
55
55
  <AttributeItem label="Invoice">
56
56
  <span className="inline-flex min-w-0 items-center gap-[var(--bh-space-md-8)]">
57
- <PaperclipIcon className="size-[var(--bh-space-4xl-20)] shrink-0 text-[var(--bh-content-muted)]" />
57
+ <PaperclipIcon className="m-[var(--bh-icon-slot-inset-default)] size-[var(--bh-icon-size-default)] shrink-0 text-[var(--bh-content-muted)]" />
58
58
  INV-2024-00123.pdf
59
59
  </span>
60
60
  </AttributeItem>
@@ -8,6 +8,7 @@ import {
8
8
  AvatarImage,
9
9
  AvatarStatus,
10
10
  } from "@/components/ui/avatar"
11
+ import { Button } from "@/components/ui/button"
11
12
  import { PlusIcon } from "lucide-react"
12
13
 
13
14
  const avatarUrl = new URL("../assets/avatars/avatar-01.jpg", import.meta.url).href
@@ -84,13 +85,15 @@ export function AvatarDemo() {
84
85
  ))}
85
86
  <AvatarGroupCount size="md">+10</AvatarGroupCount>
86
87
  </AvatarGroup>
87
- <Avatar
88
+ <Button
88
89
  aria-label="Add member"
89
- className="border border-dashed border-[var(--bh-border-subtle)] bg-[var(--bh-bg-default)] text-[var(--bh-content-subtle)]"
90
- size="md"
90
+ className="size-[var(--bh-avatar-size-md)] rounded-[var(--bh-radius-full)] border-dashed"
91
+ size="icon-sm"
92
+ type="button"
93
+ variant="outline"
91
94
  >
92
95
  <PlusIcon aria-hidden="true" className="size-[var(--bh-space-3xl-16)]" />
93
- </Avatar>
96
+ </Button>
94
97
  </div>
95
98
 
96
99
  <div className="flex items-center gap-[var(--bh-space-xl-12)]">
@@ -202,7 +202,6 @@ function ToolRow({ item }: { item: ToolItem }) {
202
202
  <AvatarIcon
203
203
  className={[
204
204
  "border border-[var(--bh-border-subtle)]",
205
- "[&_svg]:size-[var(--bh-space-4xl-20)]",
206
205
  toolToneClassNames[tone],
207
206
  ].join(" ")}
208
207
  size="lg"
@@ -0,0 +1,29 @@
1
+ import {
2
+ ActivityHeatmap,
3
+ type ActivityHeatmapItem,
4
+ type ActivityHeatmapLevel,
5
+ } from "@/components/ui/expanded/ActivityHeatmap"
6
+
7
+ const activityItems: ActivityHeatmapItem[] = Array.from(
8
+ { length: 39 * 7 },
9
+ (_, index) => {
10
+ const column = Math.floor(index / 7)
11
+ const row = index % 7
12
+ const level = ((column * 11 + row * 7 + column * row * 3) % 5) as ActivityHeatmapLevel
13
+
14
+ return {
15
+ id: `activity-${index}`,
16
+ label: `Activity level ${level} of 4`,
17
+ level,
18
+ }
19
+ }
20
+ )
21
+
22
+ export function ActivityHeatmapDemo() {
23
+ return (
24
+ <ActivityHeatmap
25
+ ariaLabel="Weekly activity over the last 39 weeks"
26
+ items={activityItems}
27
+ />
28
+ )
29
+ }
@@ -30,16 +30,16 @@ const rtlBreadcrumbItems: BreadcrumbItem[] = [
30
30
  export function BreadcrumbsDemo() {
31
31
  return (
32
32
  <div className="grid gap-[var(--bh-space-3xl-16)]">
33
- <Breadcrumbs aria-label="Breadcrumb" items={breadcrumbItems} overflowLabel="More" />
33
+ <Breadcrumbs aria-label="Default breadcrumb" items={breadcrumbItems} overflowLabel="More" />
34
34
  <Breadcrumbs
35
- aria-label="Breadcrumb"
35
+ aria-label="Raised breadcrumb"
36
36
  items={breadcrumbItems}
37
37
  overflowLabel="More"
38
38
  separator="chevron"
39
39
  style="raised"
40
40
  />
41
41
  <Breadcrumbs
42
- aria-label="Breadcrumb"
42
+ aria-label="Collapsed breadcrumb"
43
43
  items={longBreadcrumbItems}
44
44
  maxItems={4}
45
45
  overflowLabel="More"
@@ -1,4 +1,4 @@
1
- import { Kbd } from "@/components/ui/kbd"
1
+ import { Kbd, Shortcut } from "@/components/ui/kbd"
2
2
 
3
3
  export function KbdDemo() {
4
4
  return (
@@ -6,6 +6,7 @@ export function KbdDemo() {
6
6
  <Kbd>⌘</Kbd>
7
7
  <Kbd>K</Kbd>
8
8
  <Kbd size="md">Shift</Kbd>
9
+ <Shortcut keys={["Mod", "K"]} />
9
10
  </div>
10
11
  )
11
12
  }
@@ -6,14 +6,14 @@ import {
6
6
  export function PaginationDemo() {
7
7
  return (
8
8
  <div className="grid gap-[var(--bh-space-5xl-24)]">
9
- <Pagination />
10
- <Pagination variant="ghost" />
9
+ <Pagination aria-label="Soft numeric pagination" />
10
+ <Pagination aria-label="Ghost numeric pagination" variant="ghost" />
11
11
 
12
- <Pagination type="simple" />
13
- <Pagination type="summary" />
12
+ <Pagination aria-label="Simple pagination" type="simple" />
13
+ <Pagination aria-label="Summary pagination" type="summary" />
14
14
 
15
15
  <div dir="rtl">
16
- <Pagination dir="rtl" messages={arabicPaginationMessages} />
16
+ <Pagination aria-label="ترقيم الصفحات العربية" dir="rtl" messages={arabicPaginationMessages} />
17
17
  </div>
18
18
  </div>
19
19
  )
@@ -1,41 +1,79 @@
1
- import { HomeIcon, SettingsIcon, UsersIcon } from "lucide-react"
1
+ import {
2
+ CreditCardIcon,
3
+ InboxIcon,
4
+ LayoutGridIcon,
5
+ SettingsIcon,
6
+ UsersIcon,
7
+ } from "lucide-react"
2
8
 
9
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar"
3
10
  import {
4
11
  Sidebar,
5
12
  SidebarBody,
6
13
  SidebarFooter,
14
+ SidebarFooterUser,
7
15
  SidebarGroup,
8
- SidebarGroupLabel,
9
16
  SidebarHeader,
17
+ SidebarHeaderWorkspaceSwitch,
10
18
  SidebarInset,
11
19
  SidebarMenu,
20
+ SidebarMenuBadge,
12
21
  SidebarMenuButton,
13
22
  SidebarMenuItem,
23
+ SidebarMenuSub,
24
+ SidebarMenuSubButton,
14
25
  SidebarProvider,
15
- SidebarTrigger,
16
26
  } from "@/components/ui/sidebar"
17
27
 
18
28
  export function SidebarDemo() {
19
29
  return (
20
- <SidebarProvider className="flex h-[var(--bh-space-18xl-320)]" defaultCollapsed={false}>
30
+ <SidebarProvider className="flex h-[var(--bh-space-19xl-384)]">
21
31
  <Sidebar>
22
32
  <SidebarHeader>
23
- <div className="min-w-0 flex-1 text-sm font-semibold group-data-[collapsed=true]/sidebar:hidden">
24
- Banhaten
25
- </div>
26
- <SidebarTrigger />
33
+ <SidebarHeaderWorkspaceSwitch
34
+ image={
35
+ <span className="grid size-full place-items-center bg-[var(--bh-bg-brand-default)] text-xs font-semibold text-[var(--bh-content-on-brand)]">
36
+ AC
37
+ </span>
38
+ }
39
+ label="Acme Inc"
40
+ />
27
41
  </SidebarHeader>
28
42
  <SidebarBody>
29
43
  <SidebarGroup>
30
- <SidebarGroupLabel>Workspace</SidebarGroupLabel>
31
44
  <SidebarMenu>
32
45
  <SidebarMenuItem>
33
- <SidebarMenuButton active icon={<HomeIcon />}>
46
+ <SidebarMenuButton icon={<LayoutGridIcon />}>
34
47
  Overview
35
48
  </SidebarMenuButton>
36
49
  </SidebarMenuItem>
37
50
  <SidebarMenuItem>
38
- <SidebarMenuButton icon={<UsersIcon />}>Members</SidebarMenuButton>
51
+ <SidebarMenuButton icon={<CreditCardIcon />}>
52
+ Transactions
53
+ </SidebarMenuButton>
54
+ <SidebarMenuSub>
55
+ <SidebarMenuItem>
56
+ <SidebarMenuSubButton showLine>
57
+ Payments
58
+ </SidebarMenuSubButton>
59
+ </SidebarMenuItem>
60
+ <SidebarMenuItem>
61
+ <SidebarMenuSubButton active showLine>
62
+ Refunds
63
+ </SidebarMenuSubButton>
64
+ </SidebarMenuItem>
65
+ </SidebarMenuSub>
66
+ </SidebarMenuItem>
67
+ <SidebarMenuItem>
68
+ <SidebarMenuButton
69
+ badge={<SidebarMenuBadge>12</SidebarMenuBadge>}
70
+ icon={<InboxIcon />}
71
+ >
72
+ Inbox
73
+ </SidebarMenuButton>
74
+ </SidebarMenuItem>
75
+ <SidebarMenuItem>
76
+ <SidebarMenuButton icon={<UsersIcon />}>Customers</SidebarMenuButton>
39
77
  </SidebarMenuItem>
40
78
  <SidebarMenuItem>
41
79
  <SidebarMenuButton icon={<SettingsIcon />}>Settings</SidebarMenuButton>
@@ -44,10 +82,18 @@ export function SidebarDemo() {
44
82
  </SidebarGroup>
45
83
  </SidebarBody>
46
84
  <SidebarFooter>
47
- <SidebarMenuButton icon={<SettingsIcon />}>Admin</SidebarMenuButton>
85
+ <SidebarFooterUser
86
+ avatar={
87
+ <Avatar aria-label="Olivia Rhye" className="size-full" size="sm">
88
+ <AvatarFallback size="sm">OR</AvatarFallback>
89
+ </Avatar>
90
+ }
91
+ name="Olivia Rhye"
92
+ supportText="olivia@acme.com"
93
+ />
48
94
  </SidebarFooter>
49
95
  </Sidebar>
50
- <SidebarInset className="grid place-items-center bg-[var(--bh-bg-subtle)] p-[var(--bh-space-4xl-20)] text-sm text-[var(--bh-content-subtle)]">
96
+ <SidebarInset className="grid min-w-0 place-items-center bg-[var(--bh-bg-subtle)] p-[var(--bh-space-4xl-20)] text-sm text-[var(--bh-content-subtle)]">
51
97
  App content
52
98
  </SidebarInset>
53
99
  </SidebarProvider>
@@ -15,7 +15,7 @@ This project uses the Banhaten design system. Treat the installed component API
15
15
  - Do not align Banhaten controls with raw height, padding, radius, or font-size utilities. Width and responsive layout utilities are allowed.
16
16
  - Use `DatePicker` or `DateRangePicker` for dates. Do not simulate calendar fields with `Input` or native date inputs.
17
17
  - Pass explicit `leadingIcon` and `trailingIcon` nodes. Use `showAtSign` or `showInfo` only for those exact semantic affordances. The legacy `hasLeadingIcon` and `hasInformationIcon` flags are deprecated.
18
- - Use `shortcutKeys={["Mod", "K"]}` or `KbdShortcut` for shortcuts. Never hard-code a single Command glyph for every platform.
18
+ - Use `shortcutKeys={["Mod", "K"]}` on search inputs, `Shortcut` for one compact combined hint, or `KbdShortcut` when each physical key needs its own keycap. Never hard-code a single Command glyph for every platform.
19
19
  - Use `SegmentedControl` for small mutually exclusive view, density, period, or numeric choices. Do not rebuild joined numeric buttons locally.
20
20
  - Use functional `Select` for option menus. `ToolbarSelect` is only a trigger-shaped toolbar primitive.
21
21
  - Preserve inherited `dir`; use logical spacing and alignment utilities for RTL-safe composition.