banhaten 0.1.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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -0
  3. package/banhaten.config.example.json +13 -0
  4. package/package.json +59 -0
  5. package/registry/assets/activity-feed-avatar.png +0 -0
  6. package/registry/assets/avatars/avatar-01.jpg +0 -0
  7. package/registry/assets/avatars/avatar-02.jpg +0 -0
  8. package/registry/assets/avatars/avatar-03.jpg +0 -0
  9. package/registry/assets/avatars/avatar-04.jpg +0 -0
  10. package/registry/assets/avatars/avatar-05.jpg +0 -0
  11. package/registry/assets/avatars/avatar-06.jpg +0 -0
  12. package/registry/assets/avatars/avatar-07.jpg +0 -0
  13. package/registry/assets/avatars/avatar-08.jpg +0 -0
  14. package/registry/assets/avatars/avatar-09.jpg +0 -0
  15. package/registry/assets/avatars/avatar-10.jpg +0 -0
  16. package/registry/assets/avatars/avatar-11.jpg +0 -0
  17. package/registry/assets/avatars/avatar-12.jpg +0 -0
  18. package/registry/assets/avatars/avatar-13.jpg +0 -0
  19. package/registry/assets/avatars/avatar-14.jpg +0 -0
  20. package/registry/assets/avatars/avatar-15.jpg +0 -0
  21. package/registry/assets/avatars/avatar-16.jpg +0 -0
  22. package/registry/assets/avatars/avatar-17.jpg +0 -0
  23. package/registry/assets/avatars/avatar-18.jpg +0 -0
  24. package/registry/assets/avatars/avatar-19.jpg +0 -0
  25. package/registry/assets/avatars/avatar-20.jpg +0 -0
  26. package/registry/assets/avatars/avatar-21.jpg +0 -0
  27. package/registry/assets/avatars/avatar-22.jpg +0 -0
  28. package/registry/assets/avatars/avatar-23.jpg +0 -0
  29. package/registry/assets/avatars/avatar-24.jpg +0 -0
  30. package/registry/assets/avatars/avatar-25.jpg +0 -0
  31. package/registry/assets/avatars/avatar-26.jpg +0 -0
  32. package/registry/assets/avatars/avatar-27.jpg +0 -0
  33. package/registry/assets/avatars/avatar-28.jpg +0 -0
  34. package/registry/assets/avatars/avatar-29.jpg +0 -0
  35. package/registry/assets/avatars/avatar-30.jpg +0 -0
  36. package/registry/assets/avatars/avatar-31.jpg +0 -0
  37. package/registry/assets/avatars/avatar-32.jpg +0 -0
  38. package/registry/assets/avatars/avatar-33.jpg +0 -0
  39. package/registry/assets/avatars/avatar-34.jpg +0 -0
  40. package/registry/assets/avatars/avatar-35.jpg +0 -0
  41. package/registry/assets/image-assets.json +744 -0
  42. package/registry/assets/images/art-01.jpg +0 -0
  43. package/registry/assets/images/art-02.jpg +0 -0
  44. package/registry/assets/images/art-03.jpg +0 -0
  45. package/registry/assets/images/art-04.jpg +0 -0
  46. package/registry/assets/images/art-05.jpg +0 -0
  47. package/registry/assets/images/art-06.jpg +0 -0
  48. package/registry/assets/images/art-07.jpg +0 -0
  49. package/registry/assets/images/art-08.jpg +0 -0
  50. package/registry/assets/images/art-09.jpg +0 -0
  51. package/registry/assets/images/art-10.jpg +0 -0
  52. package/registry/assets/images/art-11.jpg +0 -0
  53. package/registry/assets/images/art-12.jpg +0 -0
  54. package/registry/assets/images/art-13.jpg +0 -0
  55. package/registry/assets/images/art-14.jpg +0 -0
  56. package/registry/assets/images/art-15.jpg +0 -0
  57. package/registry/assets/images/art-16.jpg +0 -0
  58. package/registry/assets/images/art-17.jpg +0 -0
  59. package/registry/assets/images/art-18.jpg +0 -0
  60. package/registry/assets/images/art-19.jpg +0 -0
  61. package/registry/assets/images/art-20.jpg +0 -0
  62. package/registry/assets/images/art-21.jpg +0 -0
  63. package/registry/assets/images/art-22.jpg +0 -0
  64. package/registry/assets/images/art-23.jpg +0 -0
  65. package/registry/assets/images/art-24.jpg +0 -0
  66. package/registry/assets/images/art-25.jpg +0 -0
  67. package/registry/assets/images/art-26.jpg +0 -0
  68. package/registry/assets/images/art-27.jpg +0 -0
  69. package/registry/assets/images/nature-01.jpg +0 -0
  70. package/registry/assets/images/nature-02.jpg +0 -0
  71. package/registry/assets/images/nature-03.jpg +0 -0
  72. package/registry/assets/images/nature-04.jpg +0 -0
  73. package/registry/assets/images/nature-05.jpg +0 -0
  74. package/registry/assets/images/nature-06.jpg +0 -0
  75. package/registry/assets/images/nature-07.jpg +0 -0
  76. package/registry/assets/images/nature-08.jpg +0 -0
  77. package/registry/assets/images/nature-09.jpg +0 -0
  78. package/registry/assets/images/nature-10.jpg +0 -0
  79. package/registry/assets/images/nature-11.jpg +0 -0
  80. package/registry/assets/images/nature-12.jpg +0 -0
  81. package/registry/assets/images/nature-13.jpg +0 -0
  82. package/registry/assets/images/nature-14.jpg +0 -0
  83. package/registry/assets/images/nature-15.jpg +0 -0
  84. package/registry/assets/images/nature-16.jpg +0 -0
  85. package/registry/assets/images/nature-17.jpg +0 -0
  86. package/registry/assets/images/nature-18.jpg +0 -0
  87. package/registry/assets/images/nature-19.jpg +0 -0
  88. package/registry/assets/images/nature-20.jpg +0 -0
  89. package/registry/components/accordion.tsx +119 -0
  90. package/registry/components/alert.tsx +282 -0
  91. package/registry/components/attribute.tsx +452 -0
  92. package/registry/components/avatar.tsx +142 -0
  93. package/registry/components/badge.tsx +567 -0
  94. package/registry/components/button-group.tsx +246 -0
  95. package/registry/components/button.tsx +102 -0
  96. package/registry/components/card.tsx +613 -0
  97. package/registry/components/checkbox.tsx +244 -0
  98. package/registry/components/date-picker.tsx +1143 -0
  99. package/registry/components/divider.tsx +82 -0
  100. package/registry/components/expanded/ActivityFeed.tsx +226 -0
  101. package/registry/components/expanded/Banner.tsx +145 -0
  102. package/registry/components/expanded/BannerBoard.tsx +225 -0
  103. package/registry/components/expanded/Breadcrumbs.tsx +156 -0
  104. package/registry/components/expanded/CatalogComponentsShowcase.tsx +211 -0
  105. package/registry/components/expanded/CatalogDivider.tsx +48 -0
  106. package/registry/components/expanded/CatalogTag.tsx +92 -0
  107. package/registry/components/expanded/CommandBar.tsx +406 -0
  108. package/registry/components/expanded/FileUpload.tsx +231 -0
  109. package/registry/components/expanded/IconExplorer.tsx +612 -0
  110. package/registry/components/expanded/OnboardingStepListItem.tsx +67 -0
  111. package/registry/components/expanded/PageHeader.tsx +184 -0
  112. package/registry/components/expanded/Slideout.tsx +514 -0
  113. package/registry/components/expanded/Steps.tsx +266 -0
  114. package/registry/components/expanded/Table.tsx +1014 -0
  115. package/registry/components/expanded/Tabs.tsx +86 -0
  116. package/registry/components/expanded/Timeline.tsx +235 -0
  117. package/registry/components/expanded/TimelineShowcase.tsx +158 -0
  118. package/registry/components/expanded/activityFeed.css +292 -0
  119. package/registry/components/expanded/banner.css +312 -0
  120. package/registry/components/expanded/breadcrumbs.css +140 -0
  121. package/registry/components/expanded/catalogComponentsShowcase.css +87 -0
  122. package/registry/components/expanded/commandBar.css +473 -0
  123. package/registry/components/expanded/divider.css +75 -0
  124. package/registry/components/expanded/fileUpload.css +228 -0
  125. package/registry/components/expanded/iconExplorer.css +764 -0
  126. package/registry/components/expanded/iconPacks.ts +866 -0
  127. package/registry/components/expanded/onboardingStepListItem.css +126 -0
  128. package/registry/components/expanded/pageHeader.css +287 -0
  129. package/registry/components/expanded/slideout.css +955 -0
  130. package/registry/components/expanded/steps.css +329 -0
  131. package/registry/components/expanded/table.css +607 -0
  132. package/registry/components/expanded/tabs.css +197 -0
  133. package/registry/components/expanded/tag.css +148 -0
  134. package/registry/components/expanded/timeline.css +282 -0
  135. package/registry/components/input-content.ts +106 -0
  136. package/registry/components/input.tsx +866 -0
  137. package/registry/components/menu.tsx +758 -0
  138. package/registry/components/modal.tsx +799 -0
  139. package/registry/components/pagination.tsx +543 -0
  140. package/registry/components/progress-slider.tsx +216 -0
  141. package/registry/components/progress.tsx +367 -0
  142. package/registry/components/radio-card.tsx +654 -0
  143. package/registry/components/radio-group.tsx +570 -0
  144. package/registry/components/select-content.tsx +313 -0
  145. package/registry/components/select.tsx +871 -0
  146. package/registry/components/slider.tsx +380 -0
  147. package/registry/components/social-button.tsx +360 -0
  148. package/registry/components/spinner.tsx +31 -0
  149. package/registry/components/tag.tsx +423 -0
  150. package/registry/components/textarea.tsx +625 -0
  151. package/registry/components/toggle.tsx +272 -0
  152. package/registry/components/toolbar.tsx +467 -0
  153. package/registry/components/tooltip.tsx +427 -0
  154. package/registry/examples/accordion-demo.tsx +34 -0
  155. package/registry/examples/alert-demo.tsx +14 -0
  156. package/registry/examples/attribute-demo.tsx +65 -0
  157. package/registry/examples/avatar-demo.tsx +74 -0
  158. package/registry/examples/badge-demo.tsx +53 -0
  159. package/registry/examples/button-demo.tsx +83 -0
  160. package/registry/examples/button-group-demo.tsx +42 -0
  161. package/registry/examples/card-demo.tsx +48 -0
  162. package/registry/examples/checkbox-demo.tsx +67 -0
  163. package/registry/examples/date-picker-demo.tsx +74 -0
  164. package/registry/examples/divider-demo.tsx +17 -0
  165. package/registry/examples/expanded/activity-feed-demo.tsx +22 -0
  166. package/registry/examples/expanded/banner-demo.tsx +23 -0
  167. package/registry/examples/expanded/catalog-components-demo.tsx +5 -0
  168. package/registry/examples/expanded/command-bar-demo.tsx +10 -0
  169. package/registry/examples/expanded/icons-demo.tsx +5 -0
  170. package/registry/examples/expanded/onboarding-step-demo.tsx +11 -0
  171. package/registry/examples/expanded/page-header-demo.tsx +19 -0
  172. package/registry/examples/expanded/slideout-demo.tsx +15 -0
  173. package/registry/examples/expanded/steps-demo.tsx +18 -0
  174. package/registry/examples/expanded/tabs-demo.tsx +13 -0
  175. package/registry/examples/expanded/timeline-demo.tsx +18 -0
  176. package/registry/examples/input-demo.tsx +87 -0
  177. package/registry/examples/menu-demo.tsx +109 -0
  178. package/registry/examples/modal-demo.tsx +16 -0
  179. package/registry/examples/pagination-demo.tsx +17 -0
  180. package/registry/examples/progress-demo.tsx +37 -0
  181. package/registry/examples/progress-slider-demo.tsx +29 -0
  182. package/registry/examples/radio-card-demo.tsx +51 -0
  183. package/registry/examples/radio-group-demo.tsx +62 -0
  184. package/registry/examples/select-demo.tsx +73 -0
  185. package/registry/examples/slider-demo.tsx +31 -0
  186. package/registry/examples/social-button-demo.tsx +51 -0
  187. package/registry/examples/tag-demo.tsx +29 -0
  188. package/registry/examples/textarea-demo.tsx +79 -0
  189. package/registry/examples/toggle-demo.tsx +59 -0
  190. package/registry/examples/toolbar-demo.tsx +80 -0
  191. package/registry/examples/tooltip-demo.tsx +115 -0
  192. package/registry/hooks/use-direction.ts +27 -0
  193. package/registry/index.json +1213 -0
  194. package/registry/styles/globals.css +4600 -0
  195. package/registry/utils/cn.ts +6 -0
  196. package/src/cli/index.js +826 -0
  197. package/tokens/Color mode.zip +0 -0
  198. package/tokens/Numbers.zip +0 -0
  199. package/tokens/Radius.zip +0 -0
  200. package/tokens/Theme.zip +0 -0
  201. package/tokens/banhaten.tokens.json +5525 -0
@@ -0,0 +1,871 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import {
9
+ getDefaultSelectItemMedia,
10
+ SelectIcon,
11
+ SelectItemAvatar,
12
+ SelectItemCompanyLogo,
13
+ SelectItemFlag,
14
+ SelectItemPaymentIcon,
15
+ SelectItemStatusDot,
16
+ type SelectItemType,
17
+ type SelectStatusTone,
18
+ } from "./select-content"
19
+
20
+ type SelectSize = "lg" | "md"
21
+ type SelectVisualState = "default" | "filled" | "error" | "disabled"
22
+ type SelectVariant = "default" | "soft"
23
+ type SelectSelectionType = "default" | "checkbox"
24
+
25
+ const selectRoot = cva(
26
+ "relative grid w-[var(--bh-select-width)] max-w-full gap-[var(--bh-select-field-gap)]"
27
+ )
28
+
29
+ const selectTrigger = cva(
30
+ [
31
+ "group/select-trigger relative flex w-full items-center gap-[var(--bh-select-trigger-gap)] rounded-[var(--bh-select-trigger-radius)]",
32
+ "px-[var(--bh-select-trigger-padding-x)] text-start outline-none",
33
+ "transition-[background-color,box-shadow]",
34
+ "[--bh-select-trigger-border-current:var(--bh-select-trigger-border)] [--shadow-select-trigger:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-current,var(--bh-select-trigger-border)),var(--shadow-component-default)]",
35
+ "focus-visible:shadow-[var(--shadow-select-focus-ring)]",
36
+ "aria-invalid:[--bh-select-trigger-border-current:var(--bh-select-trigger-border-error)]",
37
+ "disabled:pointer-events-none disabled:bg-[var(--bh-select-trigger-bg-disabled)] disabled:shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-disabled)]",
38
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
39
+ ],
40
+ {
41
+ variants: {
42
+ size: {
43
+ lg: "py-[var(--bh-select-trigger-padding-y-lg)]",
44
+ md: "py-[var(--bh-select-trigger-padding-y-md)]",
45
+ },
46
+ variant: {
47
+ default:
48
+ "bg-[var(--bh-select-trigger-bg)] shadow-[var(--shadow-select-trigger)]",
49
+ soft:
50
+ "bg-[var(--bh-select-trigger-bg-soft)] shadow-none",
51
+ },
52
+ state: {
53
+ default: "",
54
+ filled: "",
55
+ error:
56
+ "[--bh-select-trigger-border-current:var(--bh-select-trigger-border-error)] shadow-[var(--shadow-select-trigger)] focus-visible:shadow-[var(--shadow-select-danger-focus-ring)]",
57
+ disabled:
58
+ "pointer-events-none bg-[var(--bh-select-trigger-bg-disabled)] shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-trigger-border-disabled)]",
59
+ },
60
+ },
61
+ defaultVariants: {
62
+ size: "lg",
63
+ variant: "default",
64
+ state: "default",
65
+ },
66
+ }
67
+ )
68
+
69
+ const selectMenu = cva(
70
+ [
71
+ "w-[var(--radix-popper-anchor-width,var(--bh-select-menu-width))] max-w-[var(--radix-popper-available-width,100vw)] overflow-hidden rounded-[var(--bh-select-menu-radius)]",
72
+ "bg-[var(--bh-select-menu-bg)] py-[var(--bh-select-menu-padding-y)]",
73
+ "shadow-[var(--shadow-select-menu)]",
74
+ ].join(" ")
75
+ )
76
+
77
+ const selectMenuList = cva(
78
+ "flex w-full flex-col gap-[var(--bh-select-menu-list-gap)]"
79
+ )
80
+
81
+ const selectMenuItem = cva(
82
+ [
83
+ "group/select-menu-item relative flex min-h-[var(--bh-select-item-min-height)] w-full items-center bg-transparent",
84
+ "px-[var(--bh-select-item-padding-x)] text-start outline-none",
85
+ "disabled:pointer-events-none data-[disabled=true]:pointer-events-none",
86
+ "focus-visible:[&>[data-slot=select-menu-item-content]]:bg-[var(--bh-select-item-hover-bg)]",
87
+ ],
88
+ {
89
+ variants: {
90
+ state: {
91
+ default: "",
92
+ },
93
+ },
94
+ defaultVariants: {
95
+ state: "default",
96
+ },
97
+ }
98
+ )
99
+
100
+ const selectMenuItemContent = cva(
101
+ [
102
+ "relative flex min-w-0 flex-1 items-center gap-[var(--bh-select-item-gap)] rounded-[var(--bh-select-item-radius)]",
103
+ "p-[var(--bh-select-item-content-padding)] transition-colors",
104
+ "group-hover/select-menu-item:bg-[var(--bh-select-item-hover-bg)] group-data-[disabled=true]/select-menu-item:bg-transparent",
105
+ ],
106
+ {
107
+ variants: {
108
+ selected: {
109
+ true: "pe-[var(--bh-select-item-selected-padding-end)]",
110
+ false: "",
111
+ },
112
+ },
113
+ defaultVariants: {
114
+ selected: false,
115
+ },
116
+ }
117
+ )
118
+
119
+ const SelectRadixItemContext = React.createContext(false)
120
+
121
+ type SelectProps = Omit<React.ComponentProps<"div">, "children" | "dir"> & {
122
+ children?: React.ReactNode
123
+ defaultSelectValue?: string
124
+ dir?: "ltr" | "rtl" | "auto"
125
+ errorMessage?: React.ReactNode
126
+ expandIcon?: React.ReactNode | false
127
+ hasHelperIcon?: boolean
128
+ hasHelperText?: boolean
129
+ hasInformationIcon?: boolean
130
+ hasLabel?: boolean
131
+ hasLeadingIcon?: boolean
132
+ hasTrailingIcon?: boolean
133
+ helperText?: React.ReactNode
134
+ isOptional?: boolean
135
+ isRequired?: boolean
136
+ label?: React.ReactNode
137
+ leadingIcon?: React.ReactNode
138
+ onOpenChange?: (open: boolean) => void
139
+ onTriggerClick?: React.MouseEventHandler<HTMLButtonElement>
140
+ onValueChange?: (value: string) => void
141
+ open?: boolean
142
+ optionalText?: React.ReactNode
143
+ placeholder?: React.ReactNode
144
+ selectValue?: string
145
+ size?: SelectSize
146
+ state?: SelectVisualState
147
+ triggerClassName?: string
148
+ triggerId?: string
149
+ triggerProps?: Omit<
150
+ SelectTriggerProps,
151
+ | "children"
152
+ | "className"
153
+ | "expandIcon"
154
+ | "leadingIcon"
155
+ | "placeholder"
156
+ | "size"
157
+ | "state"
158
+ | "trailingIcon"
159
+ | "value"
160
+ | "variant"
161
+ >
162
+ trailingIcon?: React.ReactNode
163
+ value?: React.ReactNode
164
+ variant?: SelectVariant
165
+ }
166
+
167
+ const Select = React.forwardRef<HTMLDivElement, SelectProps>(function Select({
168
+ children,
169
+ className,
170
+ defaultSelectValue,
171
+ dir,
172
+ errorMessage,
173
+ expandIcon,
174
+ hasHelperIcon = false,
175
+ hasHelperText,
176
+ hasInformationIcon = false,
177
+ hasLabel,
178
+ hasLeadingIcon = true,
179
+ hasTrailingIcon = true,
180
+ helperText,
181
+ isOptional = false,
182
+ isRequired = false,
183
+ label,
184
+ leadingIcon,
185
+ onOpenChange,
186
+ onTriggerClick,
187
+ onValueChange,
188
+ open,
189
+ optionalText,
190
+ placeholder,
191
+ selectValue,
192
+ size = "lg",
193
+ state = "default",
194
+ triggerClassName,
195
+ triggerId,
196
+ triggerProps,
197
+ trailingIcon,
198
+ value,
199
+ variant = "default",
200
+ ...props
201
+ }, ref) {
202
+ const generatedId = React.useId()
203
+ const resolvedDir = dir
204
+ const triggerVisualState = triggerProps?.disabled ? "disabled" : state
205
+ const isError = triggerVisualState === "error"
206
+ const selectedValue = value
207
+ const selectedPlaceholder = placeholder
208
+ const selectedLabel = label
209
+ const selectedOptional = optionalText
210
+ const selectedHelper = isError ? errorMessage : helperText
211
+ const shouldRenderLabel = hasLabel ?? hasRenderableContent(selectedLabel)
212
+ const shouldRenderHelperText =
213
+ hasHelperText ?? hasRenderableContent(selectedHelper)
214
+ const shouldRenderMenu = React.Children.count(children) > 0
215
+ const selectId = triggerId || generatedId
216
+ const helperId = `${selectId}-helper`
217
+ const rootOpenProps =
218
+ open === undefined ? { onOpenChange } : { open, onOpenChange }
219
+
220
+ return (
221
+ <SelectPrimitive.Root
222
+ defaultValue={defaultSelectValue}
223
+ dir={resolvedDir === "auto" ? undefined : resolvedDir}
224
+ disabled={triggerVisualState === "disabled"}
225
+ onValueChange={onValueChange}
226
+ value={selectValue}
227
+ {...rootOpenProps}
228
+ >
229
+ <SelectRoot
230
+ data-state={triggerVisualState}
231
+ data-variant={variant}
232
+ dir={resolvedDir}
233
+ ref={ref}
234
+ className={className}
235
+ {...props}
236
+ >
237
+ {shouldRenderLabel ? (
238
+ <SelectLabel
239
+ hasInformationIcon={hasInformationIcon}
240
+ htmlFor={selectId}
241
+ isOptional={isOptional}
242
+ isRequired={isRequired}
243
+ optionalText={selectedOptional}
244
+ >
245
+ {selectedLabel}
246
+ </SelectLabel>
247
+ ) : null}
248
+
249
+ <div data-slot="select-control" className="relative w-full">
250
+ <SelectPrimitive.Trigger asChild>
251
+ <SelectTrigger
252
+ aria-describedby={shouldRenderHelperText ? helperId : undefined}
253
+ aria-expanded={open || undefined}
254
+ aria-haspopup="listbox"
255
+ aria-invalid={isError || undefined}
256
+ expandIcon={expandIcon}
257
+ id={selectId}
258
+ leadingIcon={hasLeadingIcon ? leadingIcon : false}
259
+ onClick={onTriggerClick}
260
+ placeholder={selectedPlaceholder}
261
+ size={size}
262
+ state={triggerVisualState}
263
+ trailingIcon={hasTrailingIcon ? trailingIcon : false}
264
+ value={selectedValue}
265
+ variant={variant}
266
+ className={triggerClassName}
267
+ {...triggerProps}
268
+ />
269
+ </SelectPrimitive.Trigger>
270
+
271
+ {shouldRenderMenu ? (
272
+ <RadixSelectMenuContent forceMount={open === true || undefined}>
273
+ {children}
274
+ </RadixSelectMenuContent>
275
+ ) : null}
276
+ </div>
277
+
278
+ {shouldRenderHelperText ? (
279
+ <SelectHelperText
280
+ hasIcon={hasHelperIcon}
281
+ id={helperId}
282
+ type={isError ? "error" : "default"}
283
+ >
284
+ {selectedHelper}
285
+ </SelectHelperText>
286
+ ) : null}
287
+ </SelectRoot>
288
+ </SelectPrimitive.Root>
289
+ )
290
+ })
291
+
292
+ type RadixSelectMenuContentProps = React.ComponentProps<typeof SelectPrimitive.Content>
293
+ // Radix sideOffset requires a number; this mirrors --bh-select-menu-offset.
294
+ const SELECT_MENU_SIDE_OFFSET_PX = 4
295
+
296
+ function RadixSelectMenuContent({
297
+ children,
298
+ className,
299
+ position = "popper",
300
+ sideOffset = SELECT_MENU_SIDE_OFFSET_PX,
301
+ ...props
302
+ }: RadixSelectMenuContentProps) {
303
+ return (
304
+ <SelectPrimitive.Content
305
+ asChild
306
+ position={position}
307
+ sideOffset={sideOffset}
308
+ {...props}
309
+ >
310
+ <div
311
+ data-slot="select-menu"
312
+ className={cn(
313
+ selectMenu(),
314
+ "absolute start-0 top-[calc(100%+var(--bh-select-menu-offset))] z-[var(--bh-z-popover)]",
315
+ className
316
+ )}
317
+ >
318
+ <SelectPrimitive.Viewport asChild>
319
+ <div data-slot="select-menu-list" className={selectMenuList()}>
320
+ <SelectRadixItemContext.Provider value>
321
+ {children}
322
+ </SelectRadixItemContext.Provider>
323
+ </div>
324
+ </SelectPrimitive.Viewport>
325
+ </div>
326
+ </SelectPrimitive.Content>
327
+ )
328
+ }
329
+
330
+ type SelectRootProps = React.ComponentProps<"div">
331
+
332
+ const SelectRoot = React.forwardRef<HTMLDivElement, SelectRootProps>(function SelectRoot(
333
+ { className, ...props },
334
+ ref
335
+ ) {
336
+ return (
337
+ <div
338
+ data-slot="select-root"
339
+ ref={ref}
340
+ className={cn(selectRoot(), className)}
341
+ {...props}
342
+ />
343
+ )
344
+ })
345
+
346
+ type SelectLabelProps = React.ComponentProps<"label"> & {
347
+ hasInformationIcon?: boolean
348
+ isOptional?: boolean
349
+ isRequired?: boolean
350
+ optionalText?: React.ReactNode
351
+ }
352
+
353
+ function SelectLabel({
354
+ children,
355
+ className,
356
+ hasInformationIcon = false,
357
+ isOptional = false,
358
+ isRequired = false,
359
+ optionalText,
360
+ ...props
361
+ }: SelectLabelProps) {
362
+ return (
363
+ <label
364
+ data-slot="select-label"
365
+ className={cn(
366
+ "flex w-full items-center gap-[var(--bh-select-label-gap)] text-start",
367
+ className
368
+ )}
369
+ {...props}
370
+ >
371
+ <span
372
+ data-slot="select-label-text"
373
+ dir="auto"
374
+ className="min-w-0 shrink-0 whitespace-nowrap text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)] text-[var(--bh-content-default)] rtl:text-[length:var(--bh-text-body-xs-medium-font-size)] rtl:font-[var(--bh-text-body-xs-medium-font-weight)] rtl:leading-[var(--bh-text-body-xs-medium-line-height)] rtl:tracking-[var(--bh-text-body-xs-medium-letter-spacing)]"
375
+ >
376
+ {children}
377
+ </span>
378
+ {isRequired ? (
379
+ <span
380
+ aria-hidden="true"
381
+ data-slot="select-label-required"
382
+ className="shrink-0 whitespace-nowrap 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-danger-default)]"
383
+ >
384
+ *
385
+ </span>
386
+ ) : null}
387
+ {isOptional && hasRenderableContent(optionalText) ? (
388
+ <span
389
+ data-slot="select-label-optional"
390
+ dir="auto"
391
+ className="min-w-0 shrink-0 whitespace-nowrap 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)]"
392
+ >
393
+ {optionalText}
394
+ </span>
395
+ ) : null}
396
+ {hasInformationIcon ? (
397
+ <SelectIcon
398
+ name="info"
399
+ className="text-[var(--bh-select-trigger-icon-color)]"
400
+ />
401
+ ) : null}
402
+ </label>
403
+ )
404
+ }
405
+
406
+ function hasRenderableContent(content: React.ReactNode) {
407
+ return (
408
+ content !== undefined &&
409
+ content !== null &&
410
+ content !== false &&
411
+ content !== ""
412
+ )
413
+ }
414
+
415
+ type SelectHelperTextProps = React.ComponentProps<"p"> & {
416
+ hasIcon?: boolean
417
+ type?: "default" | "error"
418
+ }
419
+
420
+ function SelectHelperText({
421
+ children,
422
+ className,
423
+ hasIcon = false,
424
+ type = "default",
425
+ ...props
426
+ }: SelectHelperTextProps) {
427
+ const isError = type === "error"
428
+
429
+ return (
430
+ <p
431
+ data-slot="select-helper-text"
432
+ data-type={type}
433
+ className={cn(
434
+ "flex w-full items-center gap-[var(--bh-select-label-gap)] text-start 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)]",
435
+ isError
436
+ ? "text-[var(--bh-content-danger-default)]"
437
+ : "text-[var(--bh-content-subtle)]",
438
+ className
439
+ )}
440
+ {...props}
441
+ >
442
+ {hasIcon ? (
443
+ <SelectIcon
444
+ name={isError ? "error" : "info"}
445
+ className={
446
+ isError
447
+ ? "text-[var(--bh-content-danger-default)]"
448
+ : "text-[var(--bh-select-trigger-icon-color)]"
449
+ }
450
+ />
451
+ ) : null}
452
+ <span data-slot="select-helper-label" dir="auto" className="min-w-0 flex-1">
453
+ {children}
454
+ </span>
455
+ </p>
456
+ )
457
+ }
458
+
459
+ type SelectTriggerProps = Omit<
460
+ React.ComponentProps<"button">,
461
+ "children" | "value"
462
+ > &
463
+ VariantProps<typeof selectTrigger> & {
464
+ expandIcon?: React.ReactNode | false
465
+ leadingIcon?: React.ReactNode | false
466
+ placeholder?: React.ReactNode
467
+ trailingIcon?: React.ReactNode | false
468
+ value?: React.ReactNode
469
+ }
470
+
471
+ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
472
+ (
473
+ {
474
+ className,
475
+ disabled,
476
+ expandIcon,
477
+ leadingIcon,
478
+ placeholder,
479
+ size = "lg",
480
+ state,
481
+ trailingIcon,
482
+ type = "button",
483
+ value,
484
+ variant = "default",
485
+ ...props
486
+ },
487
+ ref
488
+ ) => {
489
+ const hasValue = value !== undefined && value !== null && value !== ""
490
+ const visualState = disabled ? "disabled" : state ?? (hasValue ? "filled" : "default")
491
+ const isDisabled = visualState === "disabled"
492
+
493
+ return (
494
+ <button
495
+ data-size={size}
496
+ data-slot="select-trigger"
497
+ data-state={visualState}
498
+ data-variant={variant}
499
+ disabled={isDisabled}
500
+ ref={ref}
501
+ type={type}
502
+ className={cn(
503
+ selectTrigger({ size, state: visualState, variant }),
504
+ className
505
+ )}
506
+ {...props}
507
+ >
508
+ <span
509
+ data-slot="select-trigger-main"
510
+ className="flex min-w-0 flex-1 items-center gap-[var(--bh-select-content-gap)]"
511
+ >
512
+ {leadingIcon !== false ? (
513
+ <span
514
+ data-slot="select-leading-icon"
515
+ 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)]"
516
+ >
517
+ {leadingIcon ?? <SelectIcon name="user" />}
518
+ </span>
519
+ ) : null}
520
+
521
+ <span
522
+ data-slot="select-value-wrap"
523
+ className="flex min-w-0 flex-1 items-center ps-[var(--bh-select-value-padding-inline)]"
524
+ >
525
+ <span
526
+ data-slot="select-value"
527
+ dir="auto"
528
+ className={cn(
529
+ "min-w-0 flex-1 truncate text-start text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
530
+ hasValue || visualState === "filled"
531
+ ? "text-[var(--bh-content-default)]"
532
+ : "text-[var(--bh-content-subtle)]",
533
+ isDisabled && "text-[var(--bh-content-disabled)]"
534
+ )}
535
+ >
536
+ {hasValue ? value : placeholder}
537
+ </span>
538
+ </span>
539
+ </span>
540
+
541
+ {trailingIcon !== false ? (
542
+ <span
543
+ data-slot="select-trailing-icon"
544
+ 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)]"
545
+ >
546
+ {trailingIcon ?? <SelectIcon name="info" />}
547
+ </span>
548
+ ) : null}
549
+
550
+ {expandIcon !== false ? (
551
+ <span
552
+ data-slot="select-expand-icon"
553
+ 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)]"
554
+ >
555
+ {expandIcon ?? <SelectIcon name="chevron-down" />}
556
+ </span>
557
+ ) : null}
558
+ </button>
559
+ )
560
+ }
561
+ )
562
+ SelectTrigger.displayName = "SelectTrigger"
563
+
564
+ type SelectMenuProps = React.ComponentProps<"div">
565
+
566
+ const SelectMenu = React.forwardRef<HTMLDivElement, SelectMenuProps>(function SelectMenu(
567
+ { children, className, onKeyDown, role, ...props },
568
+ ref
569
+ ) {
570
+ const handleKeyDown = React.useCallback(
571
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
572
+ onKeyDown?.(event)
573
+ if (event.defaultPrevented) return
574
+
575
+ if (event.key === "Escape") {
576
+ event.currentTarget.blur()
577
+ return
578
+ }
579
+
580
+ if (
581
+ event.key !== "ArrowDown" &&
582
+ event.key !== "ArrowUp" &&
583
+ event.key !== "Home" &&
584
+ event.key !== "End"
585
+ ) {
586
+ return
587
+ }
588
+
589
+ const items = getFocusableSelectItems(event.currentTarget)
590
+ if (items.length === 0) return
591
+
592
+ event.preventDefault()
593
+
594
+ const activeItem = event.currentTarget.ownerDocument.activeElement
595
+ const currentIndex =
596
+ activeItem instanceof HTMLElement ? items.indexOf(activeItem) : -1
597
+ let nextIndex = currentIndex
598
+
599
+ if (event.key === "Home") nextIndex = 0
600
+ if (event.key === "End") nextIndex = items.length - 1
601
+ if (event.key === "ArrowDown") nextIndex = (currentIndex + 1) % items.length
602
+ if (event.key === "ArrowUp") {
603
+ nextIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1
604
+ }
605
+
606
+ items[nextIndex]?.focus()
607
+ },
608
+ [onKeyDown]
609
+ )
610
+
611
+ return (
612
+ <div
613
+ data-slot="select-menu"
614
+ onKeyDown={handleKeyDown}
615
+ ref={ref}
616
+ role={role || "listbox"}
617
+ className={cn(selectMenu(), className)}
618
+ {...props}
619
+ >
620
+ <div data-slot="select-menu-list" className={selectMenuList()}>
621
+ {children}
622
+ </div>
623
+ </div>
624
+ )
625
+ })
626
+
627
+ type SelectMenuItemBaseProps = Omit<
628
+ React.ComponentProps<"button">,
629
+ "children" | "type" | "value"
630
+ > & {
631
+ addonText?: React.ReactNode
632
+ children?: React.ReactNode
633
+ label?: React.ReactNode
634
+ type?: "button" | "submit" | "reset"
635
+ value?: string
636
+ }
637
+
638
+ type SelectMenuItemMediaProps =
639
+ | { itemType?: "default"; media?: React.ReactNode | false }
640
+ | {
641
+ itemType: Exclude<SelectItemType, "default">
642
+ media?: React.ReactNode | false
643
+ }
644
+
645
+ type SelectMenuItemSelectionProps =
646
+ | { selected?: boolean; selectionType?: "default" }
647
+ | { selected?: boolean; selectionType: "checkbox" }
648
+
649
+ type SelectMenuItemDynamicProps = {
650
+ itemType?: SelectItemType
651
+ media?: React.ReactNode | false
652
+ selected?: boolean
653
+ selectionType?: SelectSelectionType
654
+ }
655
+
656
+ type SelectMenuItemProps = SelectMenuItemBaseProps &
657
+ (
658
+ | (SelectMenuItemMediaProps & SelectMenuItemSelectionProps)
659
+ | SelectMenuItemDynamicProps
660
+ )
661
+
662
+ const SelectMenuItem = React.forwardRef<HTMLButtonElement, SelectMenuItemProps>(function SelectMenuItem({
663
+ addonText,
664
+ children,
665
+ className,
666
+ disabled,
667
+ itemType = "default",
668
+ label,
669
+ media,
670
+ role,
671
+ selected = false,
672
+ selectionType = "default",
673
+ type = "button",
674
+ value,
675
+ ...props
676
+ }, ref) {
677
+ const inRadixSelect = React.useContext(SelectRadixItemContext)
678
+ const generatedValue = React.useId()
679
+ const isDisabled = disabled
680
+ const selectedWithEndCheck = selected && selectionType === "default"
681
+ const resolvedRole =
682
+ role || (selectionType === "checkbox" ? "menuitemcheckbox" : "option")
683
+ const selectedMedia =
684
+ media === false ? null : media ?? getDefaultSelectItemMedia(itemType)
685
+ const content = label ?? children
686
+ const itemValue =
687
+ value ??
688
+ (typeof content === "string" || typeof content === "number"
689
+ ? String(content)
690
+ : generatedValue)
691
+ const itemTextValue =
692
+ typeof content === "string" || typeof content === "number"
693
+ ? String(content)
694
+ : undefined
695
+
696
+ const item = (
697
+ <button
698
+ aria-checked={
699
+ selectionType === "checkbox" ? Boolean(selected) : undefined
700
+ }
701
+ aria-disabled={isDisabled || undefined}
702
+ aria-selected={
703
+ selectionType === "default" ? Boolean(selected) : undefined
704
+ }
705
+ data-disabled={isDisabled ? "true" : undefined}
706
+ data-item-type={itemType}
707
+ data-selected={selected ? "true" : "false"}
708
+ data-selection-type={selectionType}
709
+ data-slot="select-menu-item"
710
+ disabled={isDisabled}
711
+ ref={ref}
712
+ role={resolvedRole}
713
+ type={type}
714
+ className={cn(selectMenuItem(), className)}
715
+ {...props}
716
+ >
717
+ <span
718
+ data-slot="select-menu-item-content"
719
+ className={selectMenuItemContent({
720
+ selected: selectedWithEndCheck,
721
+ })}
722
+ >
723
+ {selectionType === "checkbox" ? (
724
+ <span
725
+ data-slot="select-menu-item-checkbox-wrap"
726
+ className="flex size-[var(--bh-select-icon-slot-size)] shrink-0 items-center justify-center"
727
+ >
728
+ <SelectCheckboxIndicator checked={selected} disabled={isDisabled} />
729
+ </span>
730
+ ) : null}
731
+
732
+ {selectedMedia ? (
733
+ <span
734
+ data-slot="select-menu-item-media"
735
+ className="flex shrink-0 items-center ps-[var(--bh-select-media-inset)] text-[var(--bh-content-subtle)] group-data-[disabled=true]/select-menu-item:text-[var(--bh-content-disabled)] group-data-[disabled=true]/select-menu-item:opacity-[var(--bh-opacity-60)] [&_svg:not([class*='size-'])]:size-[var(--bh-select-icon-slot-size)]"
736
+ >
737
+ {selectedMedia}
738
+ </span>
739
+ ) : null}
740
+
741
+ <span
742
+ data-slot="select-menu-item-value"
743
+ className="flex min-w-0 items-center px-[var(--bh-select-item-value-padding-x)]"
744
+ >
745
+ <span
746
+ data-slot="select-menu-item-label"
747
+ dir="auto"
748
+ className="min-w-0 truncate text-start text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)] text-[var(--bh-content-default)] group-data-[disabled=true]/select-menu-item:text-[var(--bh-content-disabled)]"
749
+ >
750
+ {content}
751
+ </span>
752
+ </span>
753
+
754
+ {addonText !== undefined && addonText !== null ? (
755
+ <span
756
+ data-slot="select-menu-item-addon"
757
+ dir="auto"
758
+ className="min-w-0 shrink-0 truncate text-start text-[length:var(--bh-text-body-2xs-regular-font-size)] font-[var(--bh-text-body-2xs-regular-font-weight)] leading-[var(--bh-text-body-2xs-regular-line-height)] tracking-[var(--bh-text-body-2xs-regular-letter-spacing)] text-[var(--bh-content-muted)] group-hover/select-menu-item:text-[var(--bh-content-subtle)] group-focus-visible/select-menu-item:text-[var(--bh-content-subtle)] group-data-[disabled=true]/select-menu-item:text-[var(--bh-content-disabled)]"
759
+ >
760
+ {addonText}
761
+ </span>
762
+ ) : null}
763
+ </span>
764
+
765
+ {selectedWithEndCheck ? (
766
+ <span
767
+ aria-hidden="true"
768
+ data-slot="select-menu-item-check"
769
+ className="pointer-events-none absolute end-[var(--bh-select-item-check-offset)] top-1/2 flex size-[var(--bh-select-icon-slot-size)] -translate-y-1/2 items-center justify-center text-[var(--bh-select-check-color)]"
770
+ >
771
+ <SelectIcon name="check-line" />
772
+ </span>
773
+ ) : null}
774
+ </button>
775
+ )
776
+
777
+ if (!inRadixSelect) return item
778
+
779
+ return (
780
+ <SelectPrimitive.Item
781
+ asChild
782
+ disabled={isDisabled}
783
+ textValue={itemTextValue}
784
+ value={itemValue}
785
+ >
786
+ {item}
787
+ </SelectPrimitive.Item>
788
+ )
789
+ })
790
+
791
+ function getFocusableSelectItems(root: HTMLElement) {
792
+ return Array.from(
793
+ root.querySelectorAll<HTMLElement>(
794
+ '[role="option"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"])'
795
+ )
796
+ ).filter((item) => !item.hasAttribute("disabled"))
797
+ }
798
+
799
+ type SelectCheckboxIndicatorProps = {
800
+ checked?: boolean
801
+ className?: string
802
+ disabled?: boolean
803
+ }
804
+
805
+ function SelectCheckboxIndicator({
806
+ checked = false,
807
+ className,
808
+ disabled = false,
809
+ }: SelectCheckboxIndicatorProps) {
810
+ return (
811
+ <span
812
+ aria-hidden="true"
813
+ data-checked={checked ? "true" : "false"}
814
+ data-disabled={disabled ? "true" : undefined}
815
+ data-slot="select-checkbox"
816
+ className={cn(
817
+ "relative size-[var(--bh-select-icon-slot-size)] shrink-0 overflow-hidden",
818
+ className
819
+ )}
820
+ >
821
+ <span
822
+ data-slot="select-checkbox-control"
823
+ className={cn(
824
+ "pointer-events-none absolute start-1/2 top-1/2 size-[var(--bh-select-checkbox-size)] -translate-x-1/2 -translate-y-1/2 rounded-[var(--bh-select-checkbox-radius)] shadow-[var(--shadow-select-checkbox)] [--bh-select-checkbox-border:var(--bh-border-input)] [--shadow-select-checkbox:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-select-checkbox-border,var(--bh-border-input)),var(--shadow-component-default)]",
825
+ checked
826
+ ? "bg-[var(--bh-interactive-checkbox-default)] [--bh-select-checkbox-border:transparent]"
827
+ : "bg-[var(--bh-bg-default)]",
828
+ disabled &&
829
+ "bg-[var(--bh-bg-disabled)] shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)]"
830
+ )}
831
+ />
832
+ {checked ? (
833
+ <span className="absolute start-1/2 top-1/2 flex size-[var(--bh-select-checkbox-icon-size)] -translate-x-1/2 -translate-y-1/2 items-center justify-center text-[var(--bh-content-on-color)]">
834
+ <SelectIcon name="check-fill" />
835
+ </span>
836
+ ) : null}
837
+ </span>
838
+ )
839
+ }
840
+
841
+ export {
842
+ Select,
843
+ SelectCheckboxIndicator,
844
+ SelectHelperText,
845
+ SelectItemAvatar,
846
+ SelectItemCompanyLogo,
847
+ SelectItemFlag,
848
+ SelectItemPaymentIcon,
849
+ SelectItemStatusDot,
850
+ SelectLabel,
851
+ SelectMenu,
852
+ SelectMenuItem,
853
+ SelectRoot,
854
+ SelectTrigger,
855
+ selectMenu,
856
+ selectMenuItem,
857
+ selectRoot,
858
+ selectTrigger,
859
+ }
860
+ export type {
861
+ SelectItemType,
862
+ SelectMenuItemProps,
863
+ SelectProps,
864
+ SelectRootProps,
865
+ SelectSelectionType,
866
+ SelectSize,
867
+ SelectStatusTone,
868
+ SelectTriggerProps,
869
+ SelectVariant,
870
+ SelectVisualState,
871
+ }