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,758 @@
1
+ import * as React from "react"
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ type MenuWidth = "menu" | "default" | "multiline" | "action" | "auto"
9
+ type MenuItemKind = "default" | "multiline" | "action" | "progress" | "button"
10
+ type MenuItemState = "default" | "disabled"
11
+ type MenuItemIconPosition = "leading" | "trailing"
12
+ type MenuItemAvatarSize = "sm" | "lg"
13
+
14
+ const menuVariants = cva(
15
+ "flex flex-col items-start gap-[var(--bh-menu-gap)] overflow-hidden rounded-[var(--bh-menu-radius)] bg-[var(--bh-menu-bg)] py-[var(--bh-menu-padding-y)] shadow-[var(--shadow-menu)]",
16
+ {
17
+ variants: {
18
+ width: {
19
+ menu: "w-[var(--bh-menu-width)]",
20
+ default: "w-[var(--bh-menu-item-default-width)]",
21
+ multiline: "w-[var(--bh-menu-item-multiline-width)]",
22
+ action: "w-[var(--bh-menu-item-action-width)]",
23
+ auto: "w-max",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ width: "menu",
28
+ },
29
+ }
30
+ )
31
+
32
+ const menuItemVariants = cva(
33
+ "group/menu-item relative flex w-full items-center bg-[var(--bh-menu-item-bg)] outline-none transition-colors data-[disabled=true]:pointer-events-none",
34
+ {
35
+ variants: {
36
+ kind: {
37
+ default:
38
+ "h-[var(--bh-menu-item-default-height)] px-[var(--bh-menu-item-padding-x)]",
39
+ multiline:
40
+ "min-h-[var(--bh-menu-item-multiline-height)] px-[var(--bh-menu-item-padding-x)]",
41
+ action:
42
+ "min-h-[var(--bh-menu-item-action-height)] px-[var(--bh-menu-item-padding-x)]",
43
+ progress:
44
+ "h-[var(--bh-menu-item-progress-height)] px-[var(--bh-menu-item-padding-x)]",
45
+ button:
46
+ "h-[var(--bh-menu-item-button-height)] px-[var(--bh-menu-item-padding-x)]",
47
+ },
48
+ },
49
+ defaultVariants: {
50
+ kind: "default",
51
+ },
52
+ }
53
+ )
54
+
55
+ const menuItemContentVariants = cva(
56
+ "flex min-w-0 flex-1 transition-colors group-data-[disabled=true]/menu-item:bg-transparent [&_svg]:shrink-0 rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
57
+ {
58
+ variants: {
59
+ kind: {
60
+ default:
61
+ "h-[var(--bh-menu-item-default-height)] items-center gap-[var(--bh-menu-item-gap)] rounded-[var(--bh-menu-item-radius)] p-[var(--bh-menu-item-content-padding)] group-hover/menu-item:bg-[var(--bh-menu-item-hover-bg)]",
62
+ multiline:
63
+ "min-h-[var(--bh-menu-item-multiline-height)] items-center gap-[var(--bh-menu-item-gap)] rounded-[var(--bh-menu-item-radius)] px-[var(--bh-menu-item-content-padding)] py-[var(--bh-menu-item-content-compact-padding-y)] group-hover/menu-item:bg-[var(--bh-menu-item-hover-bg)]",
64
+ action:
65
+ "min-h-[var(--bh-menu-item-action-height)] items-center gap-[var(--bh-menu-item-gap)] rounded-[var(--bh-menu-item-radius)] px-[var(--bh-menu-item-content-padding)] py-[var(--bh-menu-item-content-compact-padding-y)] group-hover/menu-item:bg-[var(--bh-menu-item-hover-bg)]",
66
+ progress:
67
+ "h-[var(--bh-menu-item-progress-height)] flex-col items-start gap-0 px-[var(--bh-menu-item-content-padding)] py-[var(--bh-menu-item-content-compact-padding-y)]",
68
+ button:
69
+ "h-[var(--bh-menu-item-button-height)] flex-col items-start gap-[var(--bh-menu-item-slot-gap)] px-[var(--bh-menu-item-content-padding)] py-[var(--bh-menu-item-content-compact-padding-y)]",
70
+ },
71
+ },
72
+ defaultVariants: {
73
+ kind: "default",
74
+ },
75
+ }
76
+ )
77
+
78
+ const menuItemIconVariants = cva(
79
+ "flex shrink-0 items-center justify-center text-[var(--bh-content-subtle)] group-data-[disabled=true]/menu-item:text-[var(--bh-content-disabled)]",
80
+ {
81
+ variants: {
82
+ position: {
83
+ leading:
84
+ "h-[var(--bh-menu-leading-icon-size)] ps-[var(--bh-menu-icon-padding)] [&_svg]:size-[var(--bh-menu-leading-icon-size)]",
85
+ trailing:
86
+ "size-[var(--bh-menu-trailing-icon-size)] text-[var(--bh-content-muted)] [&_svg]:size-[var(--bh-menu-trailing-icon-size)]",
87
+ },
88
+ },
89
+ defaultVariants: {
90
+ position: "leading",
91
+ },
92
+ }
93
+ )
94
+
95
+ type MenuProps = React.ComponentProps<"div"> &
96
+ VariantProps<typeof menuVariants> & {
97
+ asChild?: boolean
98
+ defaultOpen?: boolean
99
+ modal?: boolean
100
+ onOpenChange?: (open: boolean) => void
101
+ open?: boolean
102
+ }
103
+
104
+ type MenuContentProps = React.ComponentProps<typeof DropdownMenuPrimitive.Content> &
105
+ VariantProps<typeof menuVariants> & {
106
+ asChild?: boolean
107
+ }
108
+
109
+ const MenuRoot = DropdownMenuPrimitive.Root
110
+ const MenuTrigger = DropdownMenuPrimitive.Trigger
111
+
112
+ const MenuContent = React.forwardRef<HTMLDivElement, MenuContentProps>(function MenuContent({
113
+ className,
114
+ width,
115
+ asChild = false,
116
+ role = "menu",
117
+ ...props
118
+ }, ref) {
119
+ const Comp = asChild ? Slot : "div"
120
+
121
+ return (
122
+ <DropdownMenuPrimitive.Content asChild {...props}>
123
+ <Comp
124
+ data-slot="menu"
125
+ data-width={width ?? "menu"}
126
+ ref={ref}
127
+ role={role}
128
+ className={cn(menuVariants({ width, className }))}
129
+ />
130
+ </DropdownMenuPrimitive.Content>
131
+ )
132
+ })
133
+
134
+ function Menu({
135
+ children,
136
+ defaultOpen = true,
137
+ modal = false,
138
+ onOpenChange,
139
+ open,
140
+ ...props
141
+ }: MenuProps) {
142
+ const rootProps =
143
+ open === undefined
144
+ ? { defaultOpen, onOpenChange }
145
+ : { open, onOpenChange }
146
+
147
+ return (
148
+ <MenuRoot modal={modal} {...rootProps}>
149
+ <MenuContent forceMount {...props}>
150
+ {children}
151
+ </MenuContent>
152
+ </MenuRoot>
153
+ )
154
+ }
155
+
156
+ const MenuPortal = DropdownMenuPrimitive.Portal
157
+
158
+ function getMenuItemTextValue(children: React.ReactNode) {
159
+ if (typeof children === "string" || typeof children === "number") {
160
+ return String(children)
161
+ }
162
+
163
+ return undefined
164
+ }
165
+
166
+ type MenuItemPrimitiveProps = Pick<
167
+ React.ComponentProps<typeof DropdownMenuPrimitive.Item>,
168
+ "onSelect" | "textValue"
169
+ >
170
+
171
+ type MenuItemProps = React.ComponentProps<"div"> &
172
+ VariantProps<typeof menuItemVariants> &
173
+ MenuItemPrimitiveProps & {
174
+ asChild?: boolean
175
+ disabled?: boolean
176
+ }
177
+
178
+ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuItem({
179
+ className,
180
+ kind = "default",
181
+ disabled = false,
182
+ asChild = false,
183
+ role = "menuitem",
184
+ tabIndex,
185
+ children,
186
+ onSelect,
187
+ textValue,
188
+ ...props
189
+ }, ref) {
190
+ const Comp = asChild ? Slot : "div"
191
+ const selectedKind: MenuItemKind = kind ?? "default"
192
+ const isDisabled = disabled
193
+
194
+ return (
195
+ <DropdownMenuPrimitive.Item
196
+ asChild
197
+ disabled={isDisabled}
198
+ onSelect={onSelect}
199
+ textValue={textValue ?? getMenuItemTextValue(children)}
200
+ >
201
+ <Comp
202
+ aria-disabled={isDisabled ? true : undefined}
203
+ data-disabled={isDisabled ? "true" : "false"}
204
+ data-kind={selectedKind}
205
+ data-slot="menu-item"
206
+ ref={ref}
207
+ role={role}
208
+ tabIndex={isDisabled ? -1 : (tabIndex ?? 0)}
209
+ className={cn(menuItemVariants({ kind: selectedKind, className }))}
210
+ {...props}
211
+ >
212
+ <MenuItemContent kind={selectedKind}>
213
+ <MenuItemChildren kind={selectedKind}>{children}</MenuItemChildren>
214
+ </MenuItemContent>
215
+ </Comp>
216
+ </DropdownMenuPrimitive.Item>
217
+ )
218
+ })
219
+
220
+ MenuItem.displayName = "MenuItem"
221
+
222
+ type MenuItemContentProps = React.ComponentProps<"div"> &
223
+ VariantProps<typeof menuItemContentVariants>
224
+
225
+ function MenuItemContent({
226
+ className,
227
+ kind,
228
+ ...props
229
+ }: MenuItemContentProps) {
230
+ return (
231
+ <div
232
+ data-slot="menu-item-content"
233
+ className={cn(menuItemContentVariants({ kind, className }))}
234
+ {...props}
235
+ />
236
+ )
237
+ }
238
+
239
+ function MenuItemChildren({
240
+ children,
241
+ kind,
242
+ }: {
243
+ children: React.ReactNode
244
+ kind: MenuItemKind
245
+ }) {
246
+ return React.Children.map(children, (child) => {
247
+ if (kind === "button" && (typeof child === "string" || typeof child === "number")) {
248
+ return <MenuItemAction fullWidth>{child}</MenuItemAction>
249
+ }
250
+
251
+ if (typeof child === "string" || typeof child === "number") {
252
+ return (
253
+ <MenuItemText>
254
+ <MenuItemTitle>{child}</MenuItemTitle>
255
+ </MenuItemText>
256
+ )
257
+ }
258
+
259
+ return child
260
+ })
261
+ }
262
+
263
+ function MenuItemText({
264
+ className,
265
+ ...props
266
+ }: React.ComponentProps<"span">) {
267
+ return (
268
+ <span
269
+ data-slot="menu-item-text"
270
+ className={cn(
271
+ "flex min-w-0 flex-1 flex-col ps-[var(--bh-space-xs-4)]",
272
+ className
273
+ )}
274
+ {...props}
275
+ />
276
+ )
277
+ }
278
+
279
+ function MenuItemTitle({
280
+ className,
281
+ dir = "auto",
282
+ ...props
283
+ }: React.ComponentProps<"span">) {
284
+ return (
285
+ <span
286
+ data-slot="menu-item-title"
287
+ dir={dir}
288
+ className={cn(
289
+ "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]/menu-item:text-[var(--bh-content-disabled)]",
290
+ className
291
+ )}
292
+ {...props}
293
+ />
294
+ )
295
+ }
296
+
297
+ function MenuItemDescription({
298
+ className,
299
+ dir = "auto",
300
+ ...props
301
+ }: React.ComponentProps<"span">) {
302
+ return (
303
+ <span
304
+ data-slot="menu-item-description"
305
+ dir={dir}
306
+ className={cn(
307
+ "min-w-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-subtle)] group-data-[disabled=true]/menu-item:text-[var(--bh-content-disabled)]",
308
+ className
309
+ )}
310
+ {...props}
311
+ />
312
+ )
313
+ }
314
+
315
+ function MenuItemMeta({
316
+ className,
317
+ dir = "auto",
318
+ ...props
319
+ }: React.ComponentProps<"span">) {
320
+ return (
321
+ <span
322
+ data-slot="menu-item-meta"
323
+ dir={dir}
324
+ className={cn(
325
+ "shrink-0 whitespace-nowrap text-end 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)] group-data-[disabled=true]/menu-item:text-[var(--bh-content-disabled)]",
326
+ className
327
+ )}
328
+ {...props}
329
+ />
330
+ )
331
+ }
332
+
333
+ type MenuItemIconProps = React.ComponentProps<"span"> &
334
+ VariantProps<typeof menuItemIconVariants>
335
+
336
+ function MenuItemIcon({
337
+ className,
338
+ position,
339
+ ...props
340
+ }: MenuItemIconProps) {
341
+ return (
342
+ <span
343
+ aria-hidden="true"
344
+ data-position={position ?? "leading"}
345
+ data-slot="menu-item-icon"
346
+ className={cn(menuItemIconVariants({ position, className }))}
347
+ {...props}
348
+ />
349
+ )
350
+ }
351
+
352
+ type MenuItemAvatarProps = React.ComponentProps<"span"> & {
353
+ alt?: string
354
+ size?: MenuItemAvatarSize
355
+ src?: string
356
+ }
357
+
358
+ function MenuItemAvatar({
359
+ alt = "",
360
+ children,
361
+ className,
362
+ size = "sm",
363
+ src,
364
+ ...props
365
+ }: MenuItemAvatarProps) {
366
+ return (
367
+ <span
368
+ data-size={size}
369
+ data-slot="menu-item-avatar-wrap"
370
+ className={cn(
371
+ "flex shrink-0 items-center ps-[var(--bh-menu-icon-padding)] group-data-[disabled=true]/menu-item:opacity-[var(--bh-opacity-60)]",
372
+ size === "lg"
373
+ ? "h-[var(--bh-space-7xl-40)]"
374
+ : "h-[var(--bh-space-5xl-24)]",
375
+ className
376
+ )}
377
+ {...props}
378
+ >
379
+ <span
380
+ data-slot="menu-item-avatar"
381
+ className={cn(
382
+ "flex shrink-0 items-center justify-center overflow-hidden rounded-[var(--bh-radius-full)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-neutral-subtle)] text-[length:var(--bh-text-body-3xs-medium-font-size)] font-[var(--bh-text-body-3xs-medium-font-weight)] leading-[var(--bh-text-body-3xs-medium-line-height)] text-[var(--bh-content-subtle)]",
383
+ size === "lg"
384
+ ? "size-[var(--bh-space-7xl-40)]"
385
+ : "size-[var(--bh-space-5xl-24)]"
386
+ )}
387
+ >
388
+ {src ? (
389
+ <img alt={alt} className="size-full object-cover" src={src} />
390
+ ) : (
391
+ children
392
+ )}
393
+ </span>
394
+ </span>
395
+ )
396
+ }
397
+
398
+ function MenuItemBadge({
399
+ className,
400
+ dir = "auto",
401
+ ...props
402
+ }: React.ComponentProps<"span">) {
403
+ return (
404
+ <span
405
+ data-slot="menu-item-badge"
406
+ dir={dir}
407
+ className={cn(
408
+ "inline-flex h-[var(--bh-menu-badge-height)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-subtle)] px-[var(--bh-menu-badge-padding-x)] text-center text-[length:var(--bh-text-body-3xs-medium-font-size)] font-[var(--bh-text-body-3xs-medium-font-weight)] leading-[var(--bh-text-body-3xs-medium-line-height)] tracking-[var(--bh-text-body-3xs-medium-letter-spacing)] text-[var(--bh-content-default)] group-data-[disabled=true]/menu-item:opacity-[var(--bh-opacity-60)]",
409
+ className
410
+ )}
411
+ {...props}
412
+ />
413
+ )
414
+ }
415
+
416
+ type MenuItemSwitchProps = Omit<React.ComponentProps<"span">, "role"> & {
417
+ active?: boolean
418
+ disabled?: boolean
419
+ }
420
+
421
+ function MenuItemSwitch({
422
+ active = false,
423
+ className,
424
+ disabled = false,
425
+ ...props
426
+ }: MenuItemSwitchProps) {
427
+ return (
428
+ <span
429
+ aria-checked={active}
430
+ aria-disabled={disabled || undefined}
431
+ data-active={active ? "true" : "false"}
432
+ data-disabled={disabled ? "true" : "false"}
433
+ data-slot="menu-item-switch"
434
+ role="switch"
435
+ className={cn(
436
+ "flex h-[var(--bh-menu-switch-height)] w-[var(--bh-menu-switch-width)] shrink-0 items-center rounded-[var(--bh-radius-full)] p-[var(--bh-menu-switch-padding)] group-data-[disabled=true]/menu-item:bg-[var(--bh-interactive-switch-disabled)]",
437
+ active
438
+ ? "justify-end bg-[var(--bh-interactive-switch-active)]"
439
+ : "bg-[var(--bh-interactive-switch-default)]",
440
+ disabled && "bg-[var(--bh-interactive-switch-disabled)]",
441
+ className
442
+ )}
443
+ {...props}
444
+ >
445
+ <span
446
+ aria-hidden="true"
447
+ data-slot="menu-item-switch-handle"
448
+ className="size-[var(--bh-menu-switch-handle-size)] rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-switch-handle)] group-data-[disabled=true]/menu-item:bg-[var(--bh-interactive-switch-handle-disabled)]"
449
+ />
450
+ </span>
451
+ )
452
+ }
453
+
454
+ type MenuItemActionProps = React.ComponentProps<"button"> & {
455
+ asChild?: boolean
456
+ fullWidth?: boolean
457
+ }
458
+
459
+ function MenuItemAction({
460
+ className,
461
+ asChild = false,
462
+ fullWidth = false,
463
+ type,
464
+ children,
465
+ ...props
466
+ }: MenuItemActionProps) {
467
+ const Comp = asChild ? Slot : "button"
468
+
469
+ return (
470
+ <Comp
471
+ data-slot="menu-item-action"
472
+ className={cn(
473
+ "inline-flex h-[var(--bh-menu-action-height)] shrink-0 items-center justify-center gap-[var(--bh-menu-action-gap)] rounded-[var(--bh-control-default)] border border-[var(--bh-border-default)] bg-[var(--bh-interactive-outlined-default)] px-[var(--bh-menu-action-padding-x)] py-[var(--bh-space-md-8)] text-[length:var(--bh-text-body-md-medium-font-size)] font-[var(--bh-text-body-md-medium-font-weight)] leading-[var(--bh-text-body-md-medium-line-height)] tracking-[var(--bh-text-body-md-medium-letter-spacing)] text-[var(--bh-content-default)] outline-none transition-[background-color,border-color,box-shadow] hover:bg-[var(--bh-interactive-outlined-hover)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
474
+ fullWidth && "w-full",
475
+ className
476
+ )}
477
+ {...(!asChild ? { type: type || "button" } : {})}
478
+ {...props}
479
+ >
480
+ {asChild ? children : <MenuItemActionChildren>{children}</MenuItemActionChildren>}
481
+ </Comp>
482
+ )
483
+ }
484
+
485
+ function MenuItemActionChildren({ children }: { children: React.ReactNode }) {
486
+ return React.Children.map(children, (child) => {
487
+ if (typeof child === "string" || typeof child === "number") {
488
+ return (
489
+ <span data-slot="menu-item-action-label" dir="auto" className="min-w-0 px-[var(--bh-space-xs-4)]">
490
+ {child}
491
+ </span>
492
+ )
493
+ }
494
+
495
+ return child
496
+ })
497
+ }
498
+
499
+ type MenuProgressProps = React.ComponentProps<"div"> & {
500
+ indicator?: React.ReactNode
501
+ label?: React.ReactNode
502
+ optional?: React.ReactNode
503
+ showSpinner?: boolean
504
+ value?: number
505
+ }
506
+
507
+ function MenuProgress({
508
+ className,
509
+ indicator,
510
+ label,
511
+ optional,
512
+ showSpinner = true,
513
+ value = 50,
514
+ ...props
515
+ }: MenuProgressProps) {
516
+ const clampedValue = Math.min(100, Math.max(0, value))
517
+
518
+ return (
519
+ <div
520
+ data-slot="menu-progress"
521
+ className={cn(
522
+ "flex w-full flex-col gap-[var(--bh-menu-progress-gap)]",
523
+ className
524
+ )}
525
+ {...props}
526
+ >
527
+ <div
528
+ aria-valuemax={100}
529
+ aria-valuemin={0}
530
+ aria-valuenow={clampedValue}
531
+ data-slot="menu-progress-track"
532
+ role="progressbar"
533
+ className="h-[var(--bh-menu-progress-height)] w-full overflow-hidden rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-soft)]"
534
+ >
535
+ <span
536
+ data-slot="menu-progress-fill"
537
+ className="block h-full rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-indicator-default)]"
538
+ style={{ inlineSize: `${clampedValue}%` }}
539
+ />
540
+ </div>
541
+
542
+ {hasRenderableContent(label) ||
543
+ hasRenderableContent(optional) ||
544
+ hasRenderableContent(indicator) ||
545
+ showSpinner ? (
546
+ <div
547
+ data-slot="menu-progress-row"
548
+ className="flex min-w-0 items-center gap-[var(--bh-menu-item-slot-gap)]"
549
+ >
550
+ {hasRenderableContent(label) || hasRenderableContent(optional) ? (
551
+ <span
552
+ data-slot="menu-progress-label"
553
+ className="flex min-w-0 flex-1 items-center gap-[var(--bh-space-xs-4)]"
554
+ >
555
+ {hasRenderableContent(label) ? (
556
+ <span
557
+ data-slot="menu-progress-label-text"
558
+ dir="auto"
559
+ className="truncate text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)] leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] text-[var(--bh-content-default)]"
560
+ >
561
+ {label}
562
+ </span>
563
+ ) : null}
564
+ {hasRenderableContent(optional) ? (
565
+ <span
566
+ data-slot="menu-progress-optional"
567
+ dir="auto"
568
+ className="truncate 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)]"
569
+ >
570
+ {optional}
571
+ </span>
572
+ ) : null}
573
+ </span>
574
+ ) : null}
575
+
576
+ <span
577
+ data-slot="menu-progress-indicator"
578
+ 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)]"
579
+ >
580
+ {hasRenderableContent(indicator) ? (
581
+ <span dir="auto">{indicator}</span>
582
+ ) : null}
583
+ {showSpinner ? <MenuProgressSpinner /> : null}
584
+ </span>
585
+ </div>
586
+ ) : null}
587
+ </div>
588
+ )
589
+ }
590
+
591
+ function MenuProgressSpinner({ className }: { className?: string }) {
592
+ return (
593
+ <svg
594
+ aria-hidden="true"
595
+ data-slot="menu-progress-spinner"
596
+ viewBox="0 0 24 24"
597
+ className={cn("size-[var(--bh-menu-trailing-icon-size)] animate-spin", className)}
598
+ >
599
+ <path
600
+ d="M12 3a9 9 0 1 1-6.36 2.64"
601
+ fill="none"
602
+ stroke="currentColor"
603
+ strokeLinecap="round"
604
+ strokeWidth="3"
605
+ />
606
+ </svg>
607
+ )
608
+ }
609
+
610
+ type MenuLabelProps = React.ComponentProps<"div"> & {
611
+ caption?: React.ReactNode
612
+ label?: React.ReactNode
613
+ }
614
+
615
+ function MenuLabel({
616
+ caption,
617
+ children,
618
+ className,
619
+ label,
620
+ role = "presentation",
621
+ ...props
622
+ }: MenuLabelProps) {
623
+ return (
624
+ <DropdownMenuPrimitive.Label asChild>
625
+ <div
626
+ data-slot="menu-label"
627
+ role={role}
628
+ className={cn(
629
+ "flex h-[var(--bh-menu-item-label-height)] w-full items-center gap-0 bg-[var(--bh-menu-item-bg)] px-[var(--bh-menu-label-padding-x)] py-[var(--bh-space-xs-4)] text-[length:var(--bh-text-body-xs-regular-font-size)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)]",
630
+ className
631
+ )}
632
+ {...props}
633
+ >
634
+ {children ?? (
635
+ <>
636
+ {hasRenderableContent(label) ? (
637
+ <span
638
+ data-slot="menu-label-primary"
639
+ dir="auto"
640
+ className="min-w-0 flex-1 truncate text-start font-[var(--bh-text-body-xs-medium-font-weight)] text-[var(--bh-content-default)]"
641
+ >
642
+ {label}
643
+ </span>
644
+ ) : null}
645
+ {hasRenderableContent(caption) ? (
646
+ <span
647
+ data-slot="menu-label-caption"
648
+ dir="auto"
649
+ className="shrink-0 whitespace-nowrap text-end font-[var(--bh-text-body-xs-regular-font-weight)] text-[var(--bh-content-subtle)]"
650
+ >
651
+ {caption}
652
+ </span>
653
+ ) : null}
654
+ </>
655
+ )}
656
+ </div>
657
+ </DropdownMenuPrimitive.Label>
658
+ )
659
+ }
660
+
661
+ function hasRenderableContent(content: React.ReactNode) {
662
+ return (
663
+ content !== undefined &&
664
+ content !== null &&
665
+ content !== false &&
666
+ content !== ""
667
+ )
668
+ }
669
+
670
+ function MenuCaption({
671
+ className,
672
+ dir = "auto",
673
+ role = "presentation",
674
+ ...props
675
+ }: React.ComponentProps<"div">) {
676
+ return (
677
+ <div
678
+ data-slot="menu-caption"
679
+ role={role}
680
+ className="flex h-[var(--bh-menu-item-caption-height)] w-full items-center px-[var(--bh-menu-item-padding-x)]"
681
+ >
682
+ <span
683
+ dir={dir}
684
+ className={cn(
685
+ "flex h-full min-w-0 flex-1 items-center justify-center px-[var(--bh-menu-item-content-padding)] py-[var(--bh-menu-item-content-compact-padding-y)] text-center 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)]",
686
+ className
687
+ )}
688
+ {...props}
689
+ />
690
+ </div>
691
+ )
692
+ }
693
+
694
+ function MenuSeparator({
695
+ className,
696
+ role = "separator",
697
+ ...props
698
+ }: React.ComponentProps<"div">) {
699
+ return (
700
+ <DropdownMenuPrimitive.Separator asChild>
701
+ <div
702
+ data-slot="menu-separator"
703
+ role={role}
704
+ className={cn(
705
+ "flex h-[var(--bh-menu-item-divider-height)] w-full items-center py-[var(--bh-menu-divider-padding-y)]",
706
+ className
707
+ )}
708
+ {...props}
709
+ >
710
+ <span className="block w-full border-t border-[var(--bh-border-subtle)]" />
711
+ </div>
712
+ </DropdownMenuPrimitive.Separator>
713
+ )
714
+ }
715
+
716
+ export {
717
+ Menu,
718
+ MenuCaption,
719
+ MenuContent,
720
+ MenuItem,
721
+ MenuItemAction,
722
+ MenuItemAvatar,
723
+ MenuItemBadge,
724
+ MenuItemContent,
725
+ MenuItemDescription,
726
+ MenuItemIcon,
727
+ MenuItemMeta,
728
+ MenuItemSwitch,
729
+ MenuItemText,
730
+ MenuItemTitle,
731
+ MenuLabel,
732
+ MenuProgress,
733
+ MenuPortal,
734
+ MenuRoot,
735
+ MenuSeparator,
736
+ MenuTrigger,
737
+ menuItemContentVariants,
738
+ menuItemIconVariants,
739
+ menuItemVariants,
740
+ menuVariants,
741
+ }
742
+ export type {
743
+ MenuItemAvatarSize,
744
+ MenuItemAvatarProps,
745
+ MenuItemActionProps,
746
+ MenuContentProps,
747
+ MenuItemContentProps,
748
+ MenuItemIconPosition,
749
+ MenuItemIconProps,
750
+ MenuItemKind,
751
+ MenuItemProps,
752
+ MenuItemSwitchProps,
753
+ MenuItemState,
754
+ MenuLabelProps,
755
+ MenuProgressProps,
756
+ MenuProps,
757
+ MenuWidth,
758
+ }