@zentauri-ui/zentauri-components 1.3.1 → 1.4.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 (307) hide show
  1. package/README.md +78 -0
  2. package/cli/cli.integration.test.ts +51 -0
  3. package/cli/index.mjs +664 -0
  4. package/cli/registry.json +36 -0
  5. package/cli/rewrite-imports.mjs +57 -0
  6. package/cli/rewrite-imports.test.ts +71 -0
  7. package/dist/ui/slider/slider.d.ts +18 -0
  8. package/dist/ui/slider/slider.d.ts.map +1 -1
  9. package/dist/ui/slider.js +21 -25
  10. package/dist/ui/slider.js.map +1 -1
  11. package/dist/ui/slider.mjs +21 -25
  12. package/dist/ui/slider.mjs.map +1 -1
  13. package/package.json +8 -2
  14. package/src/hooks/index.ts +48 -0
  15. package/src/hooks/useBodyScrollLock/index.ts +1 -0
  16. package/src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts +51 -0
  17. package/src/hooks/useBodyScrollLock/useBodyScrollLock.ts +48 -0
  18. package/src/hooks/useClickOutside/index.ts +5 -0
  19. package/src/hooks/useClickOutside/useClickOutside.test.tsx +60 -0
  20. package/src/hooks/useClickOutside/useClickOutside.ts +52 -0
  21. package/src/hooks/useClipboard/index.ts +1 -0
  22. package/src/hooks/useClipboard/useClipboard.test.ts +101 -0
  23. package/src/hooks/useClipboard/useClipboard.ts +69 -0
  24. package/src/hooks/useControllableState/index.ts +4 -0
  25. package/src/hooks/useControllableState/useControllableState.test.ts +59 -0
  26. package/src/hooks/useControllableState/useControllableState.ts +49 -0
  27. package/src/hooks/useDebouncedValue/index.ts +1 -0
  28. package/src/hooks/useDebouncedValue/useDebouncedValue.test.ts +74 -0
  29. package/src/hooks/useDebouncedValue/useDebouncedValue.ts +29 -0
  30. package/src/hooks/useDisclosure/index.ts +5 -0
  31. package/src/hooks/useDisclosure/useDisclosure.test.ts +64 -0
  32. package/src/hooks/useDisclosure/useDisclosure.ts +62 -0
  33. package/src/hooks/useDocumentTitle/index.ts +4 -0
  34. package/src/hooks/useDocumentTitle/useDocumentTitle.test.ts +40 -0
  35. package/src/hooks/useDocumentTitle/useDocumentTitle.ts +58 -0
  36. package/src/hooks/useFocusManagement/index.ts +1 -0
  37. package/src/hooks/useFocusManagement/useFocusManagement.test.tsx +45 -0
  38. package/src/hooks/useFocusManagement/useFocusManagement.ts +77 -0
  39. package/src/hooks/useHover/index.ts +1 -0
  40. package/src/hooks/useHover/useHover.test.ts +45 -0
  41. package/src/hooks/useHover/useHover.ts +45 -0
  42. package/src/hooks/useInView/index.ts +1 -0
  43. package/src/hooks/useInView/useInView.test.ts +43 -0
  44. package/src/hooks/useInView/useInView.ts +28 -0
  45. package/src/hooks/useIntersectionObserver/index.ts +4 -0
  46. package/src/hooks/useIntersectionObserver/useIntersectionObserver.test.ts +75 -0
  47. package/src/hooks/useIntersectionObserver/useIntersectionObserver.ts +54 -0
  48. package/src/hooks/useIsMounted/index.ts +1 -0
  49. package/src/hooks/useIsMounted/useIsMounted.test.ts +25 -0
  50. package/src/hooks/useIsMounted/useIsMounted.ts +22 -0
  51. package/src/hooks/useIsomorphicLayoutEffect/index.ts +1 -0
  52. package/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.test.ts +19 -0
  53. package/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts +12 -0
  54. package/src/hooks/useLocalStorage/index.ts +4 -0
  55. package/src/hooks/useLocalStorage/useLocalStorage.test.ts +99 -0
  56. package/src/hooks/useLocalStorage/useLocalStorage.ts +109 -0
  57. package/src/hooks/useMediaQuery/index.ts +1 -0
  58. package/src/hooks/useMediaQuery/useMediaQuery.test.ts +63 -0
  59. package/src/hooks/useMediaQuery/useMediaQuery.ts +37 -0
  60. package/src/hooks/useNetworkStatus/index.ts +1 -0
  61. package/src/hooks/useNetworkStatus/useNetworkStatus.test.ts +53 -0
  62. package/src/hooks/useNetworkStatus/useNetworkStatus.ts +33 -0
  63. package/src/hooks/usePageVisibility/index.ts +1 -0
  64. package/src/hooks/usePageVisibility/usePageVisibility.test.ts +21 -0
  65. package/src/hooks/usePageVisibility/usePageVisibility.ts +31 -0
  66. package/src/hooks/usePagination/index.ts +6 -0
  67. package/src/hooks/usePagination/usePagination.test.ts +139 -0
  68. package/src/hooks/usePagination/usePagination.ts +153 -0
  69. package/src/hooks/usePrefersColorScheme/index.ts +4 -0
  70. package/src/hooks/usePrefersColorScheme/usePrefersColorScheme.test.ts +53 -0
  71. package/src/hooks/usePrefersColorScheme/usePrefersColorScheme.ts +21 -0
  72. package/src/hooks/usePrefersReducedMotion/index.ts +1 -0
  73. package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.test.ts +27 -0
  74. package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.ts +14 -0
  75. package/src/hooks/useResizeObserver/index.ts +4 -0
  76. package/src/hooks/useResizeObserver/useResizeObserver.test.ts +68 -0
  77. package/src/hooks/useResizeObserver/useResizeObserver.ts +58 -0
  78. package/src/hooks/useSessionStorage/index.ts +4 -0
  79. package/src/hooks/useSessionStorage/useSessionStorage.test.ts +54 -0
  80. package/src/hooks/useSessionStorage/useSessionStorage.ts +84 -0
  81. package/src/hooks/useThrottledCallback/index.ts +1 -0
  82. package/src/hooks/useThrottledCallback/useThrottledCallback.test.ts +75 -0
  83. package/src/hooks/useThrottledCallback/useThrottledCallback.ts +36 -0
  84. package/src/hooks/useToggle/index.ts +1 -0
  85. package/src/hooks/useToggle/useToggle.test.ts +40 -0
  86. package/src/hooks/useToggle/useToggle.ts +22 -0
  87. package/src/hooks/useWindowSize/index.ts +1 -0
  88. package/src/hooks/useWindowSize/useWindowSize.test.ts +23 -0
  89. package/src/hooks/useWindowSize/useWindowSize.ts +39 -0
  90. package/src/lib/utils.ts +25 -0
  91. package/src/ui/accordion/accordion-base.tsx +223 -0
  92. package/src/ui/accordion/accordion.test.tsx +146 -0
  93. package/src/ui/accordion/accordion.tsx +11 -0
  94. package/src/ui/accordion/animated/accordion-content-animated.tsx +46 -0
  95. package/src/ui/accordion/animated/accordion-root-animated.tsx +10 -0
  96. package/src/ui/accordion/animated/animations.ts +16 -0
  97. package/src/ui/accordion/animated/index.ts +7 -0
  98. package/src/ui/accordion/animated/types.ts +7 -0
  99. package/src/ui/accordion/index.ts +23 -0
  100. package/src/ui/accordion/types.ts +48 -0
  101. package/src/ui/accordion/variants.ts +115 -0
  102. package/src/ui/alert/alert-base.tsx +157 -0
  103. package/src/ui/alert/alert.test.tsx +150 -0
  104. package/src/ui/alert/alert.tsx +9 -0
  105. package/src/ui/alert/animated/alert-animated.tsx +20 -0
  106. package/src/ui/alert/animated/animations.ts +20 -0
  107. package/src/ui/alert/animated/index.ts +3 -0
  108. package/src/ui/alert/animated/types.ts +16 -0
  109. package/src/ui/alert/index.ts +22 -0
  110. package/src/ui/alert/types.ts +28 -0
  111. package/src/ui/alert/variants.ts +74 -0
  112. package/src/ui/avatar/animated/animations.ts +11 -0
  113. package/src/ui/avatar/animated/avatar-animated.tsx +25 -0
  114. package/src/ui/avatar/animated/index.ts +6 -0
  115. package/src/ui/avatar/animated/types.ts +16 -0
  116. package/src/ui/avatar/avatar-base.tsx +184 -0
  117. package/src/ui/avatar/avatar.test.tsx +51 -0
  118. package/src/ui/avatar/avatar.tsx +11 -0
  119. package/src/ui/avatar/index.ts +16 -0
  120. package/src/ui/avatar/types.ts +36 -0
  121. package/src/ui/avatar/variants.ts +52 -0
  122. package/src/ui/badge/animated/animations.ts +20 -0
  123. package/src/ui/badge/animated/badge-animated.tsx +28 -0
  124. package/src/ui/badge/animated/index.ts +5 -0
  125. package/src/ui/badge/animated/types.ts +18 -0
  126. package/src/ui/badge/badge-base.tsx +53 -0
  127. package/src/ui/badge/badge.test.tsx +48 -0
  128. package/src/ui/badge/badge.tsx +9 -0
  129. package/src/ui/badge/index.ts +5 -0
  130. package/src/ui/badge/types.ts +25 -0
  131. package/src/ui/badge/variants.ts +85 -0
  132. package/src/ui/breadcrumb/breadcrumb.test.tsx +62 -0
  133. package/src/ui/breadcrumb/breadcrumb.tsx +135 -0
  134. package/src/ui/breadcrumb/index.ts +28 -0
  135. package/src/ui/breadcrumb/types.ts +29 -0
  136. package/src/ui/breadcrumb/variants.ts +53 -0
  137. package/src/ui/buttons/animated/animations.ts +34 -0
  138. package/src/ui/buttons/animated/button-animated.tsx +70 -0
  139. package/src/ui/buttons/animated/index.ts +5 -0
  140. package/src/ui/buttons/animated/types.ts +29 -0
  141. package/src/ui/buttons/button-base.tsx +59 -0
  142. package/src/ui/buttons/button.test.tsx +480 -0
  143. package/src/ui/buttons/button.tsx +9 -0
  144. package/src/ui/buttons/index.ts +5 -0
  145. package/src/ui/buttons/types.ts +14 -0
  146. package/src/ui/buttons/variants.ts +77 -0
  147. package/src/ui/card/animated/animations.ts +32 -0
  148. package/src/ui/card/animated/card-animated.tsx +28 -0
  149. package/src/ui/card/animated/index.ts +12 -0
  150. package/src/ui/card/animated/types.ts +8 -0
  151. package/src/ui/card/card-base.tsx +146 -0
  152. package/src/ui/card/card.test.tsx +79 -0
  153. package/src/ui/card/card.tsx +11 -0
  154. package/src/ui/card/index.ts +21 -0
  155. package/src/ui/card/types.ts +42 -0
  156. package/src/ui/card/variants.ts +122 -0
  157. package/src/ui/divider/animated/animations.ts +27 -0
  158. package/src/ui/divider/animated/divider-animated.tsx +24 -0
  159. package/src/ui/divider/animated/index.ts +4 -0
  160. package/src/ui/divider/animated/types.ts +18 -0
  161. package/src/ui/divider/divider-base.tsx +80 -0
  162. package/src/ui/divider/divider.tsx +9 -0
  163. package/src/ui/divider/index.ts +14 -0
  164. package/src/ui/divider/types.ts +18 -0
  165. package/src/ui/divider/variants.ts +98 -0
  166. package/src/ui/drawer/animated/animations.ts +39 -0
  167. package/src/ui/drawer/animated/drawer-content-animated.tsx +101 -0
  168. package/src/ui/drawer/animated/index.ts +14 -0
  169. package/src/ui/drawer/animated/types.ts +18 -0
  170. package/src/ui/drawer/drawer-base.tsx +259 -0
  171. package/src/ui/drawer/drawer.test.tsx +132 -0
  172. package/src/ui/drawer/drawer.tsx +11 -0
  173. package/src/ui/drawer/index.ts +21 -0
  174. package/src/ui/drawer/types.ts +39 -0
  175. package/src/ui/drawer/variants.ts +122 -0
  176. package/src/ui/dropdown/dropdown.test.tsx +114 -0
  177. package/src/ui/dropdown/dropdown.tsx +179 -0
  178. package/src/ui/dropdown/index.ts +15 -0
  179. package/src/ui/dropdown/types.ts +68 -0
  180. package/src/ui/dropdown/variants.ts +138 -0
  181. package/src/ui/empty-state/animated/animations.ts +19 -0
  182. package/src/ui/empty-state/animated/empty-state-animated.tsx +23 -0
  183. package/src/ui/empty-state/animated/index.ts +7 -0
  184. package/src/ui/empty-state/animated/types.ts +26 -0
  185. package/src/ui/empty-state/empty-state-base.tsx +114 -0
  186. package/src/ui/empty-state/empty-state.tsx +9 -0
  187. package/src/ui/empty-state/index.ts +10 -0
  188. package/src/ui/empty-state/types.ts +19 -0
  189. package/src/ui/empty-state/variants.ts +51 -0
  190. package/src/ui/file-upload/file-upload.test.tsx +36 -0
  191. package/src/ui/file-upload/file-upload.tsx +119 -0
  192. package/src/ui/file-upload/index.ts +5 -0
  193. package/src/ui/file-upload/types.ts +21 -0
  194. package/src/ui/file-upload/variants.ts +29 -0
  195. package/src/ui/inputs/animated/animations.ts +36 -0
  196. package/src/ui/inputs/animated/index.ts +5 -0
  197. package/src/ui/inputs/animated/input-animated.tsx +124 -0
  198. package/src/ui/inputs/animated/types.ts +40 -0
  199. package/src/ui/inputs/index.ts +5 -0
  200. package/src/ui/inputs/input-base.tsx +114 -0
  201. package/src/ui/inputs/input.test.tsx +414 -0
  202. package/src/ui/inputs/input.tsx +8 -0
  203. package/src/ui/inputs/types.ts +18 -0
  204. package/src/ui/inputs/variants.ts +316 -0
  205. package/src/ui/modal/animated/animations.ts +29 -0
  206. package/src/ui/modal/animated/index.ts +5 -0
  207. package/src/ui/modal/animated/modal-content-animated.tsx +96 -0
  208. package/src/ui/modal/animated/types.ts +23 -0
  209. package/src/ui/modal/index.ts +21 -0
  210. package/src/ui/modal/modal-base.tsx +279 -0
  211. package/src/ui/modal/modal.test.tsx +129 -0
  212. package/src/ui/modal/modal.tsx +8 -0
  213. package/src/ui/modal/types.ts +31 -0
  214. package/src/ui/modal/variants.ts +109 -0
  215. package/src/ui/pagination/index.ts +13 -0
  216. package/src/ui/pagination/pagination.test.tsx +165 -0
  217. package/src/ui/pagination/pagination.tsx +237 -0
  218. package/src/ui/pagination/types.ts +66 -0
  219. package/src/ui/pagination/variants.ts +97 -0
  220. package/src/ui/progress/animated/animations.ts +9 -0
  221. package/src/ui/progress/animated/index.ts +17 -0
  222. package/src/ui/progress/animated/progress-animated.tsx +133 -0
  223. package/src/ui/progress/animated/types.ts +35 -0
  224. package/src/ui/progress/index.ts +10 -0
  225. package/src/ui/progress/progress-base.tsx +151 -0
  226. package/src/ui/progress/progress.test.tsx +84 -0
  227. package/src/ui/progress/progress.tsx +12 -0
  228. package/src/ui/progress/types.ts +33 -0
  229. package/src/ui/progress/variants.ts +105 -0
  230. package/src/ui/select/index.ts +25 -0
  231. package/src/ui/select/select.test.tsx +128 -0
  232. package/src/ui/select/select.tsx +221 -0
  233. package/src/ui/select/types.ts +77 -0
  234. package/src/ui/select/variants.ts +163 -0
  235. package/src/ui/skeleton/animated/animations.ts +15 -0
  236. package/src/ui/skeleton/animated/index.ts +20 -0
  237. package/src/ui/skeleton/animated/skeleton-animated.tsx +119 -0
  238. package/src/ui/skeleton/animated/types.ts +49 -0
  239. package/src/ui/skeleton/index.ts +24 -0
  240. package/src/ui/skeleton/skeleton-base.tsx +288 -0
  241. package/src/ui/skeleton/skeleton.tsx +8 -0
  242. package/src/ui/skeleton/types.ts +31 -0
  243. package/src/ui/skeleton/variants.ts +254 -0
  244. package/src/ui/slider/index.ts +22 -0
  245. package/src/ui/slider/slider.test.tsx +94 -0
  246. package/src/ui/slider/slider.tsx +728 -0
  247. package/src/ui/slider/types.ts +66 -0
  248. package/src/ui/slider/variants.ts +81 -0
  249. package/src/ui/spinner/animated/index.ts +5 -0
  250. package/src/ui/spinner/animated/spinner.test.tsx +41 -0
  251. package/src/ui/spinner/animated/spinner.tsx +143 -0
  252. package/src/ui/spinner/animated/types.ts +11 -0
  253. package/src/ui/spinner/animated/variants.ts +50 -0
  254. package/src/ui/stepper/index.ts +22 -0
  255. package/src/ui/stepper/stepper.test.tsx +183 -0
  256. package/src/ui/stepper/stepper.tsx +172 -0
  257. package/src/ui/stepper/types.ts +32 -0
  258. package/src/ui/stepper/variants.ts +69 -0
  259. package/src/ui/table/animated/animations.ts +9 -0
  260. package/src/ui/table/animated/index.ts +15 -0
  261. package/src/ui/table/animated/table-animated.tsx +15 -0
  262. package/src/ui/table/animated/types.ts +16 -0
  263. package/src/ui/table/index.ts +22 -0
  264. package/src/ui/table/table-base.tsx +197 -0
  265. package/src/ui/table/table.tsx +13 -0
  266. package/src/ui/table/types.ts +47 -0
  267. package/src/ui/table/variants.ts +105 -0
  268. package/src/ui/tabs/animated/animations.ts +48 -0
  269. package/src/ui/tabs/animated/index.ts +8 -0
  270. package/src/ui/tabs/animated/tabs-content-animated.tsx +46 -0
  271. package/src/ui/tabs/animated/types.ts +24 -0
  272. package/src/ui/tabs/index.ts +10 -0
  273. package/src/ui/tabs/tabs-base.tsx +185 -0
  274. package/src/ui/tabs/tabs.test.tsx +53 -0
  275. package/src/ui/tabs/tabs.tsx +2 -0
  276. package/src/ui/tabs/types.ts +88 -0
  277. package/src/ui/tabs/variants.ts +70 -0
  278. package/src/ui/toast/animated/animations.ts +17 -0
  279. package/src/ui/toast/animated/index.ts +9 -0
  280. package/src/ui/toast/animated/toast-animated.tsx +96 -0
  281. package/src/ui/toast/animated/types.ts +13 -0
  282. package/src/ui/toast/index.ts +26 -0
  283. package/src/ui/toast/toast-base.tsx +231 -0
  284. package/src/ui/toast/toast.test.tsx +102 -0
  285. package/src/ui/toast/toast.tsx +13 -0
  286. package/src/ui/toast/types.ts +57 -0
  287. package/src/ui/toast/variants.ts +73 -0
  288. package/src/ui/toggle/animated/animations.ts +9 -0
  289. package/src/ui/toggle/animated/index.ts +7 -0
  290. package/src/ui/toggle/animated/toggle-animated.tsx +76 -0
  291. package/src/ui/toggle/animated/types.ts +13 -0
  292. package/src/ui/toggle/index.ts +5 -0
  293. package/src/ui/toggle/toggle-base.tsx +70 -0
  294. package/src/ui/toggle/toggle.test.tsx +44 -0
  295. package/src/ui/toggle/toggle.tsx +9 -0
  296. package/src/ui/toggle/types.ts +18 -0
  297. package/src/ui/toggle/variants.ts +84 -0
  298. package/src/ui/tooltip/animated/animations.ts +16 -0
  299. package/src/ui/tooltip/animated/index.ts +10 -0
  300. package/src/ui/tooltip/animated/tooltip-content-animated.tsx +47 -0
  301. package/src/ui/tooltip/animated/types.ts +19 -0
  302. package/src/ui/tooltip/index.ts +17 -0
  303. package/src/ui/tooltip/tooltip-base.tsx +152 -0
  304. package/src/ui/tooltip/tooltip.test.tsx +84 -0
  305. package/src/ui/tooltip/tooltip.tsx +8 -0
  306. package/src/ui/tooltip/types.ts +57 -0
  307. package/src/ui/tooltip/variants.ts +61 -0
@@ -0,0 +1,36 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type {
3
+ ComponentPropsWithRef,
4
+ ComponentPropsWithoutRef,
5
+ ElementType,
6
+ } from "react";
7
+
8
+ import type { avatarVariants } from "./variants";
9
+
10
+ type AvatarVariantProps = VariantProps<typeof avatarVariants>;
11
+
12
+ export interface AvatarBaseProps extends ComponentPropsWithRef<"span"> {
13
+ size?: AvatarVariantProps["size"];
14
+ appearance?: AvatarVariantProps["appearance"];
15
+ as?: ElementType;
16
+ }
17
+
18
+ export type AvatarProps = Omit<AvatarBaseProps, "as">;
19
+
20
+ export type AvatarImageProps = ComponentPropsWithoutRef<"img">;
21
+
22
+ export type AvatarFallbackProps = ComponentPropsWithoutRef<"span"> & {
23
+ delayMs?: number;
24
+ };
25
+
26
+ export type AvatarGroupProps = ComponentPropsWithoutRef<"div"> & {
27
+ /** Max avatars before +N overflow label */
28
+ max?: number;
29
+ };
30
+
31
+ export type ImageStatus = "idle" | "loaded" | "error";
32
+
33
+ export type AvatarCtx = {
34
+ imageStatus: ImageStatus;
35
+ setImageStatus: (v: ImageStatus) => void;
36
+ };
@@ -0,0 +1,52 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ export const avatarVariants = cva(
4
+ "relative flex shrink-0 overflow-hidden rounded-full border border-white/10 bg-white/10 text-slate-200",
5
+ {
6
+ variants: {
7
+ appearance: {
8
+ default: "border-white/10 bg-white/10 text-slate-200",
9
+ muted: "border-black/40 bg-slate-950 dark:border-white/10 dark:bg-slate-950/40 text-slate-200",
10
+ sky: "border-sky-600 bg-sky-600/[0.03] text-slate-200",
11
+ rose: "border-rose-600 bg-rose-600/[0.03] text-slate-200",
12
+ purple: "border-purple-600 bg-purple-600/[0.03] text-slate-200",
13
+ pink: "border-pink-600 bg-pink-600/[0.03] text-slate-200",
14
+ orange: "border-orange-600 bg-orange-600/[0.03] text-slate-200",
15
+ yellow: "border-yellow-600 bg-yellow-600/[0.03] text-slate-200",
16
+ teal: "border-teal-600 bg-teal-600/[0.03] text-slate-200",
17
+ indigo: "border-indigo-600 bg-indigo-600/[0.03] text-slate-200",
18
+ emerald: "border-emerald-600 bg-emerald-600/[0.03] text-slate-200",
19
+ gray: "border-gray-600 bg-gray-600/[0.03] text-slate-200",
20
+ amber: "border-amber-600 bg-amber-600/[0.03] text-slate-200",
21
+ violet: "border-violet-600 bg-violet-600/[0.03] text-slate-200",
22
+ "gradient-blue": "bg-linear-to-r from-blue-950/70 to-purple-950/70 backdrop-blur-xl text-slate-200",
23
+ "gradient-green": "bg-linear-to-r from-green-950/70 to-lime-950/70 backdrop-blur-xl text-slate-200",
24
+ "gradient-red": "bg-linear-to-r from-red-950/70 to-pink-950/70 backdrop-blur-xl text-slate-200",
25
+ "gradient-yellow": "bg-linear-to-r from-yellow-950/70 to-orange-950/70 backdrop-blur-xl text-slate-200",
26
+ "gradient-purple": "bg-linear-to-r from-purple-950/70 to-pink-950/70 backdrop-blur-xl text-slate-200",
27
+ "gradient-teal": "bg-linear-to-r from-teal-950/70 to-cyan-950/70 backdrop-blur-xl text-slate-200",
28
+ "gradient-indigo": "bg-linear-to-r from-indigo-950/70 to-purple-950/70 backdrop-blur-xl text-slate-200",
29
+ "gradient-pink": "bg-linear-to-r from-pink-950/70 to-rose-950/70 backdrop-blur-xl text-slate-200",
30
+ "gradient-orange": "bg-linear-to-r from-orange-950/70 to-red-950/70 backdrop-blur-xl text-slate-200",
31
+ },
32
+ size: {
33
+ sm: "size-8 text-xs",
34
+ md: "size-10 text-sm",
35
+ lg: "size-12 text-base",
36
+ xl: "size-16 text-lg",
37
+ },
38
+ },
39
+ defaultVariants: {
40
+ size: "md",
41
+ appearance: "default",
42
+ },
43
+ },
44
+ );
45
+
46
+ export const avatarImageVariants = cva("aspect-square size-full object-cover");
47
+
48
+ export const avatarFallbackVariants = cva(
49
+ "flex size-full items-center justify-center bg-slate-800 font-medium text-slate-100",
50
+ );
51
+
52
+ export const avatarGroupVariants = cva("flex items-center [&_[data-slot=avatar]]:-ml-2 [&_[data-slot=avatar]]:ring-2 [&_[data-slot=avatar]]:ring-slate-950 [&_[data-slot=avatar]]:first:ml-0");
@@ -0,0 +1,20 @@
1
+ import { BadgeAnimationPresets } from "./types";
2
+
3
+ export const badgeAnimationPresets: BadgeAnimationPresets = {
4
+ none: {},
5
+ pop: {
6
+ initial: { scale: 0.92, opacity: 0 },
7
+ animate: { scale: 1, opacity: 1 },
8
+ transition: { type: "spring", stiffness: 520, damping: 28 },
9
+ },
10
+ bounce: {
11
+ whileHover: { y: -2, scale: 1.04 },
12
+ whileTap: { scale: 0.96 },
13
+ transition: { type: "spring", bounce: 0.45, stiffness: 420, damping: 18 },
14
+ },
15
+ fade: {
16
+ initial: { opacity: 0 },
17
+ animate: { opacity: 1 },
18
+ transition: { duration: 0.2 },
19
+ },
20
+ };
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+
5
+ import { badgeAnimationPresets } from "./animations";
6
+ import { BadgeBase } from "../badge-base";
7
+ import type { BadgeBaseProps } from "../types";
8
+ import type { BadgeAnimatedProps } from "./types";
9
+
10
+ export function BadgeAnimated({
11
+ animation = "none",
12
+ ...props
13
+ }: BadgeAnimatedProps) {
14
+ const motionProps = badgeAnimationPresets[animation];
15
+
16
+ return (
17
+ <BadgeBase
18
+ {...({
19
+ as: motion.span,
20
+ initial: animation === "none" ? false : undefined,
21
+ ...motionProps,
22
+ ...props,
23
+ } as BadgeBaseProps)}
24
+ />
25
+ );
26
+ }
27
+
28
+ BadgeAnimated.displayName = "BadgeAnimated";
@@ -0,0 +1,5 @@
1
+ "use client";
2
+
3
+ export { BadgeAnimated } from "./badge-animated";
4
+ export type { BadgeAnimatedProps, BadgeAnimation, BadgeAnimationPresets } from "./types";
5
+ export { badgeAnimationPresets } from "./animations";
@@ -0,0 +1,18 @@
1
+ import { HTMLMotionProps } from "framer-motion";
2
+ import type { BadgeBaseProps } from "../types";
3
+
4
+ export type BadgeAnimatedProps = Omit<BadgeBaseProps, "as"> & {
5
+ animation?: BadgeAnimation;
6
+ };
7
+ export type BadgeAnimation = "none" | "pop" | "bounce" | "fade";
8
+
9
+
10
+ type BadgePresetMotionProps = Pick<
11
+ HTMLMotionProps<"span">,
12
+ "style" | "transition" | "whileHover" | "whileTap" | "animate" | "initial"
13
+ >;
14
+
15
+ export type BadgeAnimationPresets = Record<
16
+ BadgeAnimation,
17
+ BadgePresetMotionProps
18
+ >;
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { HiXMark } from "react-icons/hi2";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ import type { BadgeBaseProps } from "./types";
8
+ import { badgeCloseButtonVariants, badgeVariants } from "./variants";
9
+
10
+ export function BadgeBase({
11
+ className,
12
+ appearance,
13
+ size,
14
+ shape,
15
+ closable = false,
16
+ onClose,
17
+ closeLabel = "Remove",
18
+ children,
19
+ ref,
20
+ "aria-label": ariaLabel,
21
+ as: Wrapper = "span",
22
+ ...rest
23
+ }: BadgeBaseProps) {
24
+ const isDot = shape === "dot";
25
+ const resolvedAriaLabel =
26
+ ariaLabel ?? (isDot ? "Status indicator" : undefined);
27
+
28
+ return (
29
+ <Wrapper
30
+ ref={ref}
31
+ role="status"
32
+ data-slot="badge"
33
+ aria-label={resolvedAriaLabel}
34
+ className={cn(badgeVariants({ appearance, size, shape }), className)}
35
+ {...rest}
36
+ >
37
+ {!isDot ? children : null}
38
+ {closable ? (
39
+ <button
40
+ type="button"
41
+ data-slot="badge-close"
42
+ aria-label={closeLabel}
43
+ onClick={onClose}
44
+ className={badgeCloseButtonVariants({ size })}
45
+ >
46
+ <HiXMark className="size-3.5" aria-hidden />
47
+ </button>
48
+ ) : null}
49
+ </Wrapper>
50
+ );
51
+ }
52
+
53
+ BadgeBase.displayName = "Badge";
@@ -0,0 +1,48 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { describe, expect, it, vi } from "vitest";
5
+
6
+ import { Badge } from "./badge";
7
+
8
+ describe("Badge", () => {
9
+ it("should expose displayName", () => {
10
+ expect(Badge.displayName).toBe("Badge");
11
+ });
12
+
13
+ it("should stamp data-slot", () => {
14
+ render(<Badge>Tag</Badge>);
15
+ const root = document.querySelector('[data-slot="badge"]');
16
+ expect(root).toBeTruthy();
17
+ expect(root?.getAttribute("data-slot")).toBe("badge");
18
+ });
19
+
20
+ it("should apply secondary appearance", () => {
21
+ render(<Badge appearance="secondary">S</Badge>);
22
+ const root = document.querySelector('[data-slot="badge"]') as HTMLElement;
23
+ expect(root.className).toMatch(/bg-slate-800/);
24
+ });
25
+
26
+ it("should call onClose when closable", async () => {
27
+ const user = userEvent.setup();
28
+ const onClose = vi.fn();
29
+ render(
30
+ <Badge closable onClose={onClose}>
31
+ X
32
+ </Badge>,
33
+ );
34
+ await user.click(screen.getByRole("button", { name: "Remove" }));
35
+ expect(onClose).toHaveBeenCalledTimes(1);
36
+ });
37
+
38
+ it("should forward ref", () => {
39
+ const ref = createRef<HTMLSpanElement>();
40
+ render(<Badge ref={ref}>R</Badge>);
41
+ expect(ref.current?.getAttribute("data-slot")).toBe("badge");
42
+ });
43
+
44
+ it("should set aria-label for dot variant by default", () => {
45
+ render(<Badge shape="dot" appearance="emerald" />);
46
+ expect(screen.getByLabelText("Status indicator")).toBeInTheDocument();
47
+ });
48
+ });
@@ -0,0 +1,9 @@
1
+ // badge.tsx — default static entry (no framer-motion)
2
+ import { BadgeBase } from "./badge-base";
3
+ import type { BadgeProps } from "./types";
4
+
5
+ export function Badge(props: BadgeProps) {
6
+ return <BadgeBase {...props} />;
7
+ }
8
+
9
+ Badge.displayName = "Badge";
@@ -0,0 +1,5 @@
1
+ "use client";
2
+
3
+ export { Badge } from "./badge";
4
+ export type { BadgeBaseProps, BadgeProps, BadgeVariantProps } from "./types";
5
+ export { badgeVariants, badgeCloseButtonVariants } from "./variants";
@@ -0,0 +1,25 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type {
3
+ ComponentPropsWithRef,
4
+ ElementType,
5
+ MouseEventHandler,
6
+ ReactNode,
7
+ } from "react";
8
+
9
+ import type { badgeVariants } from "./variants";
10
+
11
+ export type BadgeVariantProps = VariantProps<typeof badgeVariants>;
12
+
13
+ export interface BadgeBaseProps extends ComponentPropsWithRef<"span"> {
14
+ appearance?: BadgeVariantProps["appearance"];
15
+ size?: BadgeVariantProps["size"];
16
+ shape?: BadgeVariantProps["shape"];
17
+ closable?: boolean;
18
+ onClose?: MouseEventHandler<HTMLButtonElement>;
19
+ closeLabel?: string;
20
+ children?: ReactNode;
21
+ "aria-label"?: string;
22
+ as?: ElementType;
23
+ }
24
+
25
+ export type BadgeProps = Omit<BadgeBaseProps, "as">;
@@ -0,0 +1,85 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ /**
4
+ * Tailwind class maps mirroring `buttons/variants.ts` appearance tokens.
5
+ * Reused by primitives that should stay visually aligned with Button.
6
+ */
7
+ export const buttonLikeSolidAppearances = {
8
+ default: "bg-slate-50 text-slate-950 shadow-[0_1px_2px_rgba(15,23,42,0.12)]",
9
+ secondary: "bg-slate-800 text-slate-50",
10
+ destructive: "bg-rose-600 text-white",
11
+ outline: "border border-white/10 bg-white/5 text-slate-50",
12
+ ghost: "bg-transparent text-slate-200",
13
+ glass: "border border-white/15 bg-white/10 text-white backdrop-blur-md",
14
+ emerald: "bg-emerald-600 text-white",
15
+ indigo: "bg-indigo-600 text-white",
16
+ purple: "bg-purple-600 text-white",
17
+ pink: "bg-pink-600 text-white",
18
+ rose: "bg-rose-600 text-white",
19
+ sky: "bg-sky-600 text-white",
20
+ teal: "bg-teal-600 text-white",
21
+ yellow: "bg-yellow-600 text-white",
22
+ orange: "bg-orange-600 text-white",
23
+ "gradient-blue": "bg-gradient-to-r from-blue-600 to-purple-600 text-white",
24
+ "gradient-green": "bg-gradient-to-r from-green-600 to-lime-600 text-white",
25
+ "gradient-red": "bg-gradient-to-r from-red-600 to-pink-600 text-white",
26
+ "gradient-yellow":
27
+ "bg-gradient-to-r from-yellow-600 to-orange-600 text-white",
28
+ "gradient-purple": "bg-gradient-to-r from-purple-600 to-pink-600 text-white",
29
+ "gradient-teal": "bg-gradient-to-r from-teal-600 to-cyan-600 text-white",
30
+ "gradient-indigo":
31
+ "bg-gradient-to-r from-indigo-600 to-purple-600 text-white",
32
+ "gradient-pink": "bg-gradient-to-r from-pink-600 to-rose-600 text-white",
33
+ "gradient-orange": "bg-gradient-to-r from-orange-600 to-red-600 text-white",
34
+ } as const;
35
+
36
+ export type ButtonLikeSolidAppearance = keyof typeof buttonLikeSolidAppearances;
37
+
38
+ const badgeAppearances = {
39
+ ...buttonLikeSolidAppearances,
40
+ outline: "border border-white/15 bg-transparent text-slate-200 shadow-none",
41
+ ghost: "bg-transparent text-slate-300 shadow-none",
42
+ } as const;
43
+
44
+ export const badgeVariants = cva(
45
+ [
46
+ "inline-flex max-w-full items-center justify-center gap-1 font-medium",
47
+ "whitespace-nowrap ring-offset-slate-950 transition-colors",
48
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-300 focus-visible:ring-offset-2",
49
+ "select-none",
50
+ ],
51
+ {
52
+ variants: {
53
+ appearance: badgeAppearances,
54
+ size: {
55
+ sm: "h-6 min-h-6 px-2 text-[0.65rem] leading-none",
56
+ md: "h-7 min-h-7 px-2.5 text-xs leading-none",
57
+ lg: "h-8 min-h-8 px-3 text-sm leading-none",
58
+ },
59
+ shape: {
60
+ pill: "rounded-full",
61
+ square: "rounded-md",
62
+ dot: "h-2.5 min-h-2.5 w-2.5 min-w-2.5 rounded-full p-0 px-0 text-[0]",
63
+ },
64
+ },
65
+ defaultVariants: {
66
+ appearance: "default",
67
+ size: "md",
68
+ shape: "pill",
69
+ },
70
+ },
71
+ );
72
+
73
+ export const badgeCloseButtonVariants = cva(
74
+ "inline-flex shrink-0 items-center justify-center rounded-md p-0.5 text-current opacity-70 transition hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40",
75
+ {
76
+ variants: {
77
+ size: {
78
+ sm: "size-4",
79
+ md: "size-5",
80
+ lg: "size-6",
81
+ },
82
+ },
83
+ defaultVariants: { size: "md" },
84
+ },
85
+ );
@@ -0,0 +1,62 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import {
6
+ Breadcrumb,
7
+ BreadcrumbItem,
8
+ BreadcrumbLink,
9
+ BreadcrumbList,
10
+ BreadcrumbPage,
11
+ BreadcrumbSeparator,
12
+ } from "./breadcrumb";
13
+
14
+ describe("Breadcrumb", () => {
15
+ it("should expose displayName on primitives", () => {
16
+ expect(Breadcrumb.displayName).toBe("Breadcrumb");
17
+ expect(BreadcrumbList.displayName).toBe("BreadcrumbList");
18
+ expect(BreadcrumbItem.displayName).toBe("BreadcrumbItem");
19
+ expect(BreadcrumbLink.displayName).toBe("BreadcrumbLink");
20
+ expect(BreadcrumbPage.displayName).toBe("BreadcrumbPage");
21
+ expect(BreadcrumbSeparator.displayName).toBe("BreadcrumbSeparator");
22
+ });
23
+
24
+ it("should stamp data-slot on breadcrumb root", () => {
25
+ render(
26
+ <Breadcrumb>
27
+ <BreadcrumbList>
28
+ <BreadcrumbItem>
29
+ <BreadcrumbLink href="/">Home</BreadcrumbLink>
30
+ </BreadcrumbItem>
31
+ </BreadcrumbList>
32
+ </Breadcrumb>,
33
+ );
34
+ expect(document.querySelector('[data-slot="breadcrumb"]')).toBeTruthy();
35
+ });
36
+
37
+ it("should mark current page with aria-current", () => {
38
+ render(
39
+ <Breadcrumb>
40
+ <BreadcrumbList>
41
+ <BreadcrumbItem>
42
+ <BreadcrumbPage>Settings</BreadcrumbPage>
43
+ </BreadcrumbItem>
44
+ </BreadcrumbList>
45
+ </Breadcrumb>,
46
+ );
47
+ expect(screen.getByText("Settings")).toHaveAttribute(
48
+ "aria-current",
49
+ "page",
50
+ );
51
+ });
52
+
53
+ it("should forward ref on Breadcrumb", () => {
54
+ const ref = createRef<HTMLElement>();
55
+ render(
56
+ <Breadcrumb ref={ref}>
57
+ <BreadcrumbList />
58
+ </Breadcrumb>,
59
+ );
60
+ expect(ref.current?.getAttribute("data-slot")).toBe("breadcrumb");
61
+ });
62
+ });
@@ -0,0 +1,135 @@
1
+ "use client";
2
+
3
+ import type { Ref } from "react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ import type {
8
+ BreadcrumbItemProps,
9
+ BreadcrumbLinkProps,
10
+ BreadcrumbListProps,
11
+ BreadcrumbPageProps,
12
+ BreadcrumbProps,
13
+ BreadcrumbSeparatorProps,
14
+ } from "./types";
15
+ import {
16
+ breadcrumbItemVariants,
17
+ breadcrumbLinkVariants,
18
+ breadcrumbListVariants,
19
+ breadcrumbNavVariants,
20
+ breadcrumbPageVariants,
21
+ breadcrumbSeparatorVariants,
22
+ } from "./variants";
23
+
24
+ export function Breadcrumb({
25
+ className,
26
+ ref,
27
+ "aria-label": ariaLabel = "Breadcrumb",
28
+ ...rest
29
+ }: BreadcrumbProps) {
30
+ return (
31
+ <nav
32
+ ref={ref}
33
+ data-slot="breadcrumb"
34
+ aria-label={ariaLabel}
35
+ className={cn(className)}
36
+ {...rest}
37
+ />
38
+ );
39
+ }
40
+
41
+ Breadcrumb.displayName = "Breadcrumb";
42
+
43
+ export function BreadcrumbList({
44
+ className,
45
+ ref,
46
+ ...rest
47
+ }: BreadcrumbListProps & { ref?: Ref<HTMLOListElement> }) {
48
+ return (
49
+ <ol
50
+ ref={ref}
51
+ data-slot="breadcrumb-list"
52
+ className={cn(breadcrumbListVariants(), className)}
53
+ {...rest}
54
+ />
55
+ );
56
+ }
57
+
58
+ BreadcrumbList.displayName = "BreadcrumbList";
59
+
60
+ export function BreadcrumbItem({
61
+ className,
62
+ ref,
63
+ ...rest
64
+ }: BreadcrumbItemProps & { ref?: Ref<HTMLLIElement> }) {
65
+ return (
66
+ <li
67
+ ref={ref}
68
+ data-slot="breadcrumb-item"
69
+ className={cn(breadcrumbItemVariants(), className)}
70
+ {...rest}
71
+ />
72
+ );
73
+ }
74
+
75
+ BreadcrumbItem.displayName = "BreadcrumbItem";
76
+
77
+ export function BreadcrumbLink({
78
+ className,
79
+ ref,
80
+ appearance = "default",
81
+ ...rest
82
+ }: BreadcrumbLinkProps) {
83
+ return (
84
+ <a
85
+ ref={ref}
86
+ data-slot="breadcrumb-link"
87
+ className={cn(breadcrumbLinkVariants(), breadcrumbNavVariants({ appearance }), className)}
88
+ {...rest}
89
+ />
90
+ );
91
+ }
92
+
93
+ BreadcrumbLink.displayName = "BreadcrumbLink";
94
+
95
+ export function BreadcrumbPage({
96
+ className,
97
+ ref,
98
+ appearance = "default",
99
+ ...rest
100
+ }: BreadcrumbPageProps) {
101
+ return (
102
+ <span
103
+ ref={ref}
104
+ data-slot="breadcrumb-page"
105
+ aria-current="page"
106
+ className={cn(breadcrumbPageVariants(), breadcrumbNavVariants({ appearance }), className)}
107
+ {...rest}
108
+ />
109
+ );
110
+ }
111
+
112
+ BreadcrumbPage.displayName = "BreadcrumbPage";
113
+
114
+ export function BreadcrumbSeparator({
115
+ className,
116
+ size,
117
+ children = "/",
118
+ ref,
119
+ ...rest
120
+ }: BreadcrumbSeparatorProps & { ref?: Ref<HTMLSpanElement> }) {
121
+ return (
122
+ <span
123
+ ref={ref}
124
+ data-slot="breadcrumb-separator"
125
+ aria-hidden
126
+ role="presentation"
127
+ className={cn(breadcrumbSeparatorVariants({ size }), className)}
128
+ {...rest}
129
+ >
130
+ {children}
131
+ </span>
132
+ );
133
+ }
134
+
135
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ export {
4
+ Breadcrumb,
5
+ BreadcrumbItem,
6
+ BreadcrumbLink,
7
+ BreadcrumbList,
8
+ BreadcrumbPage,
9
+ BreadcrumbSeparator,
10
+ } from "./breadcrumb";
11
+ export type {
12
+ SeparatorVariantProps,
13
+ BreadcrumbItemProps,
14
+ BreadcrumbLinkProps,
15
+ BreadcrumbListProps,
16
+ BreadcrumbPageProps,
17
+ BreadcrumbProps,
18
+ BreadcrumbSeparatorProps,
19
+ BreadcrumbAppearance,
20
+ } from "./types";
21
+ export {
22
+ breadcrumbItemVariants,
23
+ breadcrumbLinkVariants,
24
+ breadcrumbListVariants,
25
+ breadcrumbNavVariants,
26
+ breadcrumbPageVariants,
27
+ breadcrumbSeparatorVariants,
28
+ } from "./variants";
@@ -0,0 +1,29 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { ComponentPropsWithoutRef, ReactNode, Ref } from "react";
3
+
4
+ import type { breadcrumbSeparatorVariants, breadcrumbNavVariants, breadcrumbLinkVariants, breadcrumbPageVariants } from "./variants";
5
+
6
+ export type BreadcrumbProps = ComponentPropsWithoutRef<"nav"> & {
7
+ "aria-label"?: string;
8
+ } & { ref?: Ref<HTMLElement> };
9
+
10
+ export type BreadcrumbListProps = ComponentPropsWithoutRef<"ol">;
11
+
12
+ export type BreadcrumbItemProps = ComponentPropsWithoutRef<"li">;
13
+
14
+ export type BreadcrumbLinkProps = ComponentPropsWithoutRef<"a"> & VariantProps<typeof breadcrumbLinkVariants> & {
15
+ appearance?: BreadcrumbAppearance;
16
+ } & { ref?: Ref<HTMLAnchorElement> };
17
+
18
+ export type BreadcrumbPageProps = ComponentPropsWithoutRef<"span"> & VariantProps<typeof breadcrumbPageVariants> & {
19
+ appearance?: BreadcrumbAppearance;
20
+ } & { ref?: Ref<HTMLSpanElement> };
21
+
22
+ export type SeparatorVariantProps = VariantProps<typeof breadcrumbSeparatorVariants>;
23
+
24
+ export type BreadcrumbAppearance = NonNullable<VariantProps<typeof breadcrumbNavVariants>["appearance"]>;
25
+
26
+ export type BreadcrumbSeparatorProps = ComponentPropsWithoutRef<"span"> &
27
+ SeparatorVariantProps & {
28
+ children?: ReactNode;
29
+ };