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,216 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SliderPrimitive from "@radix-ui/react-slider"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ type ProgressSliderTone = "brand" | "neutral" | "success" | "warning" | "danger"
10
+ type ProgressSliderSize = "default" | "lg"
11
+
12
+ const progressSliderVariants = cva(
13
+ [
14
+ "group/progress-slider relative flex touch-none select-none items-center",
15
+ "w-[var(--bh-progress-slider-width-current)] max-w-full",
16
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-[var(--bh-opacity-60)]",
17
+ "[--bh-progress-slider-width-current:var(--bh-progress-slider-width-md)]",
18
+ "[--bh-progress-slider-height-current:var(--bh-progress-slider-height-md)]",
19
+ "[--bh-progress-slider-handle-width-current:var(--bh-progress-slider-handle-width-md)]",
20
+ "[--bh-progress-slider-border-width-current:var(--bh-progress-slider-border-width-md)]",
21
+ "[--bh-progress-slider-grip-height-current:var(--bh-progress-slider-grip-height-md)]",
22
+ "[--bh-progress-slider-track-bg:var(--bh-bg-brand-soft)]",
23
+ "[--bh-progress-slider-fill-bg:var(--bh-bg-brand-subtle)]",
24
+ "[--bh-progress-slider-fill-border:var(--bh-border-brand-subtle)]",
25
+ "[--bh-progress-slider-grip-bg:var(--bh-border-brand-strong)]",
26
+ ],
27
+ {
28
+ variants: {
29
+ size: {
30
+ default: [
31
+ "[--bh-progress-slider-width-current:var(--bh-progress-slider-width-md)]",
32
+ "[--bh-progress-slider-height-current:var(--bh-progress-slider-height-md)]",
33
+ "[--bh-progress-slider-handle-width-current:var(--bh-progress-slider-handle-width-md)]",
34
+ "[--bh-progress-slider-border-width-current:var(--bh-progress-slider-border-width-md)]",
35
+ "[--bh-progress-slider-grip-height-current:var(--bh-progress-slider-grip-height-md)]",
36
+ ],
37
+ lg: [
38
+ "[--bh-progress-slider-width-current:var(--bh-progress-slider-width-lg)]",
39
+ "[--bh-progress-slider-height-current:var(--bh-progress-slider-height-lg)]",
40
+ "[--bh-progress-slider-handle-width-current:var(--bh-progress-slider-handle-width-lg)]",
41
+ "[--bh-progress-slider-border-width-current:var(--bh-progress-slider-border-width-lg)]",
42
+ "[--bh-progress-slider-grip-height-current:var(--bh-progress-slider-grip-height-lg)]",
43
+ ],
44
+ },
45
+ tone: {
46
+ brand: [
47
+ "[--bh-progress-slider-track-bg:var(--bh-bg-brand-soft)]",
48
+ "[--bh-progress-slider-fill-bg:var(--bh-bg-brand-subtle)]",
49
+ "[--bh-progress-slider-fill-border:var(--bh-border-brand-subtle)]",
50
+ "[--bh-progress-slider-grip-bg:var(--bh-border-brand-strong)]",
51
+ ],
52
+ neutral: [
53
+ "[--bh-progress-slider-track-bg:var(--bh-bg-neutral-soft)]",
54
+ "[--bh-progress-slider-fill-bg:var(--bh-bg-neutral-subtle)]",
55
+ "[--bh-progress-slider-fill-border:var(--bh-border-subtle)]",
56
+ "[--bh-progress-slider-grip-bg:var(--bh-border-strong)]",
57
+ ],
58
+ success: [
59
+ "[--bh-progress-slider-track-bg:var(--bh-bg-success-soft)]",
60
+ "[--bh-progress-slider-fill-bg:var(--bh-bg-success-subtle)]",
61
+ "[--bh-progress-slider-fill-border:var(--bh-border-success-subtle)]",
62
+ "[--bh-progress-slider-grip-bg:var(--bh-border-success-strong)]",
63
+ ],
64
+ warning: [
65
+ "[--bh-progress-slider-track-bg:var(--bh-bg-warning-soft)]",
66
+ "[--bh-progress-slider-fill-bg:var(--bh-bg-warning-subtle)]",
67
+ "[--bh-progress-slider-fill-border:var(--bh-border-warning-subtle)]",
68
+ "[--bh-progress-slider-grip-bg:var(--bh-border-warning-strong)]",
69
+ ],
70
+ danger: [
71
+ "[--bh-progress-slider-track-bg:var(--bh-bg-danger-soft)]",
72
+ "[--bh-progress-slider-fill-bg:var(--bh-bg-danger-subtle)]",
73
+ "[--bh-progress-slider-fill-border:var(--bh-border-danger-subtle)]",
74
+ "[--bh-progress-slider-grip-bg:var(--bh-border-danger-strong)]",
75
+ ],
76
+ },
77
+ },
78
+ defaultVariants: {
79
+ size: "default",
80
+ tone: "brand",
81
+ },
82
+ }
83
+ )
84
+
85
+ type ProgressSliderProps = Omit<
86
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>,
87
+ | "children"
88
+ | "defaultValue"
89
+ | "onValueChange"
90
+ | "onValueCommit"
91
+ | "orientation"
92
+ | "value"
93
+ > &
94
+ VariantProps<typeof progressSliderVariants> & {
95
+ ariaValueText?: string | ((value: number) => string)
96
+ defaultValue?: number
97
+ onValueChange?: (value: number) => void
98
+ onValueCommit?: (value: number) => void
99
+ showTicks?: boolean
100
+ tickCount?: number
101
+ value?: number
102
+ }
103
+
104
+ function ProgressSlider({
105
+ ariaValueText,
106
+ className,
107
+ defaultValue,
108
+ max = 100,
109
+ min = 0,
110
+ onValueChange,
111
+ onValueCommit,
112
+ showTicks = false,
113
+ size,
114
+ step,
115
+ tickCount = 8,
116
+ tone,
117
+ value,
118
+ ...props
119
+ }: ProgressSliderProps) {
120
+ const isControlled = value !== undefined
121
+ const initialValueRef = React.useRef(
122
+ normalizeProgressValue(value ?? defaultValue ?? min, min, max)
123
+ )
124
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(
125
+ initialValueRef.current
126
+ )
127
+ const currentValue = normalizeProgressValue(
128
+ isControlled ? value : uncontrolledValue,
129
+ min,
130
+ max
131
+ )
132
+ const sliderValue = [currentValue]
133
+
134
+ return (
135
+ <SliderPrimitive.Root
136
+ className={cn(progressSliderVariants({ size, tone }), className)}
137
+ data-slot="progress-slider"
138
+ defaultValue={isControlled ? undefined : [initialValueRef.current]}
139
+ max={max}
140
+ min={min}
141
+ onValueChange={(nextValue) => {
142
+ const nextProgressValue = normalizeProgressValue(
143
+ nextValue[0] ?? min,
144
+ min,
145
+ max
146
+ )
147
+
148
+ if (!isControlled) {
149
+ setUncontrolledValue(nextProgressValue)
150
+ }
151
+
152
+ onValueChange?.(nextProgressValue)
153
+ }}
154
+ onValueCommit={(nextValue) => {
155
+ onValueCommit?.(normalizeProgressValue(nextValue[0] ?? min, min, max))
156
+ }}
157
+ orientation="horizontal"
158
+ step={step}
159
+ value={isControlled ? sliderValue : undefined}
160
+ {...props}
161
+ >
162
+ <SliderPrimitive.Track
163
+ className="relative h-[var(--bh-progress-slider-height-current)] w-full grow overflow-hidden rounded-[var(--bh-progress-slider-radius)] bg-[var(--bh-progress-slider-track-bg)]"
164
+ data-slot="progress-slider-track"
165
+ >
166
+ <SliderPrimitive.Range
167
+ className="absolute inset-y-0 z-[var(--bh-z-raised)] rounded-[var(--bh-progress-slider-radius)] border-[length:var(--bh-progress-slider-border-width-current)] border-[var(--bh-progress-slider-fill-border)] bg-[var(--bh-progress-slider-fill-bg)]"
168
+ data-slot="progress-slider-range"
169
+ />
170
+ {showTicks ? (
171
+ <span
172
+ aria-hidden="true"
173
+ className="pointer-events-none absolute inset-0 z-[var(--bh-z-popover)] flex items-center justify-evenly gap-[var(--bh-progress-slider-tick-gap)] px-[var(--bh-progress-slider-tick-padding)]"
174
+ data-slot="progress-slider-ticks"
175
+ >
176
+ {Array.from({ length: tickCount }).map((_, index) => (
177
+ <span
178
+ className="block size-[var(--bh-progress-slider-tick-size)] rounded-[var(--bh-radius-full)] bg-[var(--bh-progress-slider-tick-bg)]"
179
+ key={index}
180
+ />
181
+ ))}
182
+ </span>
183
+ ) : null}
184
+ </SliderPrimitive.Track>
185
+
186
+ <SliderPrimitive.Thumb
187
+ aria-label={props["aria-label"]}
188
+ aria-valuetext={getProgressAriaValueText(ariaValueText, currentValue)}
189
+ className="relative z-[var(--bh-z-overlay)] flex h-[var(--bh-progress-slider-height-current)] w-[var(--bh-progress-slider-handle-width-current)] shrink-0 items-center justify-center rounded-[var(--bh-progress-slider-radius)] bg-transparent outline-none transition-[box-shadow] focus-visible:shadow-[var(--shadow-progress-slider-thumb-focus)]"
190
+ data-slot="progress-slider-thumb"
191
+ >
192
+ <span
193
+ aria-hidden="true"
194
+ className="pointer-events-none block h-[var(--bh-progress-slider-grip-height-current)] w-[var(--bh-progress-slider-grip-width)] rounded-[var(--bh-radius-full)] bg-[var(--bh-progress-slider-grip-bg)] group-data-[disabled]/progress-slider:bg-[var(--bh-content-disabled)]"
195
+ data-slot="progress-slider-grip"
196
+ />
197
+ </SliderPrimitive.Thumb>
198
+ </SliderPrimitive.Root>
199
+ )
200
+ }
201
+
202
+ function normalizeProgressValue(value: number | undefined, min: number, max: number) {
203
+ const nextValue = Number.isFinite(value) ? Number(value) : min
204
+ return Math.min(Math.max(nextValue, min), max)
205
+ }
206
+
207
+ function getProgressAriaValueText(
208
+ ariaValueText: ProgressSliderProps["ariaValueText"],
209
+ value: number
210
+ ) {
211
+ if (typeof ariaValueText === "function") return ariaValueText(value)
212
+ return ariaValueText
213
+ }
214
+
215
+ export { ProgressSlider, progressSliderVariants }
216
+ export type { ProgressSliderProps, ProgressSliderSize, ProgressSliderTone }
@@ -0,0 +1,367 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { InfoIcon, LoaderCircleIcon } from "lucide-react"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ type ProgressSize = "sm" | "lg"
10
+ type ProgressLabelPosition = "top" | "bottom" | "none" | "inline"
11
+
12
+ const progressVariants = cva(
13
+ [
14
+ "group/progress flex w-full min-w-0 text-[var(--bh-content-default)]",
15
+ "[--bh-progress-track-height-current:var(--bh-space-md-8)]",
16
+ "[--bh-progress-indicator-size:var(--bh-space-3xl-16)]",
17
+ "[--bh-progress-gap:var(--bh-space-sm-6)]",
18
+ ],
19
+ {
20
+ variants: {
21
+ size: {
22
+ sm: "[--bh-progress-track-height-current:var(--bh-space-xs-4)]",
23
+ lg: "[--bh-progress-track-height-current:var(--bh-space-md-8)]",
24
+ },
25
+ labelPosition: {
26
+ top: "flex-col items-start gap-[var(--bh-progress-gap)]",
27
+ bottom: "flex-col items-start gap-[var(--bh-progress-gap)]",
28
+ none: "flex-col items-start gap-[var(--bh-progress-gap)]",
29
+ inline: "items-center gap-[var(--bh-progress-gap)]",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ size: "lg",
34
+ labelPosition: "top",
35
+ },
36
+ }
37
+ )
38
+
39
+ const progressTrackVariants = cva(
40
+ "relative w-full min-w-0 overflow-hidden rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-soft)]",
41
+ {
42
+ variants: {
43
+ size: {
44
+ sm: "h-[var(--bh-space-xs-4)]",
45
+ lg: "h-[var(--bh-space-md-8)]",
46
+ },
47
+ },
48
+ defaultVariants: {
49
+ size: "lg",
50
+ },
51
+ }
52
+ )
53
+
54
+ type ProgressProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> &
55
+ VariantProps<typeof progressVariants> & {
56
+ getValueLabel?: (value: number, percent: number) => string
57
+ helperText?: React.ReactNode
58
+ indicator?: React.ReactNode
59
+ infoLabel?: string
60
+ label?: React.ReactNode
61
+ max?: number
62
+ min?: number
63
+ optional?: React.ReactNode
64
+ showIndicator?: boolean
65
+ showInfo?: boolean
66
+ showSpinner?: boolean
67
+ value?: number
68
+ }
69
+
70
+ const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(function Progress({
71
+ "aria-describedby": ariaDescribedBy,
72
+ "aria-label": ariaLabel,
73
+ "aria-labelledby": ariaLabelledBy,
74
+ className,
75
+ getValueLabel,
76
+ helperText,
77
+ indicator,
78
+ infoLabel = "More information",
79
+ label,
80
+ labelPosition,
81
+ max = 100,
82
+ min = 0,
83
+ optional,
84
+ showIndicator,
85
+ showInfo = false,
86
+ showSpinner = false,
87
+ size,
88
+ value = 0,
89
+ ...props
90
+ }, ref) {
91
+ const selectedLabelPosition = labelPosition ?? "top"
92
+ const selectedSize = size ?? "lg"
93
+ const labelId = React.useId()
94
+ const helperId = React.useId()
95
+ const { clampedValue, maxValue, minValue, percent } = getProgressValue({
96
+ max,
97
+ min,
98
+ value,
99
+ })
100
+ const roundedPercent = Math.round(percent)
101
+ const valueLabel = getValueLabel?.(clampedValue, percent)
102
+ const shouldShowIndicator =
103
+ showIndicator ?? selectedLabelPosition !== "none"
104
+ const visibleIndicator = indicator ?? `${roundedPercent}%`
105
+ const hasLabel = hasRenderableContent(label)
106
+ const hasOptional = hasRenderableContent(optional)
107
+ const hasHelperText = hasRenderableContent(helperText)
108
+ const hasHeader =
109
+ selectedLabelPosition !== "none" &&
110
+ selectedLabelPosition !== "inline" &&
111
+ (hasLabel || hasOptional || showInfo || shouldShowIndicator)
112
+ const labelledBy =
113
+ ariaLabelledBy ?? (hasLabel && !ariaLabel ? labelId : undefined)
114
+ const describedBy =
115
+ [ariaDescribedBy, hasHelperText ? helperId : undefined]
116
+ .filter(Boolean)
117
+ .join(" ") || undefined
118
+
119
+ return (
120
+ <div
121
+ data-label-position={selectedLabelPosition}
122
+ data-size={selectedSize}
123
+ data-slot="progress"
124
+ ref={ref}
125
+ className={cn(
126
+ progressVariants({
127
+ labelPosition: selectedLabelPosition,
128
+ size: selectedSize,
129
+ }),
130
+ className
131
+ )}
132
+ {...props}
133
+ >
134
+ {selectedLabelPosition === "top" && hasHeader ? (
135
+ <ProgressHeader
136
+ indicator={visibleIndicator}
137
+ infoLabel={infoLabel}
138
+ label={label}
139
+ labelId={labelId}
140
+ optional={optional}
141
+ showIndicator={shouldShowIndicator}
142
+ showInfo={showInfo}
143
+ showSpinner={showSpinner}
144
+ />
145
+ ) : null}
146
+
147
+ <ProgressTrack
148
+ ariaDescribedBy={describedBy}
149
+ ariaLabel={ariaLabel}
150
+ ariaLabelledBy={labelledBy}
151
+ max={maxValue}
152
+ min={minValue}
153
+ percent={percent}
154
+ size={selectedSize}
155
+ value={clampedValue}
156
+ valueLabel={valueLabel}
157
+ />
158
+
159
+ {selectedLabelPosition === "bottom" && hasHeader ? (
160
+ <ProgressHeader
161
+ indicator={visibleIndicator}
162
+ infoLabel={infoLabel}
163
+ label={label}
164
+ labelId={labelId}
165
+ optional={optional}
166
+ showIndicator={shouldShowIndicator}
167
+ showInfo={showInfo}
168
+ showSpinner={showSpinner}
169
+ />
170
+ ) : null}
171
+
172
+ {selectedLabelPosition === "inline" && shouldShowIndicator ? (
173
+ <ProgressIndicator
174
+ indicator={visibleIndicator}
175
+ showSpinner={showSpinner}
176
+ />
177
+ ) : null}
178
+
179
+ {hasHelperText ? (
180
+ <p
181
+ data-slot="progress-helper-text"
182
+ dir="auto"
183
+ id={helperId}
184
+ className="m-0 w-full 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-subtle)]"
185
+ >
186
+ {helperText}
187
+ </p>
188
+ ) : null}
189
+ </div>
190
+ )
191
+ })
192
+
193
+ Progress.displayName = "Progress"
194
+
195
+ function ProgressHeader({
196
+ indicator,
197
+ infoLabel,
198
+ label,
199
+ labelId,
200
+ optional,
201
+ showIndicator,
202
+ showInfo,
203
+ showSpinner,
204
+ }: {
205
+ indicator: React.ReactNode
206
+ infoLabel: string
207
+ label: React.ReactNode
208
+ labelId: string
209
+ optional: React.ReactNode
210
+ showIndicator: boolean
211
+ showInfo: boolean
212
+ showSpinner: boolean
213
+ }) {
214
+ return (
215
+ <div
216
+ data-slot="progress-header"
217
+ className="flex w-full min-w-0 items-center gap-[var(--bh-space-md-8)]"
218
+ >
219
+ <span
220
+ data-slot="progress-label-group"
221
+ className="flex min-w-0 flex-1 items-center gap-[var(--bh-space-xs-4)]"
222
+ >
223
+ {hasRenderableContent(label) ? (
224
+ <span
225
+ data-slot="progress-label"
226
+ dir="auto"
227
+ id={labelId}
228
+ className="min-w-0 truncate text-start text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)] leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] text-[var(--bh-content-default)]"
229
+ >
230
+ {label}
231
+ </span>
232
+ ) : null}
233
+ {hasRenderableContent(optional) ? (
234
+ <span
235
+ data-slot="progress-optional"
236
+ dir="auto"
237
+ className="order-2 min-w-0 truncate 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)] text-[var(--bh-content-subtle)] rtl:order-3"
238
+ >
239
+ {optional}
240
+ </span>
241
+ ) : null}
242
+ {showInfo ? (
243
+ <span
244
+ aria-label={infoLabel}
245
+ data-slot="progress-info"
246
+ role="img"
247
+ title={infoLabel}
248
+ className="order-3 inline-flex size-[var(--bh-progress-indicator-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] rtl:order-2 [&_svg]:size-[var(--bh-progress-indicator-size)]"
249
+ >
250
+ <InfoIcon aria-hidden="true" />
251
+ </span>
252
+ ) : null}
253
+ </span>
254
+
255
+ {showIndicator ? (
256
+ <ProgressIndicator indicator={indicator} showSpinner={showSpinner} />
257
+ ) : null}
258
+ </div>
259
+ )
260
+ }
261
+
262
+ function ProgressTrack({
263
+ ariaDescribedBy,
264
+ ariaLabel,
265
+ ariaLabelledBy,
266
+ max,
267
+ min,
268
+ percent,
269
+ size,
270
+ value,
271
+ valueLabel,
272
+ }: {
273
+ ariaDescribedBy?: string
274
+ ariaLabel?: string
275
+ ariaLabelledBy?: string
276
+ max: number
277
+ min: number
278
+ percent: number
279
+ size: ProgressSize
280
+ value: number
281
+ valueLabel?: string
282
+ }) {
283
+ return (
284
+ <div
285
+ aria-describedby={ariaDescribedBy}
286
+ aria-label={ariaLabel}
287
+ aria-labelledby={ariaLabelledBy}
288
+ aria-valuemax={max}
289
+ aria-valuemin={min}
290
+ aria-valuenow={value}
291
+ aria-valuetext={valueLabel}
292
+ data-slot="progress-track"
293
+ role="progressbar"
294
+ className={progressTrackVariants({ size })}
295
+ >
296
+ <span
297
+ data-slot="progress-fill"
298
+ className="absolute inset-y-0 start-0 block min-w-[var(--bh-progress-track-height-current)] rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-indicator-default)]"
299
+ style={{ inlineSize: `${percent}%` }}
300
+ />
301
+ </div>
302
+ )
303
+ }
304
+
305
+ function ProgressIndicator({
306
+ indicator,
307
+ showSpinner,
308
+ }: {
309
+ indicator: React.ReactNode
310
+ showSpinner: boolean
311
+ }) {
312
+ return (
313
+ <span
314
+ aria-hidden="true"
315
+ data-slot="progress-indicator"
316
+ className="flex shrink-0 items-center gap-[var(--bh-space-xxs-2)] 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-subtle)]"
317
+ >
318
+ {hasRenderableContent(indicator) ? (
319
+ <span data-slot="progress-indicator-value" dir="auto">
320
+ {indicator}
321
+ </span>
322
+ ) : null}
323
+ {showSpinner ? (
324
+ <LoaderCircleIcon
325
+ data-slot="progress-indicator-spinner"
326
+ className="size-[var(--bh-progress-indicator-size)] animate-spin"
327
+ />
328
+ ) : null}
329
+ </span>
330
+ )
331
+ }
332
+
333
+ function getProgressValue({
334
+ max,
335
+ min,
336
+ value,
337
+ }: {
338
+ max: number
339
+ min: number
340
+ value: number
341
+ }) {
342
+ const minValue = Number.isFinite(min) ? Number(min) : 0
343
+ const resolvedMax = Number.isFinite(max) ? Number(max) : 100
344
+ const maxValue = resolvedMax > minValue ? resolvedMax : minValue + 100
345
+ const numericValue = Number.isFinite(value) ? Number(value) : minValue
346
+ const clampedValue = Math.min(maxValue, Math.max(minValue, numericValue))
347
+ const percent = ((clampedValue - minValue) / (maxValue - minValue)) * 100
348
+
349
+ return {
350
+ clampedValue,
351
+ maxValue,
352
+ minValue,
353
+ percent: Math.min(100, Math.max(0, percent)),
354
+ }
355
+ }
356
+
357
+ function hasRenderableContent(content: React.ReactNode) {
358
+ return (
359
+ content !== undefined &&
360
+ content !== null &&
361
+ content !== false &&
362
+ content !== ""
363
+ )
364
+ }
365
+
366
+ export { Progress, progressTrackVariants, progressVariants }
367
+ export type { ProgressLabelPosition, ProgressProps, ProgressSize }