banhaten 0.1.1 → 0.1.3

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 (258) hide show
  1. package/README.md +86 -309
  2. package/banhaten.config.example.json +1 -1
  3. package/docs/design-system/README.md +11 -0
  4. package/docs/design-system/appearance-presets.md +184 -0
  5. package/docs/design-system/appearances/default.md +94 -0
  6. package/docs/design-system/appearances/rounded.md +95 -0
  7. package/docs/design-system/appearances/sharp.md +95 -0
  8. package/docs/design-system/component-showcase-consistency-report.md +217 -0
  9. package/docs/design-system/component-token-consistency-audit.md +163 -0
  10. package/docs/design-system/components/README.md +74 -0
  11. package/docs/design-system/components/accordion.md +51 -0
  12. package/docs/design-system/components/activity-feed.md +92 -0
  13. package/docs/design-system/components/alert-dialog.md +70 -0
  14. package/docs/design-system/components/alert.md +79 -0
  15. package/docs/design-system/components/aspect-ratio.md +44 -0
  16. package/docs/design-system/components/attribute.md +87 -0
  17. package/docs/design-system/components/autocomplete.md +74 -0
  18. package/docs/design-system/components/avatar.md +52 -0
  19. package/docs/design-system/components/badge.md +53 -0
  20. package/docs/design-system/components/banner.md +85 -0
  21. package/docs/design-system/components/breadcrumbs.md +174 -0
  22. package/docs/design-system/components/button-group.md +83 -0
  23. package/docs/design-system/components/button.md +77 -0
  24. package/docs/design-system/components/card.md +78 -0
  25. package/docs/design-system/components/carousel.md +44 -0
  26. package/docs/design-system/components/catalog-components.md +45 -0
  27. package/docs/design-system/components/chart.md +43 -0
  28. package/docs/design-system/components/checkbox.md +52 -0
  29. package/docs/design-system/components/collapsible.md +48 -0
  30. package/docs/design-system/components/command-bar.md +57 -0
  31. package/docs/design-system/components/command.md +60 -0
  32. package/docs/design-system/components/context-menu.md +44 -0
  33. package/docs/design-system/components/date-picker.md +77 -0
  34. package/docs/design-system/components/divider.md +101 -0
  35. package/docs/design-system/components/empty-state.md +55 -0
  36. package/docs/design-system/components/field.md +69 -0
  37. package/docs/design-system/components/file-upload.md +185 -0
  38. package/docs/design-system/components/hover-card.md +46 -0
  39. package/docs/design-system/components/icons.md +48 -0
  40. package/docs/design-system/components/input-group.md +56 -0
  41. package/docs/design-system/components/input-otp.md +55 -0
  42. package/docs/design-system/components/input.md +48 -0
  43. package/docs/design-system/components/kbd.md +44 -0
  44. package/docs/design-system/components/label.md +48 -0
  45. package/docs/design-system/components/menu.md +59 -0
  46. package/docs/design-system/components/menubar.md +45 -0
  47. package/docs/design-system/components/modal.md +98 -0
  48. package/docs/design-system/components/native-select.md +52 -0
  49. package/docs/design-system/components/navigation-menu.md +48 -0
  50. package/docs/design-system/components/onboarding-step-list-item.md +80 -0
  51. package/docs/design-system/components/page-header.md +84 -0
  52. package/docs/design-system/components/pagination.md +49 -0
  53. package/docs/design-system/components/popover.md +58 -0
  54. package/docs/design-system/components/progress-slider.md +48 -0
  55. package/docs/design-system/components/progress.md +75 -0
  56. package/docs/design-system/components/radio-card.md +49 -0
  57. package/docs/design-system/components/radio-group.md +55 -0
  58. package/docs/design-system/components/resizable.md +42 -0
  59. package/docs/design-system/components/scroll-area.md +45 -0
  60. package/docs/design-system/components/select.md +50 -0
  61. package/docs/design-system/components/sheet.md +65 -0
  62. package/docs/design-system/components/sidebar.md +68 -0
  63. package/docs/design-system/components/skeleton.md +73 -0
  64. package/docs/design-system/components/slideout.md +63 -0
  65. package/docs/design-system/components/slider.md +61 -0
  66. package/docs/design-system/components/social-button.md +47 -0
  67. package/docs/design-system/components/spinner.md +61 -0
  68. package/docs/design-system/components/steps.md +63 -0
  69. package/docs/design-system/components/table.md +397 -0
  70. package/docs/design-system/components/tabs.md +52 -0
  71. package/docs/design-system/components/tag.md +78 -0
  72. package/docs/design-system/components/textarea.md +48 -0
  73. package/docs/design-system/components/timeline.md +81 -0
  74. package/docs/design-system/components/toast.md +56 -0
  75. package/docs/design-system/components/toggle.md +79 -0
  76. package/docs/design-system/components/toolbar.md +85 -0
  77. package/docs/design-system/components/tooltip.md +90 -0
  78. package/docs/design-system/components/typography.md +18 -0
  79. package/docs/design-system/design-system-test-missing-items.md +368 -0
  80. package/docs/design-system/icons.md +69 -0
  81. package/docs/design-system/registry-and-cli.md +41 -0
  82. package/docs/design-system/tabs.md +53 -0
  83. package/docs/design-system/token-governance.md +38 -0
  84. package/package.json +83 -59
  85. package/registry/components/alert-dialog.tsx +297 -0
  86. package/registry/components/aspect-ratio.tsx +30 -0
  87. package/registry/components/autocomplete.tsx +637 -0
  88. package/registry/components/avatar.tsx +258 -22
  89. package/registry/components/badge.tsx +97 -35
  90. package/registry/components/carousel.tsx +234 -0
  91. package/registry/components/chart.tsx +170 -0
  92. package/registry/components/collapsible.tsx +69 -0
  93. package/registry/components/command.tsx +174 -0
  94. package/registry/components/context-menu.tsx +236 -0
  95. package/registry/components/date-picker-state.ts +253 -0
  96. package/registry/components/date-picker.tsx +116 -159
  97. package/registry/components/expanded/EmptyState.tsx +155 -0
  98. package/registry/components/expanded/PageHeader.tsx +1 -1
  99. package/registry/components/expanded/breadcrumbs.css +139 -139
  100. package/registry/components/expanded/catalogComponentsShowcase.css +83 -83
  101. package/registry/components/expanded/emptyState.css +111 -0
  102. package/registry/components/expanded/slideout.css +1 -0
  103. package/registry/components/expanded/steps.css +274 -274
  104. package/registry/components/expanded/table.css +1 -0
  105. package/registry/components/expanded/timeline.css +264 -264
  106. package/registry/components/field.tsx +230 -0
  107. package/registry/components/hover-card.tsx +48 -0
  108. package/registry/components/input-group.tsx +130 -0
  109. package/registry/components/input-otp.tsx +574 -0
  110. package/registry/components/input.tsx +22 -12
  111. package/registry/components/kbd.tsx +44 -0
  112. package/registry/components/label.tsx +78 -0
  113. package/registry/components/menu.tsx +373 -8
  114. package/registry/components/menubar.tsx +226 -0
  115. package/registry/components/modal.tsx +109 -76
  116. package/registry/components/native-select.tsx +205 -0
  117. package/registry/components/navigation-menu.tsx +171 -0
  118. package/registry/components/popover.tsx +840 -0
  119. package/registry/components/radio-group.tsx +1 -1
  120. package/registry/components/resizable.tsx +74 -0
  121. package/registry/components/scroll-area.tsx +67 -0
  122. package/registry/components/select.tsx +6 -4
  123. package/registry/components/sheet.tsx +305 -0
  124. package/registry/components/sidebar.tsx +352 -0
  125. package/registry/components/skeleton.css +57 -0
  126. package/registry/components/skeleton.tsx +482 -0
  127. package/registry/components/social-button.tsx +74 -10
  128. package/registry/components/spinner.tsx +79 -11
  129. package/registry/components/{expanded/tabs.css → tabs.css} +127 -106
  130. package/registry/components/tabs.tsx +242 -0
  131. package/registry/components/textarea.tsx +2 -2
  132. package/registry/components/toast.tsx +131 -0
  133. package/registry/components/tooltip.tsx +4 -0
  134. package/registry/examples/alert-dialog-demo.tsx +42 -0
  135. package/registry/examples/aspect-ratio-demo.tsx +11 -0
  136. package/registry/examples/autocomplete-demo.tsx +109 -0
  137. package/registry/examples/avatar-demo.tsx +102 -47
  138. package/registry/examples/badge-demo.tsx +16 -0
  139. package/registry/examples/carousel-demo.tsx +25 -0
  140. package/registry/examples/chart-demo.tsx +33 -0
  141. package/registry/examples/collapsible-demo.tsx +16 -0
  142. package/registry/examples/command-demo.tsx +42 -0
  143. package/registry/examples/context-menu-demo.tsx +29 -0
  144. package/registry/examples/expanded/command-bar-demo.tsx +236 -0
  145. package/registry/examples/expanded/empty-state-demo.tsx +39 -0
  146. package/registry/examples/expanded/tabs-demo.tsx +1 -1
  147. package/registry/examples/field-demo.tsx +51 -0
  148. package/registry/examples/hover-card-demo.tsx +23 -0
  149. package/registry/examples/input-demo.tsx +1 -1
  150. package/registry/examples/input-group-demo.tsx +16 -0
  151. package/registry/examples/input-otp-demo.tsx +72 -0
  152. package/registry/examples/kbd-demo.tsx +11 -0
  153. package/registry/examples/label-demo.tsx +20 -0
  154. package/registry/examples/menu-demo.tsx +101 -88
  155. package/registry/examples/menubar-demo.tsx +34 -0
  156. package/registry/examples/native-select-demo.tsx +16 -0
  157. package/registry/examples/navigation-menu-demo.tsx +29 -0
  158. package/registry/examples/popover-demo.tsx +546 -0
  159. package/registry/examples/resizable-demo.tsx +22 -0
  160. package/registry/examples/scroll-area-demo.tsx +15 -0
  161. package/registry/examples/select-demo.tsx +1 -1
  162. package/registry/examples/sheet-demo.tsx +47 -0
  163. package/registry/examples/sidebar-demo.tsx +55 -0
  164. package/registry/examples/skeleton-demo.tsx +56 -0
  165. package/registry/examples/spinner-demo.tsx +23 -1
  166. package/registry/examples/tabs-demo.tsx +13 -0
  167. package/registry/examples/textarea-demo.tsx +1 -1
  168. package/registry/examples/toast-demo.tsx +35 -0
  169. package/registry/index.json +895 -19
  170. package/registry/styles/globals.css +4733 -4602
  171. package/registry.json +1612 -0
  172. package/schema/config.schema.json +48 -0
  173. package/schema/registry.schema.json +85 -0
  174. package/schema/tokens.schema.json +63 -0
  175. package/src/cli/index.js +1413 -184
  176. package/tokens/banhaten.tokens.json +1 -1
  177. package/registry/assets/avatars/avatar-02.jpg +0 -0
  178. package/registry/assets/avatars/avatar-03.jpg +0 -0
  179. package/registry/assets/avatars/avatar-04.jpg +0 -0
  180. package/registry/assets/avatars/avatar-05.jpg +0 -0
  181. package/registry/assets/avatars/avatar-06.jpg +0 -0
  182. package/registry/assets/avatars/avatar-07.jpg +0 -0
  183. package/registry/assets/avatars/avatar-08.jpg +0 -0
  184. package/registry/assets/avatars/avatar-09.jpg +0 -0
  185. package/registry/assets/avatars/avatar-10.jpg +0 -0
  186. package/registry/assets/avatars/avatar-11.jpg +0 -0
  187. package/registry/assets/avatars/avatar-12.jpg +0 -0
  188. package/registry/assets/avatars/avatar-13.jpg +0 -0
  189. package/registry/assets/avatars/avatar-14.jpg +0 -0
  190. package/registry/assets/avatars/avatar-15.jpg +0 -0
  191. package/registry/assets/avatars/avatar-16.jpg +0 -0
  192. package/registry/assets/avatars/avatar-17.jpg +0 -0
  193. package/registry/assets/avatars/avatar-18.jpg +0 -0
  194. package/registry/assets/avatars/avatar-19.jpg +0 -0
  195. package/registry/assets/avatars/avatar-20.jpg +0 -0
  196. package/registry/assets/avatars/avatar-21.jpg +0 -0
  197. package/registry/assets/avatars/avatar-22.jpg +0 -0
  198. package/registry/assets/avatars/avatar-23.jpg +0 -0
  199. package/registry/assets/avatars/avatar-24.jpg +0 -0
  200. package/registry/assets/avatars/avatar-25.jpg +0 -0
  201. package/registry/assets/avatars/avatar-26.jpg +0 -0
  202. package/registry/assets/avatars/avatar-27.jpg +0 -0
  203. package/registry/assets/avatars/avatar-28.jpg +0 -0
  204. package/registry/assets/avatars/avatar-29.jpg +0 -0
  205. package/registry/assets/avatars/avatar-30.jpg +0 -0
  206. package/registry/assets/avatars/avatar-31.jpg +0 -0
  207. package/registry/assets/avatars/avatar-32.jpg +0 -0
  208. package/registry/assets/avatars/avatar-33.jpg +0 -0
  209. package/registry/assets/avatars/avatar-34.jpg +0 -0
  210. package/registry/assets/avatars/avatar-35.jpg +0 -0
  211. package/registry/assets/image-assets.json +0 -744
  212. package/registry/assets/images/art-02.jpg +0 -0
  213. package/registry/assets/images/art-03.jpg +0 -0
  214. package/registry/assets/images/art-04.jpg +0 -0
  215. package/registry/assets/images/art-05.jpg +0 -0
  216. package/registry/assets/images/art-06.jpg +0 -0
  217. package/registry/assets/images/art-07.jpg +0 -0
  218. package/registry/assets/images/art-08.jpg +0 -0
  219. package/registry/assets/images/art-09.jpg +0 -0
  220. package/registry/assets/images/art-10.jpg +0 -0
  221. package/registry/assets/images/art-11.jpg +0 -0
  222. package/registry/assets/images/art-12.jpg +0 -0
  223. package/registry/assets/images/art-13.jpg +0 -0
  224. package/registry/assets/images/art-14.jpg +0 -0
  225. package/registry/assets/images/art-15.jpg +0 -0
  226. package/registry/assets/images/art-16.jpg +0 -0
  227. package/registry/assets/images/art-17.jpg +0 -0
  228. package/registry/assets/images/art-18.jpg +0 -0
  229. package/registry/assets/images/art-19.jpg +0 -0
  230. package/registry/assets/images/art-20.jpg +0 -0
  231. package/registry/assets/images/art-21.jpg +0 -0
  232. package/registry/assets/images/art-22.jpg +0 -0
  233. package/registry/assets/images/art-23.jpg +0 -0
  234. package/registry/assets/images/art-24.jpg +0 -0
  235. package/registry/assets/images/art-25.jpg +0 -0
  236. package/registry/assets/images/art-26.jpg +0 -0
  237. package/registry/assets/images/art-27.jpg +0 -0
  238. package/registry/assets/images/nature-01.jpg +0 -0
  239. package/registry/assets/images/nature-02.jpg +0 -0
  240. package/registry/assets/images/nature-03.jpg +0 -0
  241. package/registry/assets/images/nature-04.jpg +0 -0
  242. package/registry/assets/images/nature-05.jpg +0 -0
  243. package/registry/assets/images/nature-06.jpg +0 -0
  244. package/registry/assets/images/nature-07.jpg +0 -0
  245. package/registry/assets/images/nature-08.jpg +0 -0
  246. package/registry/assets/images/nature-09.jpg +0 -0
  247. package/registry/assets/images/nature-10.jpg +0 -0
  248. package/registry/assets/images/nature-11.jpg +0 -0
  249. package/registry/assets/images/nature-12.jpg +0 -0
  250. package/registry/assets/images/nature-13.jpg +0 -0
  251. package/registry/assets/images/nature-14.jpg +0 -0
  252. package/registry/assets/images/nature-15.jpg +0 -0
  253. package/registry/assets/images/nature-16.jpg +0 -0
  254. package/registry/assets/images/nature-17.jpg +0 -0
  255. package/registry/assets/images/nature-18.jpg +0 -0
  256. package/registry/assets/images/nature-19.jpg +0 -0
  257. package/registry/assets/images/nature-20.jpg +0 -0
  258. package/registry/components/expanded/Tabs.tsx +0 -86
@@ -0,0 +1,482 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ import "./skeleton.css"
7
+
8
+ const skeletonVariants = cva(
9
+ "relative block min-w-0 shrink-0 overflow-hidden bg-[var(--bh-skeleton-bg)] h-[var(--bh-skeleton-height)] w-[var(--bh-skeleton-width)]",
10
+ {
11
+ variants: {
12
+ shape: {
13
+ block:
14
+ "rounded-[var(--bh-skeleton-radius)] [--bh-skeleton-radius:var(--bh-radius-md-6)]",
15
+ text:
16
+ "rounded-[var(--bh-radius-full)] [--bh-skeleton-height:var(--bh-text-body-md-regular-line-height)]",
17
+ circle:
18
+ "aspect-square rounded-[var(--bh-radius-full)] [--bh-skeleton-height:var(--bh-skeleton-width)]",
19
+ },
20
+ size: {
21
+ xs: "[--bh-skeleton-height:var(--bh-space-md-8)]",
22
+ sm: "[--bh-skeleton-height:var(--bh-space-xl-12)]",
23
+ md: "[--bh-skeleton-height:var(--bh-space-3xl-16)]",
24
+ lg: "[--bh-skeleton-height:var(--bh-space-5xl-24)]",
25
+ xl: "[--bh-skeleton-height:var(--bh-space-7xl-40)]",
26
+ },
27
+ tone: {
28
+ default:
29
+ "[--bh-skeleton-bg:var(--bh-bg-muted)] [--bh-skeleton-highlight:var(--bh-bg-raised-subtle)]",
30
+ subtle:
31
+ "[--bh-skeleton-bg:var(--bh-bg-subtle)] [--bh-skeleton-highlight:var(--bh-bg-default-hover)]",
32
+ surface:
33
+ "[--bh-skeleton-bg:var(--bh-bg-raised-subtle)] [--bh-skeleton-highlight:var(--bh-bg-default)]",
34
+ brand:
35
+ "[--bh-skeleton-bg:var(--bh-bg-brand-subtle)] [--bh-skeleton-highlight:var(--bh-bg-brand-soft)]",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ shape: "block",
40
+ size: "md",
41
+ tone: "default",
42
+ },
43
+ }
44
+ )
45
+
46
+ type SkeletonCssProperties = React.CSSProperties & {
47
+ "--bh-skeleton-height"?: React.CSSProperties["height"]
48
+ "--bh-skeleton-width"?: React.CSSProperties["width"]
49
+ }
50
+
51
+ type SkeletonProps = React.ComponentProps<"span"> &
52
+ VariantProps<typeof skeletonVariants> & {
53
+ animated?: boolean
54
+ }
55
+
56
+ const Skeleton = React.forwardRef<HTMLSpanElement, SkeletonProps>(function Skeleton(
57
+ {
58
+ animated = true,
59
+ "aria-hidden": ariaHidden = true,
60
+ className,
61
+ shape,
62
+ size,
63
+ tone,
64
+ ...props
65
+ },
66
+ ref
67
+ ) {
68
+ return (
69
+ <span
70
+ aria-hidden={ariaHidden}
71
+ data-animated={animated ? "true" : "false"}
72
+ data-slot="skeleton"
73
+ ref={ref}
74
+ className={cn(skeletonVariants({ shape, size, tone, className }))}
75
+ {...props}
76
+ />
77
+ )
78
+ })
79
+
80
+ type SkeletonTextProps = Omit<React.ComponentProps<"div">, "children"> & {
81
+ animated?: boolean
82
+ lineCount?: number
83
+ lineSize?: SkeletonProps["size"]
84
+ tone?: SkeletonProps["tone"]
85
+ widths?: Array<React.CSSProperties["width"]>
86
+ }
87
+
88
+ function SkeletonText({
89
+ animated,
90
+ className,
91
+ lineCount = 3,
92
+ lineSize = "md",
93
+ tone,
94
+ widths,
95
+ ...props
96
+ }: SkeletonTextProps) {
97
+ const count = Math.max(1, lineCount)
98
+
99
+ return (
100
+ <div
101
+ aria-hidden="true"
102
+ data-slot="skeleton-text"
103
+ className={cn("grid gap-[var(--bh-space-xs-4)]", className)}
104
+ {...props}
105
+ >
106
+ {Array.from({ length: count }, (_, index) => {
107
+ const width =
108
+ widths?.[index] ?? (index === count - 1 && count > 1 ? "66%" : "100%")
109
+
110
+ return (
111
+ <Skeleton
112
+ animated={animated}
113
+ key={index}
114
+ shape="text"
115
+ size={lineSize}
116
+ tone={tone}
117
+ style={{ "--bh-skeleton-width": width } as SkeletonCssProperties}
118
+ />
119
+ )
120
+ })}
121
+ </div>
122
+ )
123
+ }
124
+
125
+ const skeletonAvatarSizes = {
126
+ xs: "size-[var(--bh-space-5xl-24)]",
127
+ sm: "size-[var(--bh-space-6xl-32)]",
128
+ md: "size-[var(--bh-space-7xl-40)]",
129
+ lg: "size-[var(--bh-space-8xl-48)]",
130
+ xl: "size-[var(--bh-space-9xl-64)]",
131
+ } as const
132
+
133
+ type SkeletonAvatarProps = Omit<SkeletonProps, "shape" | "size"> & {
134
+ size?: keyof typeof skeletonAvatarSizes
135
+ }
136
+
137
+ function SkeletonAvatar({
138
+ className,
139
+ size = "md",
140
+ ...props
141
+ }: SkeletonAvatarProps) {
142
+ return (
143
+ <Skeleton
144
+ shape="circle"
145
+ className={cn(skeletonAvatarSizes[size], className)}
146
+ {...props}
147
+ />
148
+ )
149
+ }
150
+
151
+ const skeletonButtonSizes = {
152
+ sm: "h-[var(--bh-button-sm-height)] w-[var(--bh-space-11xl-96)]",
153
+ default: "h-[var(--bh-button-md-height)] w-[var(--bh-space-12xl-128)]",
154
+ lg: "h-[var(--bh-button-lg-height)] w-[var(--bh-space-13xl-160)]",
155
+ xl: "h-[var(--bh-button-xl-height)] w-[var(--bh-space-14xl-192)]",
156
+ icon: "size-[var(--bh-button-md-height)]",
157
+ } as const
158
+
159
+ type SkeletonButtonProps = Omit<SkeletonProps, "shape" | "size"> & {
160
+ size?: keyof typeof skeletonButtonSizes
161
+ }
162
+
163
+ function SkeletonButton({
164
+ className,
165
+ size = "default",
166
+ ...props
167
+ }: SkeletonButtonProps) {
168
+ return (
169
+ <Skeleton
170
+ shape="block"
171
+ className={cn(
172
+ "rounded-[var(--bh-control-default)]",
173
+ skeletonButtonSizes[size],
174
+ className
175
+ )}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+
181
+ type SkeletonInputProps = Omit<React.ComponentProps<"div">, "children"> & {
182
+ animated?: boolean
183
+ helper?: boolean
184
+ label?: boolean
185
+ tone?: SkeletonProps["tone"]
186
+ }
187
+
188
+ function SkeletonInput({
189
+ animated,
190
+ className,
191
+ helper = true,
192
+ label = true,
193
+ tone,
194
+ ...props
195
+ }: SkeletonInputProps) {
196
+ return (
197
+ <div
198
+ aria-hidden="true"
199
+ data-slot="skeleton-input"
200
+ className={cn("grid gap-[var(--bh-space-sm-6)]", className)}
201
+ {...props}
202
+ >
203
+ {label && (
204
+ <Skeleton
205
+ animated={animated}
206
+ shape="text"
207
+ size="sm"
208
+ tone={tone}
209
+ style={{ "--bh-skeleton-width": "42%" } as SkeletonCssProperties}
210
+ />
211
+ )}
212
+ <Skeleton
213
+ animated={animated}
214
+ className="rounded-[var(--bh-control-default)]"
215
+ size="xl"
216
+ tone={tone}
217
+ />
218
+ {helper && (
219
+ <Skeleton
220
+ animated={animated}
221
+ shape="text"
222
+ size="xs"
223
+ tone={tone}
224
+ style={{ "--bh-skeleton-width": "58%" } as SkeletonCssProperties}
225
+ />
226
+ )}
227
+ </div>
228
+ )
229
+ }
230
+
231
+ type SkeletonCardProps = Omit<React.ComponentProps<"div">, "children"> & {
232
+ actions?: boolean
233
+ animated?: boolean
234
+ avatar?: boolean
235
+ lines?: number
236
+ media?: boolean
237
+ tone?: SkeletonProps["tone"]
238
+ }
239
+
240
+ function SkeletonCard({
241
+ actions = true,
242
+ animated,
243
+ avatar = true,
244
+ className,
245
+ lines = 3,
246
+ media = true,
247
+ tone,
248
+ ...props
249
+ }: SkeletonCardProps) {
250
+ return (
251
+ <div
252
+ aria-hidden="true"
253
+ data-slot="skeleton-card"
254
+ className={cn(
255
+ "grid gap-[var(--bh-space-4xl-20)] rounded-[var(--bh-radius-xl-10)] border border-[var(--bh-border-default)] bg-[var(--bh-bg-raised)] p-[var(--bh-card-desktop-content-padding)] shadow-[var(--shadow-component-default)]",
256
+ className
257
+ )}
258
+ {...props}
259
+ >
260
+ {media && (
261
+ <Skeleton
262
+ animated={animated}
263
+ className="aspect-[16/9] rounded-[var(--bh-radius-lg-8)]"
264
+ size="xl"
265
+ tone={tone}
266
+ />
267
+ )}
268
+ <div className="grid grid-cols-[auto_minmax(0,1fr)] gap-[var(--bh-space-xl-12)]">
269
+ {avatar && <SkeletonAvatar animated={animated} tone={tone} />}
270
+ <SkeletonText animated={animated} lineCount={lines} tone={tone} />
271
+ </div>
272
+ {actions && (
273
+ <div className="flex flex-wrap gap-[var(--bh-space-md-8)]">
274
+ <SkeletonButton animated={animated} size="sm" tone={tone} />
275
+ <SkeletonButton animated={animated} size="sm" tone={tone} />
276
+ </div>
277
+ )}
278
+ </div>
279
+ )
280
+ }
281
+
282
+ type SkeletonListProps = Omit<React.ComponentProps<"div">, "children"> & {
283
+ animated?: boolean
284
+ rows?: number
285
+ tone?: SkeletonProps["tone"]
286
+ withAvatar?: boolean
287
+ withMeta?: boolean
288
+ }
289
+
290
+ function SkeletonList({
291
+ animated,
292
+ className,
293
+ rows = 4,
294
+ tone,
295
+ withAvatar = true,
296
+ withMeta = true,
297
+ ...props
298
+ }: SkeletonListProps) {
299
+ const rowCount = Math.max(1, rows)
300
+
301
+ return (
302
+ <div
303
+ aria-hidden="true"
304
+ data-slot="skeleton-list"
305
+ className={cn(
306
+ "overflow-hidden rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-default)] bg-[var(--bh-bg-raised)]",
307
+ className
308
+ )}
309
+ {...props}
310
+ >
311
+ {Array.from({ length: rowCount }, (_, index) => (
312
+ <div
313
+ className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-[var(--bh-space-xl-12)] border-b border-[var(--bh-border-subtle)] p-[var(--bh-space-3xl-16)] last:border-b-0"
314
+ key={index}
315
+ >
316
+ {withAvatar && <SkeletonAvatar animated={animated} size="sm" tone={tone} />}
317
+ <SkeletonText
318
+ animated={animated}
319
+ lineCount={2}
320
+ lineSize="sm"
321
+ tone={tone}
322
+ widths={["74%", "46%"]}
323
+ />
324
+ {withMeta && (
325
+ <Skeleton
326
+ animated={animated}
327
+ shape="text"
328
+ size="sm"
329
+ tone={tone}
330
+ style={{ "--bh-skeleton-width": "64%" } as SkeletonCssProperties}
331
+ />
332
+ )}
333
+ </div>
334
+ ))}
335
+ </div>
336
+ )
337
+ }
338
+
339
+ type SkeletonTableProps = Omit<React.ComponentProps<"div">, "children"> & {
340
+ animated?: boolean
341
+ columns?: number
342
+ rows?: number
343
+ showHeader?: boolean
344
+ tone?: SkeletonProps["tone"]
345
+ }
346
+
347
+ function SkeletonTable({
348
+ animated,
349
+ className,
350
+ columns = 4,
351
+ rows = 5,
352
+ showHeader = true,
353
+ tone,
354
+ ...props
355
+ }: SkeletonTableProps) {
356
+ const columnCount = Math.max(1, columns)
357
+ const rowCount = Math.max(1, rows)
358
+ const gridStyle = {
359
+ "--bh-skeleton-table-columns": `repeat(${columnCount}, minmax(0, 1fr))`,
360
+ } as React.CSSProperties
361
+
362
+ return (
363
+ <div
364
+ aria-hidden="true"
365
+ data-slot="skeleton-table"
366
+ className={cn(
367
+ "overflow-hidden rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-default)] bg-[var(--bh-bg-raised)]",
368
+ className
369
+ )}
370
+ {...props}
371
+ >
372
+ {showHeader && (
373
+ <div
374
+ className="grid grid-cols-[var(--bh-skeleton-table-columns)] gap-[var(--bh-space-3xl-16)] border-b border-[var(--bh-border-default)] bg-[var(--bh-bg-muted)] p-[var(--bh-space-xl-12)]"
375
+ style={gridStyle}
376
+ >
377
+ {Array.from({ length: columnCount }, (_, index) => (
378
+ <Skeleton
379
+ animated={animated}
380
+ key={index}
381
+ shape="text"
382
+ size="sm"
383
+ tone={tone}
384
+ />
385
+ ))}
386
+ </div>
387
+ )}
388
+ {Array.from({ length: rowCount }, (_, rowIndex) => (
389
+ <div
390
+ className="grid grid-cols-[var(--bh-skeleton-table-columns)] items-center gap-[var(--bh-space-3xl-16)] border-b border-[var(--bh-border-subtle)] p-[var(--bh-space-xl-12)] last:border-b-0"
391
+ key={rowIndex}
392
+ style={gridStyle}
393
+ >
394
+ {Array.from({ length: columnCount }, (_, columnIndex) => (
395
+ <Skeleton
396
+ animated={animated}
397
+ key={columnIndex}
398
+ shape="text"
399
+ size="sm"
400
+ tone={tone}
401
+ style={
402
+ {
403
+ "--bh-skeleton-width":
404
+ columnIndex === columnCount - 1 ? "58%" : "100%",
405
+ } as SkeletonCssProperties
406
+ }
407
+ />
408
+ ))}
409
+ </div>
410
+ ))}
411
+ </div>
412
+ )
413
+ }
414
+
415
+ type SkeletonFormProps = Omit<React.ComponentProps<"div">, "children"> & {
416
+ actions?: boolean
417
+ animated?: boolean
418
+ fields?: number
419
+ tone?: SkeletonProps["tone"]
420
+ }
421
+
422
+ function SkeletonForm({
423
+ actions = true,
424
+ animated,
425
+ className,
426
+ fields = 3,
427
+ tone,
428
+ ...props
429
+ }: SkeletonFormProps) {
430
+ const fieldCount = Math.max(1, fields)
431
+
432
+ return (
433
+ <div
434
+ aria-hidden="true"
435
+ data-slot="skeleton-form"
436
+ className={cn(
437
+ "grid gap-[var(--bh-space-4xl-20)] rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-default)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-5xl-24)]",
438
+ className
439
+ )}
440
+ {...props}
441
+ >
442
+ {Array.from({ length: fieldCount }, (_, index) => (
443
+ <SkeletonInput
444
+ animated={animated}
445
+ helper={index === fieldCount - 1}
446
+ key={index}
447
+ tone={tone}
448
+ />
449
+ ))}
450
+ {actions && (
451
+ <div className="flex flex-wrap justify-end gap-[var(--bh-space-md-8)]">
452
+ <SkeletonButton animated={animated} size="sm" tone={tone} />
453
+ <SkeletonButton animated={animated} tone={tone} />
454
+ </div>
455
+ )}
456
+ </div>
457
+ )
458
+ }
459
+
460
+ export {
461
+ Skeleton,
462
+ SkeletonAvatar,
463
+ SkeletonButton,
464
+ SkeletonCard,
465
+ SkeletonForm,
466
+ SkeletonInput,
467
+ SkeletonList,
468
+ SkeletonTable,
469
+ SkeletonText,
470
+ skeletonVariants,
471
+ }
472
+ export type {
473
+ SkeletonAvatarProps,
474
+ SkeletonButtonProps,
475
+ SkeletonCardProps,
476
+ SkeletonFormProps,
477
+ SkeletonInputProps,
478
+ SkeletonListProps,
479
+ SkeletonProps,
480
+ SkeletonTableProps,
481
+ SkeletonTextProps,
482
+ }
@@ -3,12 +3,7 @@ import { Slot } from "@radix-ui/react-slot"
3
3
  import { cva, type VariantProps } from "class-variance-authority"
4
4
  import {
5
5
  AppleIcon,
6
- ChromeIcon,
7
- FacebookIcon,
8
- LinkedinIcon,
9
6
  MessageCircleIcon,
10
- TwitterIcon,
11
- type LucideIcon,
12
7
  } from "lucide-react"
13
8
 
14
9
  import { cn } from "@/lib/utils"
@@ -23,6 +18,11 @@ type SocialPlatform =
23
18
 
24
19
  type SocialButtonVariant = "solid" | "outline"
25
20
  type SocialButtonSize = "default" | "icon"
21
+ type SocialButtonIconComponent = React.ComponentType<
22
+ React.SVGProps<SVGSVGElement> & {
23
+ strokeWidth?: number
24
+ }
25
+ >
26
26
 
27
27
  type SocialPlatformConfig = {
28
28
  label: string
@@ -276,12 +276,76 @@ function SocialButtonIcon({
276
276
  )
277
277
  }
278
278
 
279
- const socialButtonIcons: Record<SocialPlatform, LucideIcon> = {
279
+ function FacebookBrandIcon({
280
+ strokeWidth: _strokeWidth,
281
+ ...props
282
+ }: React.SVGProps<SVGSVGElement> & {
283
+ strokeWidth?: number
284
+ }) {
285
+ return (
286
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
287
+ <path
288
+ d="M13.5 21v-7h2.35l.45-3h-2.8V9.06c0-.82.4-1.62 1.68-1.62h1.27V4.89S15.3 4.7 14.2 4.7c-2.3 0-3.8 1.39-3.8 3.9V11H7.84v3h2.56v7h3.1Z"
289
+ fill="currentColor"
290
+ />
291
+ </svg>
292
+ )
293
+ }
294
+
295
+ function GoogleBrandIcon({
296
+ strokeWidth: _strokeWidth,
297
+ ...props
298
+ }: React.SVGProps<SVGSVGElement> & {
299
+ strokeWidth?: number
300
+ }) {
301
+ return (
302
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
303
+ <path
304
+ d="M12 4.75a7.25 7.25 0 1 0 5.13 12.37 6.85 6.85 0 0 0 1.87-4.72H12v2.6h4.09a3.96 3.96 0 1 1-1.08-4.1l1.84-1.84A7.2 7.2 0 0 0 12 4.75Z"
305
+ fill="currentColor"
306
+ />
307
+ </svg>
308
+ )
309
+ }
310
+
311
+ function LinkedinBrandIcon({
312
+ strokeWidth: _strokeWidth,
313
+ ...props
314
+ }: React.SVGProps<SVGSVGElement> & {
315
+ strokeWidth?: number
316
+ }) {
317
+ return (
318
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
319
+ <path
320
+ d="M6.7 8.9H3.9V20h2.8V8.9ZM5.3 4A1.65 1.65 0 1 0 5.3 7.3 1.65 1.65 0 0 0 5.3 4Zm14.8 9.85c0-3.05-1.63-4.47-3.8-4.47a3.28 3.28 0 0 0-2.98 1.64h-.04V8.9h-2.69V20h2.8v-5.5c0-1.45.28-2.85 2.07-2.85 1.76 0 1.78 1.65 1.78 2.94V20h2.86v-6.15Z"
321
+ fill="currentColor"
322
+ />
323
+ </svg>
324
+ )
325
+ }
326
+
327
+ function TwitterBrandIcon({
328
+ strokeWidth: _strokeWidth,
329
+ ...props
330
+ }: React.SVGProps<SVGSVGElement> & {
331
+ strokeWidth?: number
332
+ }) {
333
+ return (
334
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
335
+ <path
336
+ d="m15.3 10.9 5.1-5.9h-1.2l-4.43 5.14L11.23 5H7.15l5.35 7.79L7.15 19h1.2l4.68-5.43L16.77 19h4.08l-5.55-8.1Zm-1.66 1.92-.54-.78-4.31-6.15h1.86l3.49 4.98.54.78 4.52 6.46h-1.86l-3.7-5.29Z"
337
+ fill="currentColor"
338
+ />
339
+ </svg>
340
+ )
341
+ }
342
+
343
+ const socialButtonIcons: Record<SocialPlatform, SocialButtonIconComponent> = {
280
344
  apple: AppleIcon,
281
- facebook: FacebookIcon,
282
- google: ChromeIcon,
283
- linkedin: LinkedinIcon,
284
- twitter: TwitterIcon,
345
+ facebook: FacebookBrandIcon,
346
+ google: GoogleBrandIcon,
347
+ linkedin: LinkedinBrandIcon,
348
+ twitter: TwitterBrandIcon,
285
349
  whatsapp: MessageCircleIcon,
286
350
  }
287
351
 
@@ -2,16 +2,39 @@ import * as React from "react"
2
2
 
3
3
  import { cn } from "@/lib/utils"
4
4
 
5
- function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
5
+ type SpinnerMotion = "dynamic" | "steady"
6
+
7
+ type SpinnerProps = React.ComponentProps<"svg">
8
+
9
+ type DynamicSpinnerProps = SpinnerProps
10
+
11
+ type SpinnerBaseProps = SpinnerProps & {
12
+ motion: SpinnerMotion
13
+ }
14
+
15
+ function Spinner(props: SpinnerProps) {
16
+ return <SpinnerBase {...props} motion="steady" />
17
+ }
18
+
19
+ function DynamicSpinner(props: DynamicSpinnerProps) {
20
+ return <SpinnerBase {...props} motion="dynamic" />
21
+ }
22
+
23
+ function SpinnerBase({ className, motion, ...props }: SpinnerBaseProps) {
6
24
  return (
7
25
  <svg
8
26
  aria-hidden="true"
27
+ data-motion={motion}
9
28
  data-slot="spinner"
10
29
  viewBox="0 0 24 24"
11
- className={cn("size-[var(--bh-spinner-size)] shrink-0 text-current", className)}
30
+ className={cn(
31
+ "size-[var(--bh-spinner-size)] shrink-0 text-current",
32
+ className
33
+ )}
12
34
  {...props}
13
35
  >
14
36
  <circle
37
+ data-slot="spinner-track"
15
38
  cx="12"
16
39
  cy="12"
17
40
  r="10"
@@ -21,6 +44,7 @@ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
21
44
  opacity="var(--bh-opacity-25)"
22
45
  />
23
46
  <circle
47
+ data-slot="spinner-arc"
24
48
  cx="12"
25
49
  cy="12"
26
50
  r="10"
@@ -31,17 +55,61 @@ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
31
55
  pathLength="100"
32
56
  strokeDasharray="30 70"
33
57
  >
34
- <animateTransform
35
- attributeName="transform"
36
- dur="1s"
37
- from="0 12 12"
38
- repeatCount="indefinite"
39
- to="360 12 12"
40
- type="rotate"
41
- />
58
+ {motion === "dynamic" ? (
59
+ <DynamicSpinnerMotion />
60
+ ) : (
61
+ <SteadySpinnerMotion />
62
+ )}
42
63
  </circle>
43
64
  </svg>
44
65
  )
45
66
  }
46
67
 
47
- export { Spinner }
68
+ function DynamicSpinnerMotion() {
69
+ return (
70
+ <>
71
+ <animate
72
+ attributeName="stroke-dasharray"
73
+ calcMode="spline"
74
+ dur="6s"
75
+ keySplines="0.45 0 0.55 1; 0.45 0 0.55 1; 0.45 0 0.55 1; 0.45 0 0.55 1"
76
+ keyTimes="0; 0.25; 0.5; 0.75; 1"
77
+ repeatCount="indefinite"
78
+ values="22 78; 38 62; 30 70; 42 58; 22 78"
79
+ />
80
+ <animate
81
+ attributeName="stroke-dashoffset"
82
+ calcMode="spline"
83
+ dur="6s"
84
+ keySplines="0.45 0 0.55 1; 0.45 0 0.55 1; 0.45 0 0.55 1; 0.45 0 0.55 1"
85
+ keyTimes="0; 0.25; 0.5; 0.75; 1"
86
+ repeatCount="indefinite"
87
+ values="0; -16; -44; -72; -100"
88
+ />
89
+ <animateTransform
90
+ attributeName="transform"
91
+ dur="6s"
92
+ from="0 12 12"
93
+ repeatCount="indefinite"
94
+ to="1080 12 12"
95
+ type="rotate"
96
+ />
97
+ </>
98
+ )
99
+ }
100
+
101
+ function SteadySpinnerMotion() {
102
+ return (
103
+ <animateTransform
104
+ attributeName="transform"
105
+ dur="1.5s"
106
+ from="0 12 12"
107
+ repeatCount="indefinite"
108
+ to="360 12 12"
109
+ type="rotate"
110
+ />
111
+ )
112
+ }
113
+
114
+ export { DynamicSpinner, Spinner }
115
+ export type { DynamicSpinnerProps, SpinnerProps }