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,613 @@
1
+ import * as React from "react"
2
+ import { MoreHorizontalIcon } from "lucide-react"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { Button, type ButtonProps } from "./button"
7
+
8
+ type CardOrientation = "vertical" | "horizontal"
9
+ type CardType = "leading-image" | "trailing-image" | "full-image"
10
+ type CardBreakpoint = "desktop" | "mobile"
11
+
12
+ const cardVariants = cva(
13
+ [
14
+ "group/card relative flex min-w-0 max-w-full overflow-hidden",
15
+ "rounded-[var(--bh-card-radius)] bg-[var(--bh-card-bg)] text-start",
16
+ "text-[var(--bh-content-default)] shadow-[inset_0_0_0_var(--bh-border-width-default)_var(--bh-card-border)]",
17
+ "[--bh-card-bg:var(--bh-bg-raised)]",
18
+ "[--bh-card-border:var(--bh-border-subtle)]",
19
+ "[--bh-card-radius:var(--bh-radius-3xl-16)]",
20
+ ],
21
+ {
22
+ variants: {
23
+ orientation: {
24
+ vertical: "flex-col",
25
+ horizontal: "items-stretch",
26
+ },
27
+ type: {
28
+ "leading-image": "",
29
+ "trailing-image": "",
30
+ "full-image": "",
31
+ },
32
+ breakpoint: {
33
+ desktop: [
34
+ "[--bh-card-content-padding:var(--bh-card-desktop-content-padding)]",
35
+ "[--bh-card-horizontal-padding:var(--bh-card-desktop-horizontal-padding)]",
36
+ "[--bh-card-media-height:var(--bh-card-desktop-media-height)]",
37
+ "[--bh-card-full-height:var(--bh-card-desktop-full-height)]",
38
+ ],
39
+ mobile: [
40
+ "[--bh-card-content-padding:var(--bh-card-mobile-content-padding)]",
41
+ "[--bh-card-horizontal-padding:var(--bh-card-mobile-horizontal-padding)]",
42
+ "[--bh-card-media-height:var(--bh-card-mobile-media-height)]",
43
+ "[--bh-card-full-height:var(--bh-card-mobile-full-height)]",
44
+ ],
45
+ },
46
+ },
47
+ compoundVariants: [
48
+ {
49
+ orientation: "vertical",
50
+ class: "w-[min(100%,var(--bh-card-vertical-width))]",
51
+ },
52
+ {
53
+ orientation: "vertical",
54
+ type: "full-image",
55
+ class: "h-[var(--bh-card-full-height)]",
56
+ },
57
+ {
58
+ orientation: "horizontal",
59
+ breakpoint: "desktop",
60
+ class:
61
+ "min-h-[var(--bh-card-horizontal-min-height)] w-[min(100%,var(--bh-card-horizontal-width))]",
62
+ },
63
+ {
64
+ orientation: "horizontal",
65
+ breakpoint: "mobile",
66
+ class: "w-[min(100%,var(--bh-card-vertical-width))] flex-col",
67
+ },
68
+ ],
69
+ defaultVariants: {
70
+ orientation: "vertical",
71
+ type: "leading-image",
72
+ breakpoint: "desktop",
73
+ },
74
+ }
75
+ )
76
+
77
+ type CardProps = Omit<React.ComponentProps<"article">, "title"> &
78
+ VariantProps<typeof cardVariants> & {
79
+ actions?: React.ReactNode
80
+ description?: React.ReactNode
81
+ footer?: React.ReactNode
82
+ imageAlt?: string
83
+ imageSrc?: string
84
+ moreButtonLabel?: string
85
+ onMoreClick?: React.MouseEventHandler<HTMLButtonElement>
86
+ primaryActionLabel?: React.ReactNode
87
+ primaryActionProps?: ButtonProps
88
+ secondaryActionLabel?: React.ReactNode
89
+ secondaryActionProps?: ButtonProps
90
+ showDescription?: boolean
91
+ showFooter?: boolean
92
+ showImage?: boolean
93
+ showMoreButton?: boolean
94
+ showSubtitle?: boolean
95
+ subtitle?: React.ReactNode
96
+ title?: React.ReactNode
97
+ }
98
+
99
+ type CardMediaProps = React.ComponentProps<"div"> & {
100
+ imageAlt?: string
101
+ imageSrc?: string
102
+ }
103
+
104
+ type CardActionsProps = React.ComponentProps<"div"> & {
105
+ primaryLabel?: React.ReactNode
106
+ primaryProps?: ButtonProps
107
+ secondaryLabel?: React.ReactNode
108
+ secondaryProps?: ButtonProps
109
+ }
110
+
111
+ type CardFooterProps = React.ComponentProps<"div">
112
+
113
+ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
114
+ actions,
115
+ breakpoint = "desktop",
116
+ className,
117
+ description,
118
+ footer,
119
+ imageAlt = "",
120
+ imageSrc,
121
+ moreButtonLabel = "Open card menu",
122
+ onMoreClick,
123
+ orientation = "vertical",
124
+ primaryActionLabel = "Button",
125
+ primaryActionProps,
126
+ secondaryActionLabel = "Button",
127
+ secondaryActionProps,
128
+ showDescription = true,
129
+ showFooter = true,
130
+ showImage = true,
131
+ showMoreButton = true,
132
+ showSubtitle = true,
133
+ subtitle,
134
+ title,
135
+ type = "leading-image",
136
+ ...props
137
+ }, ref) {
138
+ const cardOrientation = orientation ?? "vertical"
139
+ const cardBreakpoint = breakpoint ?? "desktop"
140
+ const cardType =
141
+ cardOrientation === "horizontal" && type === "full-image"
142
+ ? "leading-image"
143
+ : type ?? "leading-image"
144
+ const fullImage = cardOrientation === "vertical" && cardType === "full-image"
145
+ const renderedTitle = title ?? "Card Title"
146
+ const renderedSubtitle = subtitle ?? "Card Subtitle"
147
+ const renderedDescription =
148
+ description ??
149
+ "Journey through time as we explore the mysteries of the Great Pyramids and the Sphinx."
150
+
151
+ const media = showImage ? (
152
+ <CardMedia
153
+ imageAlt={imageAlt}
154
+ imageSrc={imageSrc}
155
+ className={getCardMediaClassName(cardOrientation, cardBreakpoint)}
156
+ />
157
+ ) : null
158
+
159
+ const body = (
160
+ <CardBody
161
+ actions={actions}
162
+ breakpoint={cardBreakpoint}
163
+ description={renderedDescription}
164
+ footer={footer}
165
+ moreButtonLabel={moreButtonLabel}
166
+ onMoreClick={onMoreClick}
167
+ orientation={cardOrientation}
168
+ primaryActionLabel={primaryActionLabel}
169
+ primaryActionProps={primaryActionProps}
170
+ secondaryActionLabel={secondaryActionLabel}
171
+ secondaryActionProps={secondaryActionProps}
172
+ showDescription={showDescription}
173
+ showFooter={showFooter && !fullImage}
174
+ showMoreButton={showMoreButton}
175
+ showSubtitle={showSubtitle}
176
+ subtitle={renderedSubtitle}
177
+ title={renderedTitle}
178
+ />
179
+ )
180
+
181
+ return (
182
+ <article
183
+ data-slot="card"
184
+ data-breakpoint={cardBreakpoint}
185
+ data-orientation={cardOrientation}
186
+ data-type={cardType}
187
+ ref={ref}
188
+ className={cn(
189
+ cardVariants({
190
+ orientation: cardOrientation,
191
+ type: cardType,
192
+ breakpoint: cardBreakpoint,
193
+ }),
194
+ className
195
+ )}
196
+ {...props}
197
+ >
198
+ {fullImage ? (
199
+ <>
200
+ <CardMedia
201
+ imageAlt={imageAlt}
202
+ imageSrc={imageSrc}
203
+ className="absolute inset-0 size-full"
204
+ />
205
+ <CardFullImageContent
206
+ breakpoint={cardBreakpoint}
207
+ moreButtonLabel={moreButtonLabel}
208
+ onMoreClick={onMoreClick}
209
+ showMoreButton={showMoreButton}
210
+ showSubtitle={showSubtitle}
211
+ subtitle={renderedSubtitle}
212
+ title={renderedTitle}
213
+ />
214
+ </>
215
+ ) : cardOrientation === "horizontal" &&
216
+ cardBreakpoint === "desktop" &&
217
+ cardType === "trailing-image" ? (
218
+ <>
219
+ {body}
220
+ {media}
221
+ </>
222
+ ) : (
223
+ <>
224
+ {media}
225
+ {body}
226
+ </>
227
+ )}
228
+ </article>
229
+ )
230
+ })
231
+
232
+ const CardMedia = React.forwardRef<HTMLDivElement, CardMediaProps>(
233
+ function CardMedia({ children, className, imageAlt = "", imageSrc, ...props }, ref) {
234
+ return (
235
+ <div
236
+ data-slot="card-media"
237
+ ref={ref}
238
+ className={cn(
239
+ "relative min-h-0 shrink-0 overflow-hidden bg-[var(--bh-bg-neutral-subtle)]",
240
+ className
241
+ )}
242
+ {...props}
243
+ >
244
+ {children ??
245
+ (imageSrc ? (
246
+ <img
247
+ alt={imageAlt}
248
+ className="absolute inset-0 size-full object-cover"
249
+ src={imageSrc}
250
+ />
251
+ ) : (
252
+ <CardImagePlaceholder />
253
+ ))}
254
+ </div>
255
+ )
256
+ }
257
+ )
258
+
259
+ const CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>(
260
+ function CardFooter({ children, className, ...props }, ref) {
261
+ return (
262
+ <div
263
+ data-slot="card-footer"
264
+ ref={ref}
265
+ className={cn("flex min-w-0 shrink-0 flex-col items-stretch", className)}
266
+ {...props}
267
+ >
268
+ {children}
269
+ </div>
270
+ )
271
+ }
272
+ )
273
+
274
+ const CardActions = React.forwardRef<HTMLDivElement, CardActionsProps>(
275
+ function CardActions({
276
+ children,
277
+ className,
278
+ primaryLabel = "Button",
279
+ primaryProps,
280
+ secondaryLabel = "Button",
281
+ secondaryProps,
282
+ ...props
283
+ }, ref) {
284
+ return (
285
+ <div
286
+ data-slot="card-actions"
287
+ ref={ref}
288
+ className={cn(
289
+ "grid w-full max-w-[var(--bh-card-actions-max-width)] grid-cols-2 gap-[var(--bh-space-xl-12)]",
290
+ className
291
+ )}
292
+ {...props}
293
+ >
294
+ {children ?? (
295
+ <>
296
+ <Button {...primaryProps} className={cn("min-w-0", primaryProps?.className)}>
297
+ {primaryLabel}
298
+ </Button>
299
+ <Button
300
+ variant="soft"
301
+ {...secondaryProps}
302
+ className={cn("min-w-0", secondaryProps?.className)}
303
+ >
304
+ {secondaryLabel}
305
+ </Button>
306
+ </>
307
+ )}
308
+ </div>
309
+ )
310
+ }
311
+ )
312
+
313
+ function CardBody({
314
+ actions,
315
+ breakpoint,
316
+ description,
317
+ footer,
318
+ moreButtonLabel,
319
+ onMoreClick,
320
+ orientation,
321
+ primaryActionLabel,
322
+ primaryActionProps,
323
+ secondaryActionLabel,
324
+ secondaryActionProps,
325
+ showDescription,
326
+ showFooter,
327
+ showMoreButton,
328
+ showSubtitle,
329
+ subtitle,
330
+ title,
331
+ }: {
332
+ actions?: React.ReactNode
333
+ breakpoint: CardBreakpoint
334
+ description: React.ReactNode
335
+ footer?: React.ReactNode
336
+ moreButtonLabel: string
337
+ onMoreClick?: React.MouseEventHandler<HTMLButtonElement>
338
+ orientation: CardOrientation
339
+ primaryActionLabel: React.ReactNode
340
+ primaryActionProps?: ButtonProps
341
+ secondaryActionLabel: React.ReactNode
342
+ secondaryActionProps?: ButtonProps
343
+ showDescription: boolean
344
+ showFooter: boolean
345
+ showMoreButton: boolean
346
+ showSubtitle: boolean
347
+ subtitle: React.ReactNode
348
+ title: React.ReactNode
349
+ }) {
350
+ const horizontal = orientation === "horizontal"
351
+
352
+ return (
353
+ <div
354
+ data-slot="card-content"
355
+ className={cn(
356
+ "flex min-w-0 flex-col",
357
+ horizontal
358
+ ? "flex-1 gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-horizontal-padding)]"
359
+ : "w-full gap-[var(--bh-space-5xl-24)] p-[var(--bh-card-content-padding)]"
360
+ )}
361
+ >
362
+ <div
363
+ data-slot="card-copy"
364
+ className={cn(
365
+ "flex min-w-0 flex-col",
366
+ horizontal
367
+ ? "gap-[var(--bh-space-xl-12)]"
368
+ : "gap-[var(--bh-space-md-8)]"
369
+ )}
370
+ >
371
+ <CardHeader
372
+ moreButtonLabel={moreButtonLabel}
373
+ onMoreClick={onMoreClick}
374
+ onImage={false}
375
+ showMoreButton={showMoreButton}
376
+ showSubtitle={showSubtitle}
377
+ subtitle={subtitle}
378
+ title={title}
379
+ subtitleClassName={getSubtitleClassName(breakpoint)}
380
+ />
381
+ {showDescription ? (
382
+ <p
383
+ data-slot="card-description"
384
+ dir="auto"
385
+ className={cn(
386
+ "min-w-0 break-words text-[var(--bh-content-subtle)]",
387
+ getDescriptionClassName(orientation, breakpoint)
388
+ )}
389
+ >
390
+ {description}
391
+ </p>
392
+ ) : null}
393
+ </div>
394
+
395
+ {showFooter ? (
396
+ <CardFooter>
397
+ {footer ??
398
+ actions ?? (
399
+ <CardActions
400
+ primaryLabel={primaryActionLabel}
401
+ primaryProps={primaryActionProps}
402
+ secondaryLabel={secondaryActionLabel}
403
+ secondaryProps={secondaryActionProps}
404
+ />
405
+ )}
406
+ </CardFooter>
407
+ ) : null}
408
+ </div>
409
+ )
410
+ }
411
+
412
+ function CardFullImageContent({
413
+ breakpoint,
414
+ moreButtonLabel,
415
+ onMoreClick,
416
+ showMoreButton,
417
+ showSubtitle,
418
+ subtitle,
419
+ title,
420
+ }: {
421
+ breakpoint: CardBreakpoint
422
+ moreButtonLabel: string
423
+ onMoreClick?: React.MouseEventHandler<HTMLButtonElement>
424
+ showMoreButton: boolean
425
+ showSubtitle: boolean
426
+ subtitle: React.ReactNode
427
+ title: React.ReactNode
428
+ }) {
429
+ return (
430
+ <div
431
+ data-slot="card-overlay"
432
+ className={[
433
+ "absolute inset-x-0 bottom-0 flex min-w-0 flex-col",
434
+ "bg-gradient-to-b from-transparent to-[var(--bh-card-media-overlay)]",
435
+ "px-[var(--bh-card-content-padding)] pb-[var(--bh-card-content-padding)]",
436
+ "pt-[var(--bh-space-6xl-32)] text-[var(--bh-content-on-color)]",
437
+ ].join(" ")}
438
+ >
439
+ <CardHeader
440
+ moreButtonLabel={moreButtonLabel}
441
+ onMoreClick={onMoreClick}
442
+ onImage
443
+ showMoreButton={showMoreButton}
444
+ showSubtitle={showSubtitle}
445
+ subtitle={subtitle}
446
+ subtitleClassName={getSubtitleClassName(breakpoint)}
447
+ title={title}
448
+ />
449
+ </div>
450
+ )
451
+ }
452
+
453
+ function CardHeader({
454
+ moreButtonLabel,
455
+ onImage,
456
+ onMoreClick,
457
+ showMoreButton,
458
+ showSubtitle,
459
+ subtitle,
460
+ subtitleClassName,
461
+ title,
462
+ }: {
463
+ moreButtonLabel: string
464
+ onImage: boolean
465
+ onMoreClick?: React.MouseEventHandler<HTMLButtonElement>
466
+ showMoreButton: boolean
467
+ showSubtitle: boolean
468
+ subtitle: React.ReactNode
469
+ subtitleClassName: string
470
+ title: React.ReactNode
471
+ }) {
472
+ return (
473
+ <div
474
+ data-slot="card-header"
475
+ className="flex min-w-0 items-start gap-[var(--bh-space-3xl-16)]"
476
+ >
477
+ <div className="flex min-w-0 flex-1 flex-col gap-[var(--bh-space-xxs-2)]">
478
+ <h3
479
+ data-slot="card-title"
480
+ dir="auto"
481
+ className={cn(
482
+ "min-w-0 break-words text-[length:var(--bh-text-heading-sm-semibold-font-size)]",
483
+ "font-[var(--bh-text-heading-sm-semibold-font-weight)]",
484
+ "leading-[var(--bh-text-heading-sm-semibold-line-height)]",
485
+ "tracking-[var(--bh-text-heading-sm-semibold-letter-spacing)]",
486
+ onImage ? "text-[var(--bh-content-on-color)]" : "text-[var(--bh-content-default)]"
487
+ )}
488
+ >
489
+ {title}
490
+ </h3>
491
+ {showSubtitle ? (
492
+ <p
493
+ data-slot="card-subtitle"
494
+ dir="auto"
495
+ className={cn(
496
+ "min-w-0 break-words",
497
+ onImage
498
+ ? "text-[var(--bh-content-on-color)]"
499
+ : "text-[var(--bh-content-subtle)]",
500
+ subtitleClassName
501
+ )}
502
+ >
503
+ {subtitle}
504
+ </p>
505
+ ) : null}
506
+ </div>
507
+ {showMoreButton ? (
508
+ <CardMoreButton
509
+ aria-label={moreButtonLabel}
510
+ onClick={onMoreClick}
511
+ onImage={onImage}
512
+ />
513
+ ) : null}
514
+ </div>
515
+ )
516
+ }
517
+
518
+ function CardMoreButton({
519
+ onImage,
520
+ ...props
521
+ }: React.ComponentProps<"button"> & { onImage: boolean }) {
522
+ return (
523
+ <button
524
+ data-slot="card-more"
525
+ type="button"
526
+ className={cn(
527
+ "inline-flex size-[var(--bh-card-more-size)] shrink-0 items-center justify-center",
528
+ "rounded-[var(--bh-control-default)] outline-none",
529
+ "transition-[background-color,color,box-shadow]",
530
+ "focus-visible:shadow-[var(--shadow-button-focus)]",
531
+ onImage
532
+ ? "text-[var(--bh-content-on-color)] hover:bg-[var(--bh-interactive-ghost-hover)]"
533
+ : "text-[var(--bh-content-subtle)] hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)]"
534
+ )}
535
+ {...props}
536
+ >
537
+ <MoreHorizontalIcon className="size-[var(--bh-card-more-icon-size)]" aria-hidden="true" />
538
+ </button>
539
+ )
540
+ }
541
+
542
+ function CardImagePlaceholder() {
543
+ return (
544
+ <div
545
+ aria-hidden="true"
546
+ data-slot="card-image-placeholder"
547
+ className={[
548
+ "absolute inset-0",
549
+ "bg-[linear-gradient(135deg,var(--bh-bg-accent-sky-subtle),var(--bh-bg-accent-green-subtle))]",
550
+ ].join(" ")}
551
+ />
552
+ )
553
+ }
554
+
555
+ function getCardMediaClassName(
556
+ orientation: CardOrientation,
557
+ breakpoint: CardBreakpoint
558
+ ) {
559
+ if (orientation === "horizontal" && breakpoint === "desktop") {
560
+ return "h-auto w-[var(--bh-card-horizontal-media-width)]"
561
+ }
562
+
563
+ return "h-[var(--bh-card-media-height)] w-full"
564
+ }
565
+
566
+ function getSubtitleClassName(breakpoint: CardBreakpoint) {
567
+ if (breakpoint === "mobile") {
568
+ return [
569
+ "text-[length:var(--bh-text-body-md-regular-font-size)]",
570
+ "font-[var(--bh-text-body-md-regular-font-weight)]",
571
+ "leading-[var(--bh-text-body-md-regular-line-height)]",
572
+ "tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
573
+ ].join(" ")
574
+ }
575
+
576
+ return [
577
+ "text-[length:var(--bh-text-body-lg-regular-font-size)]",
578
+ "font-[var(--bh-text-body-lg-regular-font-weight)]",
579
+ "leading-[var(--bh-text-body-lg-regular-line-height)]",
580
+ "tracking-[var(--bh-text-body-lg-regular-letter-spacing)]",
581
+ ].join(" ")
582
+ }
583
+
584
+ function getDescriptionClassName(
585
+ orientation: CardOrientation,
586
+ breakpoint: CardBreakpoint
587
+ ) {
588
+ if (orientation === "horizontal" && breakpoint === "desktop") {
589
+ return getSubtitleClassName("desktop")
590
+ }
591
+
592
+ return [
593
+ "text-[length:var(--bh-text-body-md-regular-font-size)]",
594
+ "font-[var(--bh-text-body-md-regular-font-weight)]",
595
+ "leading-[var(--bh-text-body-md-regular-line-height)]",
596
+ "tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
597
+ ].join(" ")
598
+ }
599
+
600
+ export {
601
+ Card,
602
+ CardActions,
603
+ CardFooter,
604
+ CardMedia,
605
+ cardVariants,
606
+ type CardActionsProps,
607
+ type CardBreakpoint,
608
+ type CardFooterProps,
609
+ type CardMediaProps,
610
+ type CardOrientation,
611
+ type CardProps,
612
+ type CardType,
613
+ }