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,866 @@
1
+ import * as React from "react"
2
+ import { cva } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import {
6
+ getInputDefaultValue,
7
+ getInputHelperText,
8
+ getInputPlaceholder,
9
+ getInputTags,
10
+ inputCopy,
11
+ type InputKind,
12
+ type InputTag,
13
+ type InputVisualState,
14
+ } from "./input-content"
15
+
16
+ type InputSize = "md" | "lg"
17
+ type InputVariant = "default" | "soft"
18
+
19
+ type InputNativeProps = Omit<
20
+ React.ComponentProps<"input">,
21
+ "className" | "dir" | "size"
22
+ >
23
+
24
+ type InputModeRenderProps = {
25
+ buttonLabel?: React.ReactNode
26
+ inlineLeadingAddon?: React.ReactNode
27
+ inlineTagsClassName?: string
28
+ inlineTrailingAddon?: React.ReactNode
29
+ leadingAddon?: React.ReactNode
30
+ quantityLabel?: React.ReactNode
31
+ shortcut?: React.ReactNode
32
+ tags?: InputTag[]
33
+ trailingAddon?: React.ReactNode
34
+ }
35
+
36
+ type InputKindSpecificProps = InputModeRenderProps &
37
+ (
38
+ | { kind?: "default" }
39
+ | { kind: "shortcut" }
40
+ | { kind: "add-on" }
41
+ | { kind: "inline-add-on" }
42
+ | { kind: "tags" | "inline-tags" }
43
+ | {
44
+ kind:
45
+ | "leading-dropdown"
46
+ | "trailing-dropdown"
47
+ | "leading-button"
48
+ | "trailing-button"
49
+ }
50
+ | { kind: "quantity" | "quantity-2" }
51
+ )
52
+
53
+ type InputDynamicKindProps = { kind?: InputKind } & InputModeRenderProps
54
+
55
+ type InputCommonProps = InputNativeProps & {
56
+ className?: string
57
+ controlClassName?: string
58
+ helperClassName?: string
59
+ labelClassName?: string
60
+ surfaceClassName?: string
61
+ errorMessage?: React.ReactNode
62
+ hasHelperText?: boolean
63
+ hasInformationIcon?: boolean
64
+ hasLabel?: boolean
65
+ isOptional?: boolean
66
+ isRequired?: boolean
67
+ label?: React.ReactNode
68
+ leadingIcon?: React.ReactNode
69
+ message?: React.ReactNode
70
+ optionalText?: React.ReactNode
71
+ size?: InputSize
72
+ state?: InputVisualState
73
+ trailingIcon?: React.ReactNode
74
+ valueText?: string
75
+ variant?: InputVariant
76
+ dir?: "ltr" | "rtl" | "auto"
77
+ }
78
+
79
+ type InputProps = InputCommonProps & (InputKindSpecificProps | InputDynamicKindProps)
80
+
81
+ const inputRoot = cva("grid w-[var(--bh-input-width)] max-w-full")
82
+
83
+ const inputSurface = cva(
84
+ [
85
+ "group/input-surface relative flex w-full overflow-hidden rounded-[var(--bh-input-radius)]",
86
+ "transition-[background-color,box-shadow]",
87
+ "[--bh-input-border:var(--bh-border-input)] [--shadow-input:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-input-border,var(--bh-border-input)),var(--shadow-component-default)]",
88
+ "focus-within:shadow-[var(--shadow-input-focus-ring)]",
89
+ ],
90
+ {
91
+ variants: {
92
+ size: {
93
+ md: "h-[var(--bh-input-md-height)]",
94
+ lg: "h-[var(--bh-input-lg-height)]",
95
+ },
96
+ variant: {
97
+ default: "bg-[var(--bh-interactive-input-default)]",
98
+ soft: "bg-[var(--bh-interactive-input-soft)]",
99
+ },
100
+ state: {
101
+ default:
102
+ "shadow-[var(--shadow-input)]",
103
+ filled:
104
+ "shadow-[var(--shadow-input)]",
105
+ error:
106
+ "[--bh-input-border:var(--bh-border-danger-strong)] shadow-[var(--shadow-input)] focus-within:shadow-[var(--shadow-input)]",
107
+ disabled:
108
+ "bg-[var(--bh-interactive-input-disabled)] shadow-[inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)]",
109
+ },
110
+ },
111
+ compoundVariants: [
112
+ {
113
+ variant: "soft",
114
+ state: "disabled",
115
+ class: "bg-[var(--bh-interactive-input-soft-disabled)]",
116
+ },
117
+ ],
118
+ defaultVariants: {
119
+ size: "lg",
120
+ state: "default",
121
+ variant: "default",
122
+ },
123
+ }
124
+ )
125
+
126
+ const inputContent = cva(
127
+ "flex min-w-0 flex-1 items-center gap-[var(--bh-input-content-gap)]",
128
+ {
129
+ variants: {
130
+ size: {
131
+ md: "px-[var(--bh-input-md-padding-x)]",
132
+ lg: "px-[var(--bh-input-lg-padding-x)]",
133
+ },
134
+ },
135
+ defaultVariants: {
136
+ size: "lg",
137
+ },
138
+ }
139
+ )
140
+
141
+ const inputControl = cva(
142
+ [
143
+ "min-w-0 flex-1 border-0 bg-transparent p-0 text-start",
144
+ "text-[length:var(--bh-text-body-md-regular-font-size)]",
145
+ "font-[var(--bh-text-body-md-regular-font-weight)]",
146
+ "leading-[var(--bh-text-body-md-regular-line-height)]",
147
+ "tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
148
+ "text-[var(--bh-content-default)] outline-none",
149
+ "placeholder:text-[var(--bh-content-muted)]",
150
+ "disabled:cursor-not-allowed disabled:text-[var(--bh-content-disabled)] disabled:placeholder:text-[var(--bh-content-disabled)]",
151
+ ]
152
+ )
153
+
154
+ const inputTag = cva(
155
+ [
156
+ "inline-flex h-[var(--bh-input-tag-height)] shrink-0 items-center rounded-[var(--bh-control-md)] border border-solid",
157
+ "border-[var(--bh-border-default)] bg-[var(--bh-interactive-secondary-default)] px-[var(--bh-input-tag-padding-x)]",
158
+ "text-[length:var(--bh-text-body-xs-medium-font-size)]",
159
+ "font-[var(--bh-text-body-xs-medium-font-weight)]",
160
+ "leading-[var(--bh-text-body-xs-medium-line-height)]",
161
+ "tracking-[var(--bh-text-body-xs-medium-letter-spacing)]",
162
+ "text-[var(--bh-content-default)]",
163
+ ],
164
+ {
165
+ variants: {
166
+ disabled: {
167
+ true:
168
+ "border-[var(--bh-border-disabled)] bg-[var(--bh-interactive-secondary-disabled)] text-[var(--bh-content-disabled)]",
169
+ false: "",
170
+ },
171
+ },
172
+ defaultVariants: {
173
+ disabled: false,
174
+ },
175
+ }
176
+ )
177
+
178
+ const inputSegment = cva(
179
+ [
180
+ "inline-flex h-full shrink-0 items-center justify-center gap-[var(--bh-input-segment-gap)]",
181
+ "bg-[var(--bh-interactive-secondary-default)] px-[var(--bh-input-segment-padding-x)]",
182
+ "text-[length:var(--bh-text-body-md-regular-font-size)]",
183
+ "font-[var(--bh-text-body-md-regular-font-weight)]",
184
+ "leading-[var(--bh-text-body-md-regular-line-height)]",
185
+ "tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
186
+ "text-[var(--bh-content-subtle)]",
187
+ "disabled:text-[var(--bh-content-disabled)]",
188
+ ],
189
+ {
190
+ variants: {
191
+ side: {
192
+ leading: "border-e border-[var(--bh-border-input)]",
193
+ trailing: "border-s border-[var(--bh-border-input)]",
194
+ },
195
+ interactive: {
196
+ true:
197
+ "outline-none transition-[background-color,color] hover:bg-[var(--bh-interactive-secondary-hover)] focus-visible:bg-[var(--bh-interactive-secondary-hover)]",
198
+ false: "",
199
+ },
200
+ disabled: {
201
+ true:
202
+ "border-[var(--bh-border-disabled)] bg-[var(--bh-interactive-secondary-disabled)] text-[var(--bh-content-disabled)]",
203
+ false: "",
204
+ },
205
+ },
206
+ defaultVariants: {
207
+ interactive: false,
208
+ side: "leading",
209
+ disabled: false,
210
+ },
211
+ }
212
+ )
213
+
214
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
215
+ (
216
+ {
217
+ className,
218
+ controlClassName,
219
+ helperClassName,
220
+ inlineTagsClassName,
221
+ labelClassName,
222
+ surfaceClassName,
223
+ buttonLabel,
224
+ defaultValue,
225
+ dir,
226
+ disabled,
227
+ errorMessage,
228
+ hasHelperText,
229
+ hasInformationIcon = true,
230
+ hasLabel,
231
+ id,
232
+ inlineLeadingAddon,
233
+ inlineTrailingAddon,
234
+ isOptional = false,
235
+ isRequired = false,
236
+ kind = "default",
237
+ label,
238
+ leadingAddon,
239
+ leadingIcon,
240
+ message,
241
+ optionalText,
242
+ placeholder,
243
+ quantityLabel,
244
+ readOnly,
245
+ required,
246
+ shortcut = inputCopy.shortcut,
247
+ size = "lg",
248
+ state = "default",
249
+ tags,
250
+ trailingAddon,
251
+ trailingIcon,
252
+ value,
253
+ valueText,
254
+ variant = "default",
255
+ ...props
256
+ },
257
+ ref
258
+ ) => {
259
+ const generatedId = React.useId()
260
+ const inputId = id || generatedId
261
+ const helperId = `${inputId}-helper`
262
+ const inputRef = React.useRef<HTMLInputElement | null>(null)
263
+ const resolvedDir = dir
264
+ const visualState = disabled ? "disabled" : state
265
+ const isDisabled = visualState === "disabled"
266
+ const isInvalid = visualState === "error" && !isDisabled
267
+ const isTagInput = kind === "tags" || kind === "inline-tags"
268
+ const selectedLabel = label
269
+ const selectedOptionalText = optionalText
270
+ const inferredPlaceholder = getInputPlaceholder({
271
+ placeholder,
272
+ })
273
+ const inferredDefaultValue = getInputDefaultValue({
274
+ defaultValue,
275
+ isTagInput,
276
+ state: visualState,
277
+ value,
278
+ valueText,
279
+ })
280
+ const helperText = getInputHelperText({
281
+ errorMessage,
282
+ isInvalid,
283
+ message,
284
+ })
285
+ const shouldRenderLabel = hasLabel ?? hasRenderableContent(selectedLabel)
286
+ const shouldRenderHelperText =
287
+ hasHelperText ?? hasRenderableContent(helperText)
288
+ const selectedTags = getInputTags({ kind, tags })
289
+
290
+ function focusInput() {
291
+ if (!isDisabled) inputRef.current?.focus()
292
+ }
293
+
294
+ function setInputRef(node: HTMLInputElement | null) {
295
+ inputRef.current = node
296
+
297
+ if (typeof ref === "function") {
298
+ ref(node)
299
+ } else if (ref) {
300
+ ref.current = node
301
+ }
302
+ }
303
+
304
+ return (
305
+ <div
306
+ data-kind={kind}
307
+ data-size={size}
308
+ data-slot="input-root"
309
+ data-state={visualState}
310
+ data-variant={variant}
311
+ dir={resolvedDir}
312
+ className={cn(inputRoot(), className)}
313
+ >
314
+ {shouldRenderLabel ? (
315
+ <InputLabel
316
+ className={labelClassName}
317
+ htmlFor={inputId}
318
+ isOptional={isOptional}
319
+ isRequired={isRequired || Boolean(required)}
320
+ label={selectedLabel}
321
+ optionalText={selectedOptionalText}
322
+ />
323
+ ) : null}
324
+
325
+ <div
326
+ data-slot="input-body"
327
+ className={cn(
328
+ "grid",
329
+ shouldRenderLabel && "mt-[var(--bh-input-label-gap)]",
330
+ selectedTags.inline.length > 0
331
+ ? "gap-[var(--bh-input-inline-tags-gap)]"
332
+ : "gap-[var(--bh-input-helper-gap)]"
333
+ )}
334
+ >
335
+ <div
336
+ aria-disabled={isDisabled || undefined}
337
+ data-slot="input-surface"
338
+ data-state={visualState}
339
+ className={cn(
340
+ inputSurface({
341
+ size,
342
+ state: visualState,
343
+ variant,
344
+ }),
345
+ surfaceClassName
346
+ )}
347
+ onClick={focusInput}
348
+ >
349
+ {renderLeadingSegment({
350
+ buttonLabel,
351
+ disabled: isDisabled,
352
+ kind,
353
+ leadingAddon,
354
+ quantityLabel,
355
+ trailingAddon,
356
+ })}
357
+
358
+ <span data-slot="input-content" className={cn(inputContent({ size }))}>
359
+ {shouldRenderLeadingIcon(kind) ? (
360
+ <InputLeadingIcon disabled={isDisabled} icon={leadingIcon} />
361
+ ) : null}
362
+
363
+ {kind === "inline-add-on" &&
364
+ hasRenderableContent(inlineLeadingAddon) ? (
365
+ <InputInlineAddon disabled={isDisabled}>
366
+ {inlineLeadingAddon}
367
+ </InputInlineAddon>
368
+ ) : null}
369
+
370
+ {selectedTags.inside.length > 0 ? (
371
+ <InputTagList disabled={isDisabled} tags={selectedTags.inside} />
372
+ ) : null}
373
+
374
+ <input
375
+ aria-describedby={shouldRenderHelperText ? helperId : undefined}
376
+ aria-invalid={isInvalid || undefined}
377
+ data-slot="input-control"
378
+ data-state={visualState}
379
+ defaultValue={inferredDefaultValue}
380
+ disabled={isDisabled}
381
+ id={inputId}
382
+ placeholder={inferredPlaceholder}
383
+ readOnly={readOnly}
384
+ ref={setInputRef}
385
+ required={required}
386
+ value={value}
387
+ className={cn(inputControl(), controlClassName)}
388
+ {...props}
389
+ />
390
+
391
+ {kind === "inline-add-on" &&
392
+ hasRenderableContent(inlineTrailingAddon) ? (
393
+ <InputInlineAddon disabled={isDisabled}>
394
+ {inlineTrailingAddon}
395
+ </InputInlineAddon>
396
+ ) : null}
397
+
398
+ {shouldRenderInfoIcon(kind, hasInformationIcon) ? (
399
+ <InputInfoIcon
400
+ disabled={isDisabled}
401
+ invalid={isInvalid}
402
+ icon={trailingIcon}
403
+ />
404
+ ) : null}
405
+
406
+ {kind === "shortcut" ? (
407
+ <InputShortcut disabled={isDisabled}>{shortcut}</InputShortcut>
408
+ ) : null}
409
+ </span>
410
+
411
+ {renderTrailingSegment({
412
+ buttonLabel,
413
+ disabled: isDisabled,
414
+ kind,
415
+ leadingAddon,
416
+ quantityLabel,
417
+ trailingAddon,
418
+ })}
419
+ </div>
420
+
421
+ {selectedTags.inline.length > 0 ? (
422
+ <InputTagList
423
+ className={inlineTagsClassName}
424
+ disabled={isDisabled}
425
+ inline
426
+ tags={selectedTags.inline}
427
+ />
428
+ ) : null}
429
+
430
+ {shouldRenderHelperText ? (
431
+ <InputHelperText
432
+ className={helperClassName}
433
+ id={helperId}
434
+ invalid={isInvalid}
435
+ >
436
+ {helperText}
437
+ </InputHelperText>
438
+ ) : null}
439
+ </div>
440
+ </div>
441
+ )
442
+ }
443
+ )
444
+ Input.displayName = "Input"
445
+
446
+ function InputLabel({
447
+ className,
448
+ htmlFor,
449
+ isOptional,
450
+ isRequired,
451
+ label,
452
+ optionalText,
453
+ }: {
454
+ className?: string
455
+ htmlFor: string
456
+ isOptional: boolean
457
+ isRequired: boolean
458
+ label: React.ReactNode
459
+ optionalText: React.ReactNode
460
+ }) {
461
+ return (
462
+ <label
463
+ data-slot="input-label"
464
+ htmlFor={htmlFor}
465
+ className={cn(
466
+ "flex w-full items-center gap-[var(--bh-input-inline-gap)] text-start",
467
+ className
468
+ )}
469
+ >
470
+ <span
471
+ data-slot="input-label-text"
472
+ dir="auto"
473
+ 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)]"
474
+ >
475
+ {label}
476
+ </span>
477
+ {isRequired ? (
478
+ <span
479
+ aria-hidden="true"
480
+ data-slot="input-label-required"
481
+ 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)]"
482
+ >
483
+ *
484
+ </span>
485
+ ) : null}
486
+ {isOptional && hasRenderableContent(optionalText) ? (
487
+ <span
488
+ data-slot="input-label-optional"
489
+ dir="auto"
490
+ 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)]"
491
+ >
492
+ {optionalText}
493
+ </span>
494
+ ) : null}
495
+ </label>
496
+ )
497
+ }
498
+
499
+ function hasRenderableContent(content: React.ReactNode) {
500
+ return (
501
+ content !== undefined &&
502
+ content !== null &&
503
+ content !== false &&
504
+ content !== ""
505
+ )
506
+ }
507
+
508
+ function InputHelperText({
509
+ children,
510
+ className,
511
+ id,
512
+ invalid,
513
+ }: {
514
+ children: React.ReactNode
515
+ className?: string
516
+ id: string
517
+ invalid: boolean
518
+ }) {
519
+ return (
520
+ <p
521
+ data-slot="input-helper-text"
522
+ id={id}
523
+ className={cn(
524
+ "m-0 w-full text-start text-[length:var(--bh-text-body-xs-regular-font-size)]",
525
+ "font-[var(--bh-text-body-xs-regular-font-weight)]",
526
+ "leading-[var(--bh-text-body-xs-regular-line-height)]",
527
+ "tracking-[var(--bh-text-body-xs-regular-letter-spacing)]",
528
+ invalid ? "text-[var(--bh-content-danger-default)]" : "text-[var(--bh-content-subtle)]",
529
+ className
530
+ )}
531
+ >
532
+ <span data-slot="input-helper-label" dir="auto" className="min-w-0">
533
+ {children}
534
+ </span>
535
+ </p>
536
+ )
537
+ }
538
+
539
+ function InputLeadingIcon({
540
+ disabled,
541
+ icon,
542
+ }: {
543
+ disabled: boolean
544
+ icon?: React.ReactNode
545
+ }) {
546
+ return (
547
+ <span
548
+ aria-hidden="true"
549
+ data-slot="input-leading-icon"
550
+ className={cn(
551
+ "flex size-[var(--bh-input-icon-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)]",
552
+ disabled && "text-[var(--bh-content-disabled)]"
553
+ )}
554
+ >
555
+ {icon || <span className="text-[length:var(--bh-input-leading-symbol-font-size)] leading-none">@</span>}
556
+ </span>
557
+ )
558
+ }
559
+
560
+ function InputInfoIcon({
561
+ disabled,
562
+ icon,
563
+ invalid,
564
+ }: {
565
+ disabled: boolean
566
+ icon?: React.ReactNode
567
+ invalid: boolean
568
+ }) {
569
+ return (
570
+ <span
571
+ aria-hidden="true"
572
+ data-slot="input-info-icon"
573
+ className={cn(
574
+ "flex size-[var(--bh-input-icon-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)]",
575
+ invalid && "text-[var(--bh-content-danger-default)]",
576
+ disabled && "text-[var(--bh-content-disabled)]"
577
+ )}
578
+ >
579
+ {icon || (
580
+ <svg
581
+ className="size-[var(--bh-input-info-icon-size)]"
582
+ fill="none"
583
+ focusable="false"
584
+ viewBox="0 0 16 16"
585
+ >
586
+ <path
587
+ d="M8 14.6667C4.3181 14.6667 1.33333 11.6819 1.33333 8C1.33333 4.3181 4.3181 1.33333 8 1.33333C11.6819 1.33333 14.6667 4.3181 14.6667 8C14.6667 11.6819 11.6819 14.6667 8 14.6667ZM8 6.33333C8.55227 6.33333 9 5.88562 9 5.33333C9 4.78105 8.55227 4.33333 8 4.33333C7.44773 4.33333 7 4.78105 7 5.33333C7 5.88562 7.44773 6.33333 8 6.33333ZM9.33333 10H8.66667V7H6.66667V8.33333H7.33333V10H6.66667V11.3333H9.33333V10Z"
588
+ fill="currentColor"
589
+ />
590
+ </svg>
591
+ )}
592
+ </span>
593
+ )
594
+ }
595
+
596
+ function InputInlineAddon({
597
+ children,
598
+ disabled,
599
+ }: {
600
+ children: React.ReactNode
601
+ disabled: boolean
602
+ }) {
603
+ return (
604
+ <span
605
+ data-slot="input-inline-addon"
606
+ dir="auto"
607
+ className={cn(
608
+ "shrink-0 whitespace-nowrap text-[var(--bh-content-subtle)]",
609
+ disabled && "text-[var(--bh-content-disabled)]"
610
+ )}
611
+ >
612
+ {children}
613
+ </span>
614
+ )
615
+ }
616
+
617
+ function InputShortcut({
618
+ children,
619
+ disabled,
620
+ }: {
621
+ children: React.ReactNode
622
+ disabled: boolean
623
+ }) {
624
+ return (
625
+ <span
626
+ data-slot="input-shortcut"
627
+ className={cn(
628
+ "inline-flex h-[var(--bh-input-shortcut-height)] shrink-0 items-center rounded-[var(--bh-radius-md-6)] border border-[var(--bh-border-default)] bg-[var(--bh-interactive-secondary-default)] px-[var(--bh-input-shortcut-padding-x)] text-[length:var(--bh-text-body-2xs-medium-font-size)] font-[var(--bh-text-body-2xs-medium-font-weight)] leading-[var(--bh-text-body-2xs-medium-line-height)] tracking-[var(--bh-text-body-2xs-medium-letter-spacing)] text-[var(--bh-content-subtle)]",
629
+ disabled &&
630
+ "border-[var(--bh-border-disabled)] bg-[var(--bh-interactive-secondary-disabled)] text-[var(--bh-content-disabled)]"
631
+ )}
632
+ >
633
+ {children}
634
+ </span>
635
+ )
636
+ }
637
+
638
+ function InputTagList({
639
+ className,
640
+ disabled,
641
+ inline = false,
642
+ tags,
643
+ }: {
644
+ className?: string
645
+ disabled: boolean
646
+ inline?: boolean
647
+ tags: InputTag[]
648
+ }) {
649
+ return (
650
+ <span
651
+ data-inline={inline ? "true" : undefined}
652
+ data-slot={inline ? "input-inline-tags" : "input-tags"}
653
+ className={cn(
654
+ "flex min-w-0 items-center gap-[var(--bh-input-inline-gap)]",
655
+ inline && "flex-wrap",
656
+ className
657
+ )}
658
+ >
659
+ {tags.map((tag, index) => (
660
+ <span
661
+ key={tag.id || index}
662
+ data-slot="input-tag"
663
+ className={cn(inputTag({ disabled }))}
664
+ >
665
+ <span data-slot="input-tag-label" dir="auto" className="min-w-0 truncate">
666
+ {tag.label}
667
+ </span>
668
+ </span>
669
+ ))}
670
+ </span>
671
+ )
672
+ }
673
+
674
+ function InputSegment({
675
+ children,
676
+ disabled,
677
+ interactive = false,
678
+ side,
679
+ }: {
680
+ children: React.ReactNode
681
+ disabled: boolean
682
+ interactive?: boolean
683
+ side: "leading" | "trailing"
684
+ }) {
685
+ const Comp = interactive ? "button" : "span"
686
+
687
+ return (
688
+ <Comp
689
+ data-slot="input-segment"
690
+ disabled={interactive ? disabled : undefined}
691
+ type={interactive ? "button" : undefined}
692
+ className={cn(inputSegment({ disabled, interactive, side }))}
693
+ onClick={(event) => {
694
+ if (interactive) event.stopPropagation()
695
+ }}
696
+ >
697
+ {children}
698
+ </Comp>
699
+ )
700
+ }
701
+
702
+ type InputSegmentRenderContext = {
703
+ buttonLabel?: React.ReactNode
704
+ disabled: boolean
705
+ kind: InputKind
706
+ leadingAddon?: React.ReactNode
707
+ quantityLabel?: React.ReactNode
708
+ trailingAddon?: React.ReactNode
709
+ }
710
+
711
+ type InputSegmentRenderer = (
712
+ context: InputSegmentRenderContext
713
+ ) => React.ReactNode
714
+
715
+ const leadingSegmentRenderers: Partial<Record<InputKind, InputSegmentRenderer>> = {
716
+ "add-on": ({ disabled, leadingAddon }) =>
717
+ hasRenderableContent(leadingAddon) ? (
718
+ <InputSegment disabled={disabled} side="leading">
719
+ {leadingAddon}
720
+ </InputSegment>
721
+ ) : null,
722
+ "leading-button": ({ buttonLabel, disabled }) =>
723
+ hasRenderableContent(buttonLabel) ? (
724
+ <InputSegment disabled={disabled} interactive side="leading">
725
+ {buttonLabel}
726
+ </InputSegment>
727
+ ) : null,
728
+ "leading-dropdown": ({ buttonLabel, disabled }) => (
729
+ <InputSegment disabled={disabled} interactive side="leading">
730
+ {buttonLabel}
731
+ <ChevronIcon />
732
+ </InputSegment>
733
+ ),
734
+ "quantity-2": ({ disabled, quantityLabel }) => (
735
+ <InputSegment disabled={disabled} interactive side="leading">
736
+ <MinusIcon />
737
+ {hasRenderableContent(quantityLabel) ? (
738
+ <span className="sr-only">{quantityLabel}</span>
739
+ ) : null}
740
+ </InputSegment>
741
+ ),
742
+ }
743
+
744
+ const trailingSegmentRenderers: Partial<Record<InputKind, InputSegmentRenderer>> = {
745
+ "add-on": ({ disabled, trailingAddon }) =>
746
+ hasRenderableContent(trailingAddon) ? (
747
+ <InputSegment disabled={disabled} side="trailing">
748
+ {trailingAddon}
749
+ </InputSegment>
750
+ ) : null,
751
+ "trailing-button": ({ buttonLabel, disabled }) =>
752
+ hasRenderableContent(buttonLabel) ? (
753
+ <InputSegment disabled={disabled} interactive side="trailing">
754
+ {buttonLabel}
755
+ </InputSegment>
756
+ ) : null,
757
+ "trailing-dropdown": ({ buttonLabel, disabled }) => (
758
+ <InputSegment disabled={disabled} interactive side="trailing">
759
+ {buttonLabel}
760
+ <ChevronIcon />
761
+ </InputSegment>
762
+ ),
763
+ quantity: ({ disabled }) => (
764
+ <InputSegment disabled={disabled} interactive side="trailing">
765
+ <MinusIcon />
766
+ <PlusIcon />
767
+ </InputSegment>
768
+ ),
769
+ "quantity-2": ({ disabled }) => (
770
+ <InputSegment disabled={disabled} interactive side="trailing">
771
+ <MinusIcon />
772
+ <PlusIcon />
773
+ </InputSegment>
774
+ ),
775
+ }
776
+
777
+ function renderLeadingSegment(context: InputSegmentRenderContext) {
778
+ return leadingSegmentRenderers[context.kind]?.(context) ?? null
779
+ }
780
+
781
+ function renderTrailingSegment(context: InputSegmentRenderContext) {
782
+ return trailingSegmentRenderers[context.kind]?.(context) ?? null
783
+ }
784
+
785
+ function ChevronIcon() {
786
+ return (
787
+ <svg
788
+ aria-hidden="true"
789
+ className="size-[var(--bh-input-chevron-size)]"
790
+ fill="none"
791
+ focusable="false"
792
+ viewBox="0 0 16 16"
793
+ >
794
+ <path
795
+ d="M8 10.6667L4 6.66667L4.93333 5.73333L8 8.8L11.0667 5.73333L12 6.66667L8 10.6667Z"
796
+ fill="currentColor"
797
+ />
798
+ </svg>
799
+ )
800
+ }
801
+
802
+ function MinusIcon() {
803
+ return (
804
+ <svg
805
+ aria-hidden="true"
806
+ className="size-[var(--bh-input-stepper-icon-size)]"
807
+ fill="none"
808
+ focusable="false"
809
+ viewBox="0 0 16 16"
810
+ >
811
+ <path d="M3.33333 7.33333H12.6667V8.66667H3.33333V7.33333Z" fill="currentColor" />
812
+ </svg>
813
+ )
814
+ }
815
+
816
+ function PlusIcon() {
817
+ return (
818
+ <svg
819
+ aria-hidden="true"
820
+ className="size-[var(--bh-input-stepper-icon-size)]"
821
+ fill="none"
822
+ focusable="false"
823
+ viewBox="0 0 16 16"
824
+ >
825
+ <path
826
+ d="M7.33333 7.33333V3.33333H8.66667V7.33333H12.6667V8.66667H8.66667V12.6667H7.33333V8.66667H3.33333V7.33333H7.33333Z"
827
+ fill="currentColor"
828
+ />
829
+ </svg>
830
+ )
831
+ }
832
+
833
+ function shouldRenderLeadingIcon(kind: InputKind) {
834
+ return [
835
+ "default",
836
+ "shortcut",
837
+ "tags",
838
+ "inline-tags",
839
+ "trailing-dropdown",
840
+ "leading-dropdown",
841
+ "trailing-button",
842
+ "leading-button",
843
+ ].includes(kind)
844
+ }
845
+
846
+ function shouldRenderInfoIcon(kind: InputKind, hasInformationIcon: boolean) {
847
+ if (!hasInformationIcon) return false
848
+
849
+ return ![
850
+ "shortcut",
851
+ "trailing-dropdown",
852
+ "trailing-button",
853
+ "quantity",
854
+ "quantity-2",
855
+ ].includes(kind)
856
+ }
857
+
858
+ export { Input, inputContent, inputControl, inputRoot, inputSurface }
859
+ export type {
860
+ InputKind,
861
+ InputProps,
862
+ InputSize,
863
+ InputTag,
864
+ InputVariant,
865
+ InputVisualState,
866
+ }