banhaten 0.1.3 → 0.2.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 (258) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/CONTRIBUTING.md +44 -0
  3. package/README.md +65 -13
  4. package/SECURITY.md +15 -0
  5. package/banhaten.config.example.json +0 -2
  6. package/docs/design-system/README.md +9 -0
  7. package/docs/design-system/adoption-quality-remediation.md +87 -0
  8. package/docs/design-system/agent-usage.md +13 -0
  9. package/docs/design-system/appearance-presets.md +6 -1
  10. package/docs/design-system/component-docs-pattern.md +81 -0
  11. package/docs/design-system/component-token-consistency-audit.md +13 -3
  12. package/docs/design-system/components/README.md +3 -2
  13. package/docs/design-system/components/accordion.md +16 -0
  14. package/docs/design-system/components/activity-feed.md +15 -1
  15. package/docs/design-system/components/alert-dialog.md +17 -0
  16. package/docs/design-system/components/alert.md +46 -14
  17. package/docs/design-system/components/aspect-ratio.md +15 -0
  18. package/docs/design-system/components/attribute.md +16 -0
  19. package/docs/design-system/components/autocomplete.md +19 -0
  20. package/docs/design-system/components/avatar.md +18 -0
  21. package/docs/design-system/components/badge.md +19 -0
  22. package/docs/design-system/components/banner.md +15 -1
  23. package/docs/design-system/components/breadcrumbs.md +14 -0
  24. package/docs/design-system/components/button-group.md +34 -0
  25. package/docs/design-system/components/button.md +18 -0
  26. package/docs/design-system/components/card.md +18 -1
  27. package/docs/design-system/components/carousel.md +20 -3
  28. package/docs/design-system/components/catalog-components.md +15 -1
  29. package/docs/design-system/components/chart.md +15 -0
  30. package/docs/design-system/components/checkbox.md +16 -0
  31. package/docs/design-system/components/collapsible.md +15 -0
  32. package/docs/design-system/components/command.md +15 -0
  33. package/docs/design-system/components/context-menu.md +15 -0
  34. package/docs/design-system/components/date-picker.md +21 -1
  35. package/docs/design-system/components/divider.md +16 -0
  36. package/docs/design-system/components/empty-state.md +17 -0
  37. package/docs/design-system/components/field.md +17 -0
  38. package/docs/design-system/components/file-upload.md +14 -0
  39. package/docs/design-system/components/hover-card.md +15 -0
  40. package/docs/design-system/components/input-group.md +17 -0
  41. package/docs/design-system/components/input-otp.md +18 -0
  42. package/docs/design-system/components/input.md +43 -7
  43. package/docs/design-system/components/item.md +72 -0
  44. package/docs/design-system/components/kbd.md +18 -0
  45. package/docs/design-system/components/label.md +16 -0
  46. package/docs/design-system/components/menu.md +17 -0
  47. package/docs/design-system/components/menubar.md +15 -0
  48. package/docs/design-system/components/modal.md +25 -4
  49. package/docs/design-system/components/native-select.md +18 -0
  50. package/docs/design-system/components/navigation-menu.md +15 -0
  51. package/docs/design-system/components/onboarding-step-list-item.md +15 -1
  52. package/docs/design-system/components/page-header.md +15 -1
  53. package/docs/design-system/components/pagination.md +30 -8
  54. package/docs/design-system/components/popover.md +19 -0
  55. package/docs/design-system/components/progress-slider.md +18 -0
  56. package/docs/design-system/components/progress.md +17 -0
  57. package/docs/design-system/components/radio-card.md +16 -0
  58. package/docs/design-system/components/radio-group.md +17 -0
  59. package/docs/design-system/components/resizable.md +15 -0
  60. package/docs/design-system/components/scroll-area.md +15 -0
  61. package/docs/design-system/components/segmented-control.md +71 -0
  62. package/docs/design-system/components/select.md +44 -7
  63. package/docs/design-system/components/sheet.md +17 -0
  64. package/docs/design-system/components/sidebar.md +15 -0
  65. package/docs/design-system/components/skeleton.md +18 -0
  66. package/docs/design-system/components/slideout.md +14 -0
  67. package/docs/design-system/components/slider.md +20 -0
  68. package/docs/design-system/components/social-button.md +21 -3
  69. package/docs/design-system/components/spinner.md +16 -0
  70. package/docs/design-system/components/steps.md +14 -0
  71. package/docs/design-system/components/table.md +15 -1
  72. package/docs/design-system/components/tabs.md +17 -0
  73. package/docs/design-system/components/tag.md +17 -0
  74. package/docs/design-system/components/textarea.md +16 -0
  75. package/docs/design-system/components/timeline.md +14 -0
  76. package/docs/design-system/components/toast.md +17 -0
  77. package/docs/design-system/components/toggle.md +17 -0
  78. package/docs/design-system/components/toolbar.md +21 -1
  79. package/docs/design-system/components/tooltip.md +18 -1
  80. package/docs/design-system/density-and-elevation.md +44 -0
  81. package/docs/design-system/direction-and-localization.md +23 -0
  82. package/docs/design-system/form-recipes.md +73 -0
  83. package/docs/design-system/gap-promotion-policy.md +21 -0
  84. package/docs/design-system/gap-promotion-report.md +166 -0
  85. package/docs/design-system/installation.md +68 -0
  86. package/docs/design-system/launch-comparison-shadcn.md +98 -0
  87. package/docs/design-system/registry-and-cli.md +30 -7
  88. package/docs/design-system/release-policy.md +30 -0
  89. package/docs/design-system/roadmap.md +14 -0
  90. package/docs/design-system/support-matrix.md +23 -0
  91. package/docs/design-system/tabs.md +1 -1
  92. package/docs/design-system/token-governance.md +23 -1
  93. package/package.json +29 -15
  94. package/registry/component-contract.json +40 -0
  95. package/registry/components/alert-dialog.tsx +1 -1
  96. package/registry/components/alert.tsx +86 -65
  97. package/registry/components/autocomplete.tsx +23 -9
  98. package/registry/components/avatar.tsx +7 -7
  99. package/registry/components/badge.tsx +1 -1
  100. package/registry/components/button-group.tsx +321 -4
  101. package/registry/components/button.tsx +38 -17
  102. package/registry/components/card.tsx +68 -59
  103. package/registry/components/carousel.tsx +53 -5
  104. package/registry/components/chart.tsx +36 -3
  105. package/registry/components/checkbox.tsx +3 -3
  106. package/registry/components/collapsible.tsx +1 -1
  107. package/registry/components/command.tsx +1 -1
  108. package/registry/components/context-menu.tsx +1 -1
  109. package/registry/components/date-picker.tsx +245 -104
  110. package/registry/components/expanded/ActivityFeed.tsx +6 -15
  111. package/registry/components/expanded/Banner.tsx +3 -3
  112. package/registry/components/expanded/Breadcrumbs.tsx +5 -5
  113. package/registry/components/expanded/CatalogComponentsShowcase.tsx +13 -13
  114. package/registry/components/expanded/CatalogDivider.tsx +1 -1
  115. package/registry/components/expanded/EmptyState.tsx +1 -1
  116. package/registry/components/expanded/FileUpload.tsx +9 -2
  117. package/registry/components/expanded/OnboardingStepListItem.tsx +2 -4
  118. package/registry/components/expanded/PageHeader.tsx +13 -13
  119. package/registry/components/expanded/Slideout.tsx +15 -415
  120. package/registry/components/expanded/Steps.tsx +4 -4
  121. package/registry/components/expanded/Table.tsx +116 -69
  122. package/registry/components/expanded/Timeline.tsx +3 -3
  123. package/registry/components/expanded/activityFeed.css +1 -1
  124. package/registry/components/expanded/breadcrumbs.css +1 -1
  125. package/registry/components/expanded/divider.css +1 -1
  126. package/registry/components/expanded/emptyState.css +1 -1
  127. package/registry/components/expanded/fileUpload.css +6 -6
  128. package/registry/components/expanded/slideout.css +9 -685
  129. package/registry/components/expanded/steps.css +3 -3
  130. package/registry/components/expanded/table.css +2 -2
  131. package/registry/components/expanded/timeline.css +2 -2
  132. package/registry/components/input-content.ts +1 -0
  133. package/registry/components/input-otp.tsx +13 -3
  134. package/registry/components/input.tsx +68 -43
  135. package/registry/components/item.tsx +189 -0
  136. package/registry/components/kbd.tsx +111 -4
  137. package/registry/components/menu.tsx +31 -245
  138. package/registry/components/menubar.tsx +1 -1
  139. package/registry/components/modal.tsx +142 -102
  140. package/registry/components/native-select.tsx +2 -2
  141. package/registry/components/pagination.tsx +80 -58
  142. package/registry/components/popover.tsx +162 -664
  143. package/registry/components/radio-card.tsx +116 -87
  144. package/registry/components/segmented-control.tsx +87 -0
  145. package/registry/components/select-content.tsx +2 -2
  146. package/registry/components/select.tsx +52 -16
  147. package/registry/components/sheet.tsx +1 -1
  148. package/registry/components/sidebar.tsx +1 -1
  149. package/registry/components/skeleton.css +0 -3
  150. package/registry/components/social-button.tsx +47 -25
  151. package/registry/components/spinner.tsx +18 -55
  152. package/registry/components/tabs.css +1 -1
  153. package/registry/components/tabs.tsx +8 -4
  154. package/registry/components/tag.tsx +2 -2
  155. package/registry/components/textarea.tsx +3 -3
  156. package/registry/components/toast.tsx +16 -11
  157. package/registry/components/toggle.tsx +2 -2
  158. package/registry/components/toolbar.tsx +89 -17
  159. package/registry/components/tooltip.tsx +1 -1
  160. package/registry/examples/alert-demo.tsx +25 -5
  161. package/registry/examples/attribute-demo.tsx +1 -1
  162. package/registry/examples/autocomplete-demo.tsx +3 -3
  163. package/registry/examples/avatar-demo.tsx +7 -7
  164. package/registry/examples/badge-demo.tsx +1 -1
  165. package/registry/examples/button-demo.tsx +6 -6
  166. package/registry/examples/button-group-demo.tsx +23 -2
  167. package/registry/examples/card-demo.tsx +46 -8
  168. package/registry/examples/carousel-demo.tsx +20 -2
  169. package/registry/examples/chart-demo.tsx +1 -1
  170. package/registry/examples/checkbox-demo.tsx +5 -5
  171. package/registry/examples/collection-filter-bar-demo.tsx +135 -0
  172. package/registry/examples/command-demo.tsx +3 -3
  173. package/registry/examples/{expanded/command-bar-demo.tsx → command-palette-demo.tsx} +5 -15
  174. package/registry/examples/context-menu-demo.tsx +1 -1
  175. package/registry/examples/control-density-contract-demo.tsx +61 -0
  176. package/registry/examples/date-picker-demo.tsx +12 -12
  177. package/registry/examples/divider-demo.tsx +3 -3
  178. package/registry/examples/expanded/banner-demo.tsx +1 -1
  179. package/registry/examples/expanded/breadcrumbs-demo.tsx +1 -1
  180. package/registry/examples/expanded/empty-state-demo.tsx +1 -1
  181. package/registry/examples/expanded/file-upload-demo.tsx +1 -1
  182. package/registry/examples/expanded/onboarding-step-demo.tsx +1 -1
  183. package/registry/examples/expanded/slideout-demo.tsx +2 -2
  184. package/registry/examples/expanded/steps-demo.tsx +1 -1
  185. package/registry/examples/expanded/tabs-demo.tsx +1 -1
  186. package/registry/examples/expanded/timeline-demo.tsx +1 -1
  187. package/registry/examples/hover-card-demo.tsx +2 -2
  188. package/registry/examples/input-demo.tsx +6 -6
  189. package/registry/examples/input-otp-demo.tsx +4 -4
  190. package/registry/examples/item-demo.tsx +46 -0
  191. package/registry/examples/kbd-demo.tsx +1 -1
  192. package/registry/examples/label-demo.tsx +1 -1
  193. package/registry/examples/menu-demo.tsx +1 -1
  194. package/registry/examples/modal-demo.tsx +67 -9
  195. package/registry/examples/navigation-menu-demo.tsx +1 -1
  196. package/registry/examples/pagination-demo.tsx +6 -3
  197. package/registry/examples/popover-demo.tsx +19 -19
  198. package/registry/examples/progress-demo.tsx +1 -1
  199. package/registry/examples/progress-slider-demo.tsx +1 -1
  200. package/registry/examples/radio-card-demo.tsx +4 -4
  201. package/registry/examples/radio-group-demo.tsx +2 -2
  202. package/registry/examples/resizable-demo.tsx +1 -1
  203. package/registry/examples/results-toolbar-demo.tsx +44 -0
  204. package/registry/examples/scroll-area-demo.tsx +3 -3
  205. package/registry/examples/segmented-control-demo.tsx +39 -0
  206. package/registry/examples/select-demo.tsx +1 -1
  207. package/registry/examples/sidebar-demo.tsx +1 -1
  208. package/registry/examples/slider-demo.tsx +3 -3
  209. package/registry/examples/social-button-demo.tsx +18 -8
  210. package/registry/examples/spinner-demo.tsx +13 -13
  211. package/registry/examples/tabs-demo.tsx +1 -1
  212. package/registry/examples/tag-demo.tsx +1 -1
  213. package/registry/examples/textarea-demo.tsx +4 -4
  214. package/registry/examples/toast-demo.tsx +1 -1
  215. package/registry/examples/toggle-demo.tsx +6 -6
  216. package/registry/examples/tooltip-demo.tsx +4 -4
  217. package/registry/guidance/USAGE.md +27 -0
  218. package/registry/hooks/use-direction.ts +193 -13
  219. package/registry/index.json +5311 -377
  220. package/registry/styles/globals.css +2440 -4061
  221. package/registry.json +119 -149
  222. package/schema/config.schema.json +11 -8
  223. package/schema/registry.schema.json +150 -45
  224. package/schema/tokens.schema.json +134 -34
  225. package/src/cli/command-line.js +194 -0
  226. package/src/cli/config-editors.js +9 -0
  227. package/src/cli/doctor.js +399 -0
  228. package/src/cli/file-transaction.js +295 -0
  229. package/src/cli/global-css.js +153 -0
  230. package/src/cli/index.js +567 -1613
  231. package/src/cli/install-plan.js +592 -0
  232. package/src/cli/install-receipt.js +171 -0
  233. package/src/cli/jsonc.js +203 -0
  234. package/src/cli/package-install-state.js +254 -0
  235. package/src/cli/package-manager.js +238 -0
  236. package/src/cli/package-plan.js +185 -0
  237. package/src/cli/package-version.js +97 -0
  238. package/src/cli/registry-query.js +207 -0
  239. package/src/cli/reporting.js +55 -0
  240. package/src/cli/schema-validation.js +19 -0
  241. package/src/cli/source-scanner.js +686 -0
  242. package/src/cli/vite-config.js +478 -0
  243. package/tokens/banhaten.tokens.json +4312 -309
  244. package/docs/design-system/components/command-bar.md +0 -57
  245. package/registry/components/expanded/BannerBoard.tsx +0 -225
  246. package/registry/components/expanded/CatalogTag.tsx +0 -85
  247. package/registry/components/expanded/CommandBar.tsx +0 -386
  248. package/registry/components/expanded/IconExplorer.tsx +0 -612
  249. package/registry/components/expanded/TimelineShowcase.tsx +0 -158
  250. package/registry/components/expanded/commandBar.css +0 -470
  251. package/registry/components/expanded/iconExplorer.css +0 -764
  252. package/registry/components/expanded/iconPacks.ts +0 -866
  253. package/registry/components/expanded/tag.css +0 -148
  254. package/registry/examples/expanded/icons-demo.tsx +0 -5
  255. package/tokens/Color mode.zip +0 -0
  256. package/tokens/Numbers.zip +0 -0
  257. package/tokens/Radius.zip +0 -0
  258. package/tokens/Theme.zip +0 -0
@@ -2,6 +2,7 @@ import * as React from "react"
2
2
  import { cva, type VariantProps } from "class-variance-authority"
3
3
  import { Building2Icon, CreditCardIcon, UserIcon } from "lucide-react"
4
4
 
5
+ import { useResolvedDirection, type Direction } from "@/hooks/use-direction"
5
6
  import { cn } from "@/lib/utils"
6
7
 
7
8
  type RadioCardType =
@@ -14,7 +15,7 @@ type RadioCardType =
14
15
  | "radio-label"
15
16
 
16
17
  type RadioCardState = "default" | "active" | "disabled"
17
- type RadioCardDirection = "ltr" | "rtl"
18
+ type RadioCardDirection = Direction
18
19
 
19
20
  const radioCardVariants = cva(
20
21
  [
@@ -69,91 +70,119 @@ type RadioCardProps = Omit<
69
70
  inputProps?: Omit<React.ComponentProps<"input">, "type">
70
71
  }
71
72
 
72
- function RadioCard({
73
- className,
74
- type = "with-icon",
75
- state = "default",
76
- direction = "ltr",
77
- checked,
78
- disabled,
79
- label,
80
- description,
81
- supportLabel,
82
- supportDescription,
83
- showDescription = true,
84
- showSupportItems = true,
85
- showSupportDescription = true,
86
- icon,
87
- media,
88
- inputProps,
89
- ...props
90
- }: RadioCardProps) {
91
- const hasControlledChecked =
92
- checked !== undefined || inputProps?.checked !== undefined
93
- const controlledChecked = checked ?? inputProps?.checked
94
- const inputChecked =
95
- controlledChecked ?? Boolean(inputProps?.defaultChecked)
96
- const inputReadOnly =
97
- inputProps?.readOnly ??
98
- (hasControlledChecked && inputProps?.onChange === undefined
99
- ? true
100
- : undefined)
101
- const isDisabled = disabled ?? inputProps?.disabled ?? state === "disabled"
102
- const visualState: RadioCardState = isDisabled
103
- ? "disabled"
104
- : inputChecked || state === "active"
105
- ? "active"
106
- : state ?? "default"
107
- const isRtl = direction === "rtl"
108
- const textLabel = label
109
- const textDescription = description
73
+ const RadioCard = React.forwardRef<HTMLLabelElement, RadioCardProps>(
74
+ function RadioCard(
75
+ {
76
+ className,
77
+ type = "with-icon",
78
+ state = "default",
79
+ direction,
80
+ dir,
81
+ checked,
82
+ disabled,
83
+ label,
84
+ description,
85
+ supportLabel,
86
+ supportDescription,
87
+ showDescription = true,
88
+ showSupportItems = true,
89
+ showSupportDescription = true,
90
+ icon,
91
+ media,
92
+ inputProps,
93
+ ...props
94
+ },
95
+ forwardedRef
96
+ ) {
97
+ const directionBoundaryRef = React.useRef<HTMLLabelElement | null>(null)
98
+ const explicitDirection =
99
+ direction ?? (dir === "ltr" || dir === "rtl" ? dir : undefined)
100
+ const resolvedDirection = useResolvedDirection(
101
+ explicitDirection,
102
+ directionBoundaryRef
103
+ )
104
+ const setDirectionBoundaryRef = React.useCallback(
105
+ (node: HTMLLabelElement | null) => {
106
+ directionBoundaryRef.current = node
107
+
108
+ if (typeof forwardedRef === "function") {
109
+ forwardedRef(node)
110
+ } else if (forwardedRef) {
111
+ forwardedRef.current = node
112
+ }
113
+ },
114
+ [forwardedRef]
115
+ )
116
+ const hasControlledChecked =
117
+ checked !== undefined || inputProps?.checked !== undefined
118
+ const controlledChecked = checked ?? inputProps?.checked
119
+ const inputChecked =
120
+ controlledChecked ?? Boolean(inputProps?.defaultChecked)
121
+ const inputReadOnly =
122
+ inputProps?.readOnly ??
123
+ (hasControlledChecked && inputProps?.onChange === undefined
124
+ ? true
125
+ : undefined)
126
+ const isDisabled = disabled ?? inputProps?.disabled ?? state === "disabled"
127
+ const visualState: RadioCardState = isDisabled
128
+ ? "disabled"
129
+ : inputChecked || state === "active"
130
+ ? "active"
131
+ : state ?? "default"
132
+ const isRtl = resolvedDirection === "rtl"
133
+ const textLabel = label
134
+ const textDescription = description
110
135
 
111
- return (
112
- <label
113
- data-slot="radio-card"
114
- data-state={visualState}
115
- data-type={type}
116
- data-direction={direction}
117
- {...props}
118
- dir="ltr"
119
- className={cn(radioCardVariants({ state: visualState }), className)}
120
- >
121
- <input
122
- aria-label={typeof textLabel === "string" ? textLabel : undefined}
123
- {...inputProps}
124
- checked={hasControlledChecked ? controlledChecked : undefined}
125
- className={cn("sr-only", inputProps?.className)}
126
- defaultChecked={hasControlledChecked ? undefined : inputProps?.defaultChecked}
127
- disabled={isDisabled}
128
- readOnly={inputReadOnly}
129
- type="radio"
130
- />
131
- <span
132
- data-slot="radio-card-layout"
133
- className={cn(
134
- "flex min-w-0 items-start [direction:ltr]",
135
- type === "radio-label" && "items-center",
136
- getCardGapClass(type, isRtl)
137
- )}
136
+ return (
137
+ <label
138
+ data-slot="radio-card"
139
+ data-state={visualState}
140
+ data-type={type}
141
+ {...props}
142
+ ref={setDirectionBoundaryRef}
143
+ data-direction={resolvedDirection}
144
+ dir={resolvedDirection}
145
+ className={cn(radioCardVariants({ state: visualState }), className)}
138
146
  >
139
- {renderRadioCardContent({
140
- type,
141
- direction,
142
- state: visualState,
143
- label: textLabel,
144
- description: textDescription,
145
- supportLabel,
146
- supportDescription,
147
- showDescription,
148
- showSupportItems,
149
- showSupportDescription,
150
- icon,
151
- media,
152
- })}
153
- </span>
154
- </label>
155
- )
156
- }
147
+ <input
148
+ aria-label={typeof textLabel === "string" ? textLabel : undefined}
149
+ {...inputProps}
150
+ checked={hasControlledChecked ? controlledChecked : undefined}
151
+ className={cn("sr-only", inputProps?.className)}
152
+ defaultChecked={
153
+ hasControlledChecked ? undefined : inputProps?.defaultChecked
154
+ }
155
+ disabled={isDisabled}
156
+ readOnly={inputReadOnly}
157
+ type="radio"
158
+ />
159
+ <span
160
+ data-slot="radio-card-layout"
161
+ className={cn(
162
+ "flex min-w-0 items-start [direction:ltr]",
163
+ type === "radio-label" && "items-center",
164
+ getCardGapClass(type, isRtl)
165
+ )}
166
+ >
167
+ {renderRadioCardContent({
168
+ type,
169
+ direction: resolvedDirection,
170
+ state: visualState,
171
+ label: textLabel,
172
+ description: textDescription,
173
+ supportLabel,
174
+ supportDescription,
175
+ showDescription,
176
+ showSupportItems,
177
+ showSupportDescription,
178
+ icon,
179
+ media,
180
+ })}
181
+ </span>
182
+ </label>
183
+ )
184
+ }
185
+ )
157
186
 
158
187
  function renderRadioCardContent({
159
188
  type,
@@ -564,7 +593,7 @@ function RadioCardUserIcon() {
564
593
  <UserIcon
565
594
  aria-hidden="true"
566
595
  className="size-[var(--bh-space-4xl-20)]"
567
- strokeWidth={2}
596
+ strokeWidth="var(--bh-icon-stroke-200)"
568
597
  />
569
598
  )
570
599
  }
@@ -578,7 +607,7 @@ function RadioCardPaymentIcon() {
578
607
  <CreditCardIcon
579
608
  aria-hidden="true"
580
609
  className="h-[var(--bh-radio-card-payment-logo-height)] w-[var(--bh-radio-card-payment-logo-width)]"
581
- strokeWidth={2}
610
+ strokeWidth="var(--bh-icon-stroke-200)"
582
611
  />
583
612
  </span>
584
613
  )
@@ -610,7 +639,7 @@ function RadioCardCompanyIcon({ disabled }: { disabled: boolean }) {
610
639
  <Building2Icon
611
640
  aria-hidden="true"
612
641
  className="size-[var(--bh-space-5xl-24)] text-[var(--bh-radio-card-company-mark-color)]"
613
- strokeWidth={1.8}
642
+ strokeWidth="var(--bh-icon-stroke-180)"
614
643
  />
615
644
  </span>
616
645
  )
@@ -0,0 +1,87 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import {
6
+ ButtonGroupLabel,
7
+ ToggleGroup,
8
+ ToggleGroupItem,
9
+ type ButtonGroupLabelProps,
10
+ type ControlDensity,
11
+ type ToggleGroupItemProps,
12
+ type ToggleGroupProps,
13
+ } from "@/components/ui/button-group"
14
+
15
+ type SegmentedControlProps = Omit<
16
+ ToggleGroupProps,
17
+ "itemWidth" | "mode"
18
+ > & {
19
+ "aria-label": string
20
+ density?: ControlDensity
21
+ }
22
+
23
+ const SegmentedControl = React.forwardRef<
24
+ HTMLDivElement,
25
+ SegmentedControlProps
26
+ >(function SegmentedControl(
27
+ { density = "default", type = "single", ...props },
28
+ ref
29
+ ) {
30
+ return (
31
+ <ToggleGroup
32
+ data-slot="segmented-control"
33
+ density={density}
34
+ itemWidth="content"
35
+ mode="default"
36
+ ref={ref}
37
+ type={type}
38
+ {...props}
39
+ />
40
+ )
41
+ })
42
+
43
+ type SegmentedControlItemProps = ToggleGroupItemProps
44
+
45
+ const SegmentedControlItem = React.forwardRef<
46
+ HTMLButtonElement,
47
+ SegmentedControlItemProps
48
+ >(function SegmentedControlItem(props, ref) {
49
+ return (
50
+ <ToggleGroupItem
51
+ data-slot="segmented-control-item"
52
+ itemWidth="content"
53
+ ref={ref}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+
59
+ type SegmentedControlLabelProps = Omit<
60
+ ButtonGroupLabelProps,
61
+ "itemWidth"
62
+ >
63
+
64
+ const SegmentedControlLabel = React.forwardRef<
65
+ HTMLSpanElement,
66
+ SegmentedControlLabelProps
67
+ >(function SegmentedControlLabel(props, ref) {
68
+ return (
69
+ <ButtonGroupLabel
70
+ data-slot="segmented-control-label"
71
+ itemWidth="content"
72
+ ref={ref}
73
+ {...props}
74
+ />
75
+ )
76
+ })
77
+
78
+ export {
79
+ SegmentedControl,
80
+ SegmentedControlItem,
81
+ SegmentedControlLabel,
82
+ }
83
+ export type {
84
+ SegmentedControlItemProps,
85
+ SegmentedControlLabelProps,
86
+ SegmentedControlProps,
87
+ }
@@ -104,7 +104,7 @@ function SelectItemCompanyLogo({
104
104
  aria-hidden="true"
105
105
  className="size-[var(--bh-select-company-icon-size)]"
106
106
  focusable="false"
107
- strokeWidth={2.2}
107
+ strokeWidth="var(--bh-icon-stroke-220)"
108
108
  />
109
109
  </span>
110
110
  )
@@ -177,7 +177,7 @@ function SelectIcon({
177
177
  "aria-hidden": true,
178
178
  className: iconClassName,
179
179
  focusable: false,
180
- strokeWidth: 2,
180
+ strokeWidth: "var(--bh-icon-stroke-200)",
181
181
  } as const
182
182
 
183
183
  if (name === "user") {
@@ -16,7 +16,8 @@ import {
16
16
  type SelectStatusTone,
17
17
  } from "./select-content"
18
18
 
19
- type SelectSize = "lg" | "md"
19
+ type ControlDensity = "compact" | "default" | "comfortable"
20
+ type SelectSize = "lg" | "md" | "comfortable"
20
21
  type SelectVisualState = "default" | "filled" | "error" | "disabled"
21
22
  type SelectVariant = "default" | "soft"
22
23
  type SelectSelectionType = "default" | "checkbox"
@@ -39,6 +40,7 @@ const selectTrigger = cva(
39
40
  {
40
41
  variants: {
41
42
  size: {
43
+ comfortable: "py-[var(--bh-select-trigger-padding-y-comfortable)]",
42
44
  lg: "py-[var(--bh-select-trigger-padding-y-lg)]",
43
45
  md: "py-[var(--bh-select-trigger-padding-y-md)]",
44
46
  },
@@ -120,6 +122,7 @@ const SelectRadixItemContext = React.createContext(false)
120
122
  type SelectProps = Omit<React.ComponentProps<"div">, "children" | "dir"> & {
121
123
  children?: React.ReactNode
122
124
  defaultSelectValue?: string
125
+ density?: ControlDensity
123
126
  dir?: "ltr" | "rtl" | "auto"
124
127
  errorMessage?: React.ReactNode
125
128
  expandIcon?: React.ReactNode | false
@@ -133,7 +136,7 @@ type SelectProps = Omit<React.ComponentProps<"div">, "children" | "dir"> & {
133
136
  isOptional?: boolean
134
137
  isRequired?: boolean
135
138
  label?: React.ReactNode
136
- leadingIcon?: React.ReactNode
139
+ leadingIcon?: React.ReactNode | false
137
140
  onOpenChange?: (open: boolean) => void
138
141
  onTriggerClick?: React.MouseEventHandler<HTMLButtonElement>
139
142
  onValueChange?: (value: string) => void
@@ -158,7 +161,7 @@ type SelectProps = Omit<React.ComponentProps<"div">, "children" | "dir"> & {
158
161
  | "value"
159
162
  | "variant"
160
163
  >
161
- trailingIcon?: React.ReactNode
164
+ trailingIcon?: React.ReactNode | false
162
165
  value?: React.ReactNode
163
166
  variant?: SelectVariant
164
167
  }
@@ -167,6 +170,7 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
167
170
  children,
168
171
  className,
169
172
  defaultSelectValue,
173
+ density,
170
174
  dir,
171
175
  errorMessage,
172
176
  expandIcon,
@@ -174,8 +178,8 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
174
178
  hasHelperText,
175
179
  hasInformationIcon = false,
176
180
  hasLabel,
177
- hasLeadingIcon = true,
178
- hasTrailingIcon = true,
181
+ hasLeadingIcon,
182
+ hasTrailingIcon,
179
183
  helperText,
180
184
  isOptional = false,
181
185
  isRequired = false,
@@ -188,7 +192,7 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
188
192
  optionalText,
189
193
  placeholder,
190
194
  selectValue,
191
- size = "lg",
195
+ size,
192
196
  state = "default",
193
197
  triggerClassName,
194
198
  triggerId,
@@ -199,6 +203,7 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
199
203
  ...props
200
204
  }, ref) {
201
205
  const generatedId = React.useId()
206
+ const resolvedSize = resolveSelectSize(density, size)
202
207
  const resolvedDir = dir
203
208
  const triggerVisualState = triggerProps?.disabled ? "disabled" : state
204
209
  const isError = triggerVisualState === "error"
@@ -210,6 +215,12 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
210
215
  const shouldRenderLabel = hasLabel ?? hasRenderableContent(selectedLabel)
211
216
  const shouldRenderHelperText =
212
217
  hasHelperText ?? hasRenderableContent(selectedHelper)
218
+ const shouldShowLeadingIcon =
219
+ leadingIcon !== false &&
220
+ (hasLeadingIcon ?? hasRenderableContent(leadingIcon))
221
+ const shouldShowTrailingIcon =
222
+ trailingIcon !== false &&
223
+ (hasTrailingIcon ?? hasRenderableContent(trailingIcon))
213
224
  const shouldRenderMenu = React.Children.count(children) > 0
214
225
  const selectId = triggerId || generatedId
215
226
  const helperId = `${selectId}-helper`
@@ -226,6 +237,8 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
226
237
  {...rootOpenProps}
227
238
  >
228
239
  <SelectRoot
240
+ data-density={density}
241
+ data-size={resolvedSize}
229
242
  data-state={triggerVisualState}
230
243
  data-variant={variant}
231
244
  dir={resolvedDir}
@@ -254,12 +267,20 @@ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
254
267
  aria-invalid={isError || undefined}
255
268
  expandIcon={expandIcon}
256
269
  id={selectId}
257
- leadingIcon={hasLeadingIcon ? leadingIcon : false}
270
+ leadingIcon={
271
+ shouldShowLeadingIcon
272
+ ? (leadingIcon ?? <SelectIcon name="user" />)
273
+ : false
274
+ }
258
275
  onClick={onTriggerClick}
259
276
  placeholder={selectedPlaceholder}
260
- size={size}
277
+ size={resolvedSize}
261
278
  state={triggerVisualState}
262
- trailingIcon={hasTrailingIcon ? trailingIcon : false}
279
+ trailingIcon={
280
+ shouldShowTrailingIcon
281
+ ? (trailingIcon ?? <SelectIcon name="info" />)
282
+ : false
283
+ }
263
284
  value={selectedValue}
264
285
  variant={variant}
265
286
  className={triggerClassName}
@@ -464,6 +485,7 @@ type SelectTriggerProps = Omit<
464
485
  "children" | "value"
465
486
  > &
466
487
  VariantProps<typeof selectTrigger> & {
488
+ density?: ControlDensity
467
489
  expandIcon?: React.ReactNode | false
468
490
  leadingIcon?: React.ReactNode | false
469
491
  placeholder?: React.ReactNode
@@ -475,11 +497,12 @@ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
475
497
  (
476
498
  {
477
499
  className,
500
+ density,
478
501
  disabled,
479
502
  expandIcon,
480
503
  leadingIcon,
481
504
  placeholder,
482
- size = "lg",
505
+ size,
483
506
  state,
484
507
  trailingIcon,
485
508
  type = "button",
@@ -489,13 +512,15 @@ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
489
512
  },
490
513
  ref
491
514
  ) => {
515
+ const resolvedSize = resolveSelectSize(density, size)
492
516
  const hasValue = value !== undefined && value !== null && value !== ""
493
517
  const visualState = disabled ? "disabled" : state ?? (hasValue ? "filled" : "default")
494
518
  const isDisabled = visualState === "disabled"
495
519
 
496
520
  return (
497
521
  <button
498
- data-size={size}
522
+ data-density={density}
523
+ data-size={resolvedSize}
499
524
  data-slot="select-trigger"
500
525
  data-state={visualState}
501
526
  data-variant={variant}
@@ -503,7 +528,7 @@ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
503
528
  ref={ref}
504
529
  type={type}
505
530
  className={cn(
506
- selectTrigger({ size, state: visualState, variant }),
531
+ selectTrigger({ size: resolvedSize, state: visualState, variant }),
507
532
  className
508
533
  )}
509
534
  {...props}
@@ -512,12 +537,12 @@ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
512
537
  data-slot="select-trigger-main"
513
538
  className="flex min-w-0 flex-1 items-center gap-[var(--bh-select-content-gap)]"
514
539
  >
515
- {leadingIcon !== false ? (
540
+ {hasRenderableContent(leadingIcon) ? (
516
541
  <span
517
542
  data-slot="select-leading-icon"
518
543
  className="flex size-[var(--bh-select-icon-slot-size)] shrink-0 items-center justify-center text-[var(--bh-select-trigger-icon-color)] group-data-[state=disabled]/select-trigger:text-[var(--bh-content-disabled)] [&_svg:not([class*='size-'])]:size-[var(--bh-select-icon-size)]"
519
544
  >
520
- {leadingIcon ?? <SelectIcon name="user" />}
545
+ {leadingIcon}
521
546
  </span>
522
547
  ) : null}
523
548
 
@@ -541,12 +566,12 @@ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
541
566
  </span>
542
567
  </span>
543
568
 
544
- {trailingIcon !== false ? (
569
+ {hasRenderableContent(trailingIcon) ? (
545
570
  <span
546
571
  data-slot="select-trailing-icon"
547
572
  className="flex size-[var(--bh-select-icon-slot-size)] shrink-0 items-center justify-center text-[var(--bh-select-trigger-icon-color)] group-data-[state=disabled]/select-trigger:text-[var(--bh-content-disabled)] [&_svg:not([class*='size-'])]:size-[var(--bh-select-icon-size)]"
548
573
  >
549
- {trailingIcon ?? <SelectIcon name="info" />}
574
+ {trailingIcon}
550
575
  </span>
551
576
  ) : null}
552
577
 
@@ -799,6 +824,16 @@ function getFocusableSelectItems(root: HTMLElement) {
799
824
  ).filter((item) => !item.hasAttribute("disabled"))
800
825
  }
801
826
 
827
+ function resolveSelectSize(
828
+ density: ControlDensity | undefined,
829
+ size: SelectSize | null | undefined
830
+ ): SelectSize {
831
+ if (density === "compact") return "md"
832
+ if (density === "default") return "lg"
833
+ if (density === "comfortable") return "comfortable"
834
+ return size ?? "lg"
835
+ }
836
+
802
837
  type SelectCheckboxIndicatorProps = {
803
838
  checked?: boolean
804
839
  className?: string
@@ -860,6 +895,7 @@ export {
860
895
  selectTrigger,
861
896
  }
862
897
  export type {
898
+ ControlDensity,
863
899
  SelectItemType,
864
900
  SelectMenuItemProps,
865
901
  SelectProps,
@@ -264,7 +264,7 @@ const SheetCloseButton = React.forwardRef<
264
264
  <XIcon
265
265
  aria-hidden="true"
266
266
  className="size-[var(--bh-space-5xl-24)]"
267
- strokeWidth={2}
267
+ strokeWidth="var(--bh-icon-stroke-200)"
268
268
  />
269
269
  )}
270
270
  </button>
@@ -265,7 +265,7 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
265
265
  <PanelLeftIcon
266
266
  aria-hidden="true"
267
267
  className="size-[var(--bh-space-5xl-24)] rtl:-scale-x-100"
268
- strokeWidth={2}
268
+ strokeWidth="var(--bh-icon-stroke-200)"
269
269
  />
270
270
  )}
271
271
  </button>
@@ -1,10 +1,7 @@
1
1
  [data-slot="skeleton"] {
2
2
  --bh-skeleton-bg: var(--bh-bg-muted);
3
3
  --bh-skeleton-highlight: var(--bh-bg-raised-subtle);
4
- --bh-skeleton-duration: 1.4s;
5
4
  --bh-skeleton-radius: var(--bh-radius-md-6);
6
- --bh-skeleton-shimmer-width: 62%;
7
- --bh-skeleton-width: 100%;
8
5
  --bh-skeleton-height: var(--bh-space-4xl-20);
9
6
  }
10
7