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
@@ -9,7 +9,7 @@ const kbdVariants = cva(
9
9
  [
10
10
  "inline-flex min-w-[var(--bh-space-5xl-24)] items-center justify-center whitespace-nowrap rounded-[var(--bh-radius-md-6)]",
11
11
  "border border-[var(--bh-border-default)] bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-subtle)]",
12
- "font-[var(--bh-font-family)] text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)]",
12
+ "[font-family:var(--bh-font-family)] text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)]",
13
13
  "leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] shadow-[var(--shadow-button-soft)]",
14
14
  "data-[disabled=true]:border-[var(--bh-border-disabled)] data-[disabled=true]:bg-[var(--bh-interactive-soft-disabled)] data-[disabled=true]:text-[var(--bh-content-disabled)] data-[disabled=true]:shadow-none",
15
15
  ],
@@ -26,6 +26,14 @@ const kbdVariants = cva(
26
26
  }
27
27
  )
28
28
 
29
+ const shortcutVariants = cva([
30
+ "inline-flex h-[var(--bh-space-4xl-20)] min-w-[var(--bh-space-4xl-20)] shrink-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-radius-sm-4)] px-[var(--bh-space-xs-4)]",
31
+ "border border-[var(--bh-border-default)] bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-subtle)]",
32
+ "[font-family:var(--bh-font-family)] text-[length:var(--bh-text-body-2xs-medium-font-size)] font-[var(--bh-text-body-2xs-medium-font-weight)]",
33
+ "leading-[var(--bh-text-body-2xs-medium-line-height)] tracking-[var(--bh-text-body-2xs-medium-letter-spacing)]",
34
+ "data-[disabled=true]:border-[var(--bh-border-disabled)] data-[disabled=true]:bg-[var(--bh-interactive-soft-disabled)] data-[disabled=true]:text-[var(--bh-content-disabled)]",
35
+ ])
36
+
29
37
  type KbdProps = React.ComponentProps<"kbd"> &
30
38
  VariantProps<typeof kbdVariants> & {
31
39
  disabled?: boolean
@@ -48,6 +56,55 @@ const Kbd = React.forwardRef<HTMLElement, KbdProps>(function Kbd(
48
56
 
49
57
  type ShortcutPlatform = "auto" | "mac" | "windows" | "linux"
50
58
 
59
+ type ShortcutProps = Omit<React.ComponentProps<"kbd">, "children"> & {
60
+ children?: React.ReactNode
61
+ disabled?: boolean
62
+ keys?: readonly string[]
63
+ platform?: ShortcutPlatform
64
+ }
65
+
66
+ const Shortcut = React.forwardRef<HTMLElement, ShortcutProps>(function Shortcut(
67
+ {
68
+ "aria-label": ariaLabel,
69
+ children,
70
+ className,
71
+ disabled = false,
72
+ keys = [],
73
+ platform = "auto",
74
+ ...props
75
+ },
76
+ ref
77
+ ) {
78
+ const resolvedPlatform = useShortcutPlatform(platform)
79
+ const resolvedKeys = keys.map((key) =>
80
+ resolveShortcutKey(key, resolvedPlatform)
81
+ )
82
+ const content =
83
+ children ?? formatShortcutKeys(resolvedKeys, resolvedPlatform)
84
+ const accessibleLabel =
85
+ ariaLabel ??
86
+ (resolvedKeys.length > 0
87
+ ? resolvedKeys.map(readableShortcutKey).join(" plus ")
88
+ : typeof children === "string"
89
+ ? readableShortcutText(children)
90
+ : undefined)
91
+
92
+ return (
93
+ <kbd
94
+ aria-label={accessibleLabel}
95
+ className={cn(shortcutVariants(), className)}
96
+ data-disabled={disabled ? "true" : undefined}
97
+ data-platform={resolvedPlatform}
98
+ data-slot="shortcut"
99
+ dir="ltr"
100
+ ref={ref}
101
+ {...props}
102
+ >
103
+ {content}
104
+ </kbd>
105
+ )
106
+ })
107
+
51
108
  type KbdShortcutProps = Omit<React.ComponentProps<"span">, "children"> & {
52
109
  disabled?: boolean
53
110
  keys: readonly string[]
@@ -68,18 +125,7 @@ const KbdShortcut = React.forwardRef<HTMLSpanElement, KbdShortcutProps>(
68
125
  },
69
126
  ref
70
127
  ) {
71
- const [resolvedPlatform, setResolvedPlatform] = React.useState<
72
- Exclude<ShortcutPlatform, "auto">
73
- >(platform === "auto" ? "mac" : platform)
74
-
75
- React.useEffect(() => {
76
- if (platform !== "auto") {
77
- setResolvedPlatform(platform)
78
- return
79
- }
80
-
81
- setResolvedPlatform(detectShortcutPlatform())
82
- }, [platform])
128
+ const resolvedPlatform = useShortcutPlatform(platform)
83
129
 
84
130
  const resolvedKeys = keys.map((key) =>
85
131
  resolveShortcutKey(key, resolvedPlatform)
@@ -117,6 +163,25 @@ const KbdShortcut = React.forwardRef<HTMLSpanElement, KbdShortcutProps>(
117
163
  }
118
164
  )
119
165
 
166
+ function useShortcutPlatform(
167
+ platform: ShortcutPlatform
168
+ ): Exclude<ShortcutPlatform, "auto"> {
169
+ const [resolvedPlatform, setResolvedPlatform] = React.useState<
170
+ Exclude<ShortcutPlatform, "auto">
171
+ >(platform === "auto" ? "mac" : platform)
172
+
173
+ React.useEffect(() => {
174
+ if (platform !== "auto") {
175
+ setResolvedPlatform(platform)
176
+ return
177
+ }
178
+
179
+ setResolvedPlatform(detectShortcutPlatform())
180
+ }, [platform])
181
+
182
+ return resolvedPlatform
183
+ }
184
+
120
185
  function detectShortcutPlatform(): Exclude<ShortcutPlatform, "auto"> {
121
186
  if (typeof navigator === "undefined") return "mac"
122
187
  const platform = navigator.platform.toLowerCase()
@@ -147,5 +212,34 @@ function readableShortcutKey(key: string) {
147
212
  return key
148
213
  }
149
214
 
150
- export { Kbd, KbdShortcut, kbdVariants }
151
- export type { KbdProps, KbdShortcutProps, ShortcutPlatform }
215
+ function formatShortcutKeys(
216
+ keys: readonly string[],
217
+ platform: Exclude<ShortcutPlatform, "auto">
218
+ ) {
219
+ if (platform === "mac" && keys.slice(0, -1).every(isMacShortcutSymbol)) {
220
+ return keys.join("")
221
+ }
222
+
223
+ return keys.join(" ")
224
+ }
225
+
226
+ function isMacShortcutSymbol(key: string) {
227
+ return ["⌘", "⌥", "⇧", "⌃"].includes(key)
228
+ }
229
+
230
+ function readableShortcutText(value: string) {
231
+ return value
232
+ .replaceAll("⌘", "Command ")
233
+ .replaceAll("⌥", "Option ")
234
+ .replaceAll("⇧", "Shift ")
235
+ .replaceAll("⌃", "Control ")
236
+ .trim()
237
+ }
238
+
239
+ export { Kbd, KbdShortcut, Shortcut, kbdVariants, shortcutVariants }
240
+ export type {
241
+ KbdProps,
242
+ KbdShortcutProps,
243
+ ShortcutPlatform,
244
+ ShortcutProps,
245
+ }
@@ -90,7 +90,7 @@ const menuItemIconVariants = cva(
90
90
  variants: {
91
91
  position: {
92
92
  leading:
93
- "h-[var(--bh-menu-leading-icon-size)] ps-[var(--bh-menu-icon-padding)] [&_svg]:size-[var(--bh-menu-leading-icon-size)]",
93
+ "h-[var(--bh-menu-leading-icon-slot-size)] ps-[var(--bh-menu-icon-padding)] [&_svg]:size-[var(--bh-menu-leading-icon-size)]",
94
94
  trailing:
95
95
  "size-[var(--bh-menu-trailing-icon-size)] text-[var(--bh-content-muted)] [&_svg]:size-[var(--bh-menu-trailing-icon-size)]",
96
96
  },
@@ -283,11 +283,12 @@ type MenuSubTriggerProps = React.ComponentProps<"div"> &
283
283
  }
284
284
 
285
285
  const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuItem({
286
+ "aria-checked": ariaChecked,
286
287
  className,
287
288
  kind = "default",
288
289
  disabled = false,
289
290
  asChild = false,
290
- role = "menuitem",
291
+ role,
291
292
  tabIndex,
292
293
  children,
293
294
  onSelect,
@@ -298,15 +299,23 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
298
299
  const isStaticMenu = React.useContext(MenuStaticContext)
299
300
  const selectedKind: MenuItemKind = kind ?? "default"
300
301
  const isDisabled = disabled
302
+ const switchState = findMenuItemSwitchState(children)
303
+ const resolvedRole = role ??
304
+ (switchState === undefined ? "menuitem" : "menuitemcheckbox")
305
+ const resolvedChecked =
306
+ resolvedRole === "menuitemcheckbox"
307
+ ? (ariaChecked ?? switchState ?? false)
308
+ : undefined
301
309
 
302
310
  const item = (
303
311
  <Comp
312
+ aria-checked={resolvedChecked}
304
313
  aria-disabled={isDisabled ? true : undefined}
305
314
  data-disabled={isDisabled ? "true" : "false"}
306
315
  data-kind={selectedKind}
307
316
  data-slot="menu-item"
308
317
  ref={ref}
309
- role={role}
318
+ role={resolvedRole}
310
319
  tabIndex={isDisabled ? -1 : (tabIndex ?? 0)}
311
320
  className={cn(menuItemVariants({ kind: selectedKind, className }))}
312
321
  {...props}
@@ -319,6 +328,20 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
319
328
 
320
329
  if (isStaticMenu) return item
321
330
 
331
+ if (switchState !== undefined) {
332
+ return (
333
+ <DropdownMenuPrimitive.CheckboxItem
334
+ asChild
335
+ checked={switchState}
336
+ disabled={isDisabled}
337
+ onSelect={onSelect}
338
+ textValue={textValue ?? getMenuItemTextValue(children)}
339
+ >
340
+ {item}
341
+ </DropdownMenuPrimitive.CheckboxItem>
342
+ )
343
+ }
344
+
322
345
  return (
323
346
  <DropdownMenuPrimitive.Item
324
347
  asChild
@@ -333,6 +356,27 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
333
356
 
334
357
  MenuItem.displayName = "MenuItem"
335
358
 
359
+ function findMenuItemSwitchState(children: React.ReactNode): boolean | undefined {
360
+ let state: boolean | undefined
361
+
362
+ React.Children.forEach(children, (child) => {
363
+ if (state !== undefined || !React.isValidElement(child)) return
364
+
365
+ const element = child as React.ReactElement<{
366
+ active?: boolean
367
+ children?: React.ReactNode
368
+ }>
369
+ if (element.type === MenuItemSwitch) {
370
+ state = Boolean(element.props.active)
371
+ return
372
+ }
373
+
374
+ state = findMenuItemSwitchState(element.props.children)
375
+ })
376
+
377
+ return state
378
+ }
379
+
336
380
  const MenuSubTrigger = React.forwardRef<HTMLDivElement, MenuSubTriggerProps>(function MenuSubTrigger({
337
381
  className,
338
382
  kind = "default",
@@ -576,7 +620,10 @@ function MenuItemBadge({
576
620
  )
577
621
  }
578
622
 
579
- type MenuItemSwitchProps = Omit<React.ComponentProps<"span">, "role"> & {
623
+ type MenuItemSwitchProps = Omit<
624
+ React.ComponentProps<"span">,
625
+ "aria-checked" | "aria-label" | "aria-labelledby" | "role"
626
+ > & {
580
627
  active?: boolean
581
628
  disabled?: boolean
582
629
  }
@@ -589,12 +636,10 @@ function MenuItemSwitch({
589
636
  }: MenuItemSwitchProps) {
590
637
  return (
591
638
  <span
592
- aria-checked={active}
593
- aria-disabled={disabled || undefined}
639
+ aria-hidden="true"
594
640
  data-active={active ? "true" : "false"}
595
641
  data-disabled={disabled ? "true" : "false"}
596
642
  data-slot="menu-item-switch"
597
- role="switch"
598
643
  className={cn(
599
644
  "flex h-[var(--bh-menu-switch-height)] w-[var(--bh-menu-switch-width)] shrink-0 items-center rounded-[var(--bh-radius-full)] p-[var(--bh-menu-switch-padding)] group-data-[disabled=true]/menu-item:bg-[var(--bh-interactive-switch-disabled)]",
600
645
  active
@@ -677,6 +722,8 @@ function MenuProgress({
677
722
  ...props
678
723
  }: MenuProgressProps) {
679
724
  const clampedValue = Math.min(100, Math.max(0, value))
725
+ const labelId = React.useId()
726
+ const hasLabel = hasRenderableContent(label)
680
727
 
681
728
  return (
682
729
  <div
@@ -688,6 +735,8 @@ function MenuProgress({
688
735
  {...props}
689
736
  >
690
737
  <div
738
+ aria-label={hasLabel ? undefined : "Progress"}
739
+ aria-labelledby={hasLabel ? labelId : undefined}
691
740
  aria-valuemax={100}
692
741
  aria-valuemin={0}
693
742
  aria-valuenow={clampedValue}
@@ -717,6 +766,7 @@ function MenuProgress({
717
766
  >
718
767
  {hasRenderableContent(label) ? (
719
768
  <span
769
+ id={labelId}
720
770
  data-slot="menu-progress-label-text"
721
771
  dir="auto"
722
772
  className="truncate text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)] leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] text-[var(--bh-content-default)]"
@@ -39,7 +39,7 @@ type ConfirmModalMessages = {
39
39
 
40
40
  const modalSurfaceBaseClassName = [
41
41
  "flex max-h-[calc(100vh-(var(--bh-space-8xl-48)*2))] w-[var(--bh-modal-width)] max-w-[calc(100vw-(var(--bh-space-4xl-20)*2))] flex-col overflow-hidden rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--shadow-modal)] outline-none",
42
- "[--bh-modal-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-modal-border),var(--shadow-component-default)]",
42
+ "[--bh-modal-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-modal-border),var(--shadow-2xl)]",
43
43
  ]
44
44
 
45
45
  const modalSizeVariants = {
@@ -145,7 +145,7 @@ const NavigationMenuIndicator = React.forwardRef<
145
145
  className={cn("top-full z-[var(--bh-z-popover)] flex h-[var(--bh-space-lg-10)] items-end justify-center overflow-hidden", className)}
146
146
  {...props}
147
147
  >
148
- <div className="relative top-[var(--bh-space-xs-4)] size-[var(--bh-space-lg-10)] rotate-45 rounded-tl-[var(--bh-radius-sm-4)] bg-[var(--bh-bg-raised)] shadow-[var(--shadow-popover)]" />
148
+ <div className="relative top-[var(--bh-space-xs-4)] size-[var(--bh-space-lg-10)] rotate-45 rounded-tl-[var(--bh-radius-sm-4)] bg-[var(--bh-bg-raised)] shadow-[inset_0_0_0_var(--bh-border-width-default)_var(--bh-border-subtle)]" />
149
149
  </NavigationMenuPrimitive.Indicator>
150
150
  )
151
151
  })
@@ -0,0 +1,36 @@
1
+ .ds-pagination {
2
+ container-type: inline-size;
3
+ }
4
+
5
+ .ds-pagination__controls {
6
+ overflow-x: auto;
7
+ overscroll-behavior-inline: contain;
8
+ scrollbar-width: thin;
9
+ }
10
+
11
+ /* Approved exception: container-query breakpoints stay literal because CSS vars are not reliable in query conditions. */
12
+ @container (max-width: 28rem) {
13
+ .ds-pagination [data-slot="pagination-caption"] {
14
+ width: 100%;
15
+ flex: 1 0 100%;
16
+ }
17
+
18
+ .ds-pagination[data-type="numeric"] .ds-pagination__controls {
19
+ width: 100%;
20
+ justify-content: center;
21
+ }
22
+
23
+ .ds-pagination[data-type="numeric"] .ds-pagination__pages > :nth-child(n + 4):not(:last-child) {
24
+ display: none;
25
+ }
26
+
27
+ .ds-pagination[data-type="simple"] .ds-pagination__controls {
28
+ margin-inline-start: auto;
29
+ }
30
+
31
+ .ds-pagination[data-type="summary"] [data-slot="pagination-summary"] {
32
+ order: -1;
33
+ width: 100%;
34
+ text-align: center;
35
+ }
36
+ }
@@ -5,6 +5,8 @@ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
5
5
  import { cn } from "@/lib/utils"
6
6
  import { useResolvedDirection } from "@/hooks/use-direction"
7
7
 
8
+ import "./pagination.css"
9
+
8
10
  type PaginationType = "numeric" | "simple" | "summary"
9
11
  type PaginationVariant = "soft" | "ghost"
10
12
  type PaginationPage =
@@ -65,7 +67,7 @@ const arabicPaginationMessages: PaginationMessages = {
65
67
 
66
68
  const paginationVariants = cva(
67
69
  [
68
- "flex max-w-full items-center",
70
+ "ds-pagination flex w-full max-w-full min-w-0 flex-wrap items-center",
69
71
  "text-[length:var(--bh-pagination-font-size)]",
70
72
  "leading-[var(--bh-pagination-line-height)]",
71
73
  "tracking-[var(--bh-pagination-letter-spacing)]",
@@ -74,11 +76,11 @@ const paginationVariants = cva(
74
76
  variants: {
75
77
  type: {
76
78
  numeric:
77
- "w-[var(--bh-pagination-numeric-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
79
+ "max-w-[var(--bh-pagination-numeric-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
78
80
  simple:
79
- "w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
81
+ "max-w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
80
82
  summary:
81
- "w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
83
+ "max-w-[var(--bh-pagination-simple-width)] justify-between gap-[var(--bh-pagination-root-gap)]",
82
84
  },
83
85
  showCaption: {
84
86
  true: "",
@@ -109,7 +111,7 @@ const paginationControlVariants = cva(
109
111
  "outline-none transition-[background-color,border-color,color,box-shadow]",
110
112
  "focus-visible:shadow-[var(--shadow-pagination-focus)]",
111
113
  "disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)]",
112
- "[&_svg]:pointer-events-none [&_svg]:size-[var(--bh-pagination-icon-size)] [&_svg]:shrink-0",
114
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
113
115
  "rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
114
116
  ],
115
117
  {
@@ -321,7 +323,7 @@ const Pagination = React.forwardRef<HTMLElement, PaginationProps>(function Pagin
321
323
  />
322
324
  <div
323
325
  data-slot="pagination-pages"
324
- className="flex shrink-0 items-center gap-[var(--bh-pagination-page-gap)]"
326
+ className="ds-pagination__pages flex min-w-0 shrink-0 items-center gap-[var(--bh-pagination-page-gap)]"
325
327
  >
326
328
  {pages.map((item, index) => (
327
329
  <PaginationPageItem
@@ -381,7 +383,7 @@ function PaginationControls({
381
383
  <div
382
384
  data-slot="pagination-controls"
383
385
  className={cn(
384
- "flex shrink-0 items-center",
386
+ "ds-pagination__controls flex max-w-full min-w-0 shrink-0 items-center",
385
387
  type === "numeric"
386
388
  ? "gap-[var(--bh-pagination-control-gap)]"
387
389
  : "gap-[var(--bh-pagination-action-gap)]"
@@ -505,13 +507,18 @@ function PaginationArrowIcon({ type }: { type: "previous" | "next" }) {
505
507
  const Icon = type === "previous" ? ChevronLeftIcon : ChevronRightIcon
506
508
 
507
509
  return (
508
- <Icon
509
- aria-hidden="true"
510
- data-rtl-flip="true"
510
+ <span
511
511
  data-slot="pagination-icon"
512
- focusable="false"
513
- strokeWidth="var(--bh-icon-stroke-225)"
514
- />
512
+ className="inline-flex size-[var(--bh-pagination-icon-slot-size)] shrink-0 items-center justify-center"
513
+ >
514
+ <Icon
515
+ aria-hidden="true"
516
+ className="size-[var(--bh-pagination-icon-size)]"
517
+ data-rtl-flip="true"
518
+ focusable="false"
519
+ strokeWidth="var(--bh-icon-stroke-225)"
520
+ />
521
+ </span>
515
522
  )
516
523
  }
517
524
 
@@ -12,7 +12,7 @@ type ProgressSliderSize = "default" | "lg"
12
12
  const progressSliderVariants = cva(
13
13
  [
14
14
  "group/progress-slider relative flex touch-none select-none items-center",
15
- "w-[var(--bh-progress-slider-width-current)] max-w-full",
15
+ "w-full max-w-[var(--bh-progress-slider-width-current)] min-w-0",
16
16
  "data-[disabled]:pointer-events-none data-[disabled]:opacity-[var(--bh-opacity-60)]",
17
17
  "[--bh-progress-slider-width-current:var(--bh-progress-slider-width-md)]",
18
18
  "[--bh-progress-slider-height-current:var(--bh-progress-slider-height-md)]",
@@ -579,7 +579,7 @@ function RadioCardMedia({
579
579
  <span
580
580
  data-slot="radio-card-icon"
581
581
  className={cn(
582
- "flex size-[var(--bh-space-4xl-20)] shrink-0 items-center justify-center text-[var(--bh-content-subtle)]",
582
+ "flex size-[var(--bh-icon-slot-size-default)] shrink-0 items-center justify-center text-[var(--bh-content-subtle)] [&_svg]:size-[var(--bh-icon-size-default)]",
583
583
  disabled && "text-[var(--bh-content-disabled)]"
584
584
  )}
585
585
  >
@@ -592,7 +592,6 @@ function RadioCardUserIcon() {
592
592
  return (
593
593
  <UserIcon
594
594
  aria-hidden="true"
595
- className="size-[var(--bh-space-4xl-20)]"
596
595
  strokeWidth="var(--bh-icon-stroke-200)"
597
596
  />
598
597
  )
@@ -17,7 +17,9 @@ import { cn } from "@/lib/utils"
17
17
  type ResizablePanelGroupProps = Omit<GroupProps, "orientation"> & {
18
18
  direction?: Orientation
19
19
  }
20
- type ResizablePanelProps = PanelProps
20
+ type ResizablePanelProps = PanelProps & {
21
+ contentTabIndex?: number
22
+ }
21
23
  type ResizableHandleProps = SeparatorProps & {
22
24
  withHandle?: boolean
23
25
  }
@@ -41,7 +43,39 @@ function ResizablePanelGroup({
41
43
  )
42
44
  }
43
45
 
44
- const ResizablePanel = Panel
46
+ function ResizablePanel({
47
+ className,
48
+ contentTabIndex = 0,
49
+ elementRef,
50
+ ...props
51
+ }: ResizablePanelProps) {
52
+ const setElementRef = React.useCallback(
53
+ (node: HTMLDivElement | null) => {
54
+ const content = node?.firstElementChild
55
+ if (content instanceof HTMLElement) {
56
+ content.tabIndex = contentTabIndex
57
+ content.setAttribute("data-slot", "resizable-panel-content")
58
+ }
59
+
60
+ if (typeof elementRef === "function") elementRef(node)
61
+ else if (elementRef) {
62
+ ;(elementRef as React.MutableRefObject<HTMLDivElement | null>).current = node
63
+ }
64
+ },
65
+ [contentTabIndex, elementRef]
66
+ )
67
+
68
+ return (
69
+ <Panel
70
+ className={cn(
71
+ "outline-none focus-visible:shadow-[var(--shadow-component-focus-ring)]",
72
+ className
73
+ )}
74
+ elementRef={setElementRef}
75
+ {...props}
76
+ />
77
+ )
78
+ }
45
79
 
46
80
  function ResizableHandle({
47
81
  className,
@@ -5,9 +5,15 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5
5
 
6
6
  import { cn } from "@/lib/utils"
7
7
 
8
+ type ScrollAreaOrientation = "vertical" | "horizontal" | "both"
8
9
  type ScrollAreaProps = React.ComponentPropsWithoutRef<
9
10
  typeof ScrollAreaPrimitive.Root
10
- >
11
+ > & {
12
+ orientation?: ScrollAreaOrientation
13
+ viewportProps?: React.ComponentPropsWithoutRef<
14
+ typeof ScrollAreaPrimitive.Viewport
15
+ >
16
+ }
11
17
  type ScrollBarProps = React.ComponentPropsWithoutRef<
12
18
  typeof ScrollAreaPrimitive.ScrollAreaScrollbar
13
19
  >
@@ -15,9 +21,24 @@ type ScrollBarProps = React.ComponentPropsWithoutRef<
15
21
  const ScrollArea = React.forwardRef<
16
22
  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
17
23
  ScrollAreaProps
18
- >(function ScrollArea({ children, className, ...props }, ref) {
24
+ >(function ScrollArea(
25
+ {
26
+ children,
27
+ className,
28
+ orientation = "vertical",
29
+ viewportProps,
30
+ ...props
31
+ },
32
+ ref
33
+ ) {
34
+ const hasVerticalScrollbar =
35
+ orientation === "vertical" || orientation === "both"
36
+ const hasHorizontalScrollbar =
37
+ orientation === "horizontal" || orientation === "both"
38
+
19
39
  return (
20
40
  <ScrollAreaPrimitive.Root
41
+ data-orientation={orientation}
21
42
  data-slot="scroll-area"
22
43
  ref={ref}
23
44
  className={cn("relative overflow-hidden", className)}
@@ -25,12 +46,15 @@ const ScrollArea = React.forwardRef<
25
46
  >
26
47
  <ScrollAreaPrimitive.Viewport
27
48
  data-slot="scroll-area-viewport"
28
- className="size-full rounded-[inherit]"
49
+ {...viewportProps}
50
+ tabIndex={viewportProps?.tabIndex ?? 0}
51
+ className={cn("size-full rounded-[inherit] outline-none focus-visible:shadow-[var(--shadow-component-focus-ring)]", viewportProps?.className)}
29
52
  >
30
53
  {children}
31
54
  </ScrollAreaPrimitive.Viewport>
32
- <ScrollBar />
33
- <ScrollAreaPrimitive.Corner />
55
+ {hasVerticalScrollbar ? <ScrollBar orientation="vertical" /> : null}
56
+ {hasHorizontalScrollbar ? <ScrollBar orientation="horizontal" /> : null}
57
+ {orientation === "both" ? <ScrollAreaPrimitive.Corner /> : null}
34
58
  </ScrollAreaPrimitive.Root>
35
59
  )
36
60
  })
@@ -64,4 +88,4 @@ const ScrollBar = React.forwardRef<
64
88
  })
65
89
 
66
90
  export { ScrollArea, ScrollBar }
67
- export type { ScrollAreaProps, ScrollBarProps }
91
+ export type { ScrollAreaOrientation, ScrollAreaProps, ScrollBarProps }