@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,66 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { ComponentPropsWithoutRef, ReactNode, RefObject } from "react";
3
+
4
+ import type {
5
+ sliderRangeVariants,
6
+ sliderRootVariants,
7
+ sliderThumbVariants,
8
+ sliderTrackVariants,
9
+ } from "./variants";
10
+
11
+ type SliderRootVariantProps = VariantProps<typeof sliderRootVariants>;
12
+ type SliderThumbVariantProps = VariantProps<typeof sliderThumbVariants>;
13
+
14
+ export type SliderProps = SliderRootVariantProps & {
15
+ min?: number;
16
+ max?: number;
17
+ step?: number;
18
+ value?: number;
19
+ defaultValue?: number;
20
+ onValueChange?: (value: number) => void;
21
+ disabled?: boolean;
22
+ appearance?: VariantProps<typeof sliderRangeVariants>["appearance"];
23
+ /** Label for the slider group (accessibility). */
24
+ "aria-label"?: string;
25
+ /** Optional visible label id */
26
+ "aria-labelledby"?: string;
27
+ children?: ReactNode;
28
+ } & Omit<ComponentPropsWithoutRef<"div">, "children" | "defaultValue">;
29
+
30
+ export type SliderTrackProps = ComponentPropsWithoutRef<"div"> &
31
+ VariantProps<typeof sliderTrackVariants>;
32
+
33
+ export type SliderRangeProps = ComponentPropsWithoutRef<"div"> &
34
+ VariantProps<typeof sliderRangeVariants>;
35
+
36
+ export type SliderThumbProps = SliderThumbVariantProps &
37
+ ComponentPropsWithoutRef<"div">;
38
+
39
+ export type RangeSliderProps = SliderRootVariantProps & {
40
+ min?: number;
41
+ max?: number;
42
+ step?: number;
43
+ value?: [number, number];
44
+ defaultValue?: [number, number];
45
+ onValueChange?: (value: [number, number]) => void;
46
+ disabled?: boolean;
47
+ appearance?: VariantProps<typeof sliderRangeVariants>["appearance"];
48
+ "aria-label"?: string;
49
+ "aria-labelledby"?: string;
50
+ } & Omit<ComponentPropsWithoutRef<"div">, "children" | "defaultValue">;
51
+
52
+ export type SliderAppearance = NonNullable<
53
+ Parameters<typeof sliderRangeVariants>[0]
54
+ >["appearance"];
55
+
56
+ export type SliderCtx = {
57
+ min: number;
58
+ max: number;
59
+ step: number;
60
+ value: number;
61
+ setValue: (next: number) => void;
62
+ disabled: boolean;
63
+ size: NonNullable<SliderProps["size"]>;
64
+ appearance: SliderAppearance;
65
+ trackRef: RefObject<HTMLDivElement | null>;
66
+ };
@@ -0,0 +1,81 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ export const sliderRootVariants = cva("w-full select-none touch-none", {
4
+ variants: {
5
+ size: {
6
+ sm: "py-2",
7
+ md: "py-2.5",
8
+ lg: "py-3",
9
+ },
10
+ },
11
+ defaultVariants: {
12
+ size: "md",
13
+ },
14
+ });
15
+
16
+ export const sliderTrackVariants = cva(
17
+ "relative h-2 w-full shrink-0 overflow-hidden rounded-full bg-white/10",
18
+ {
19
+ variants: {
20
+ size: {
21
+ sm: "h-1.5",
22
+ md: "h-2",
23
+ lg: "h-2.5",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ size: "md",
28
+ },
29
+ },
30
+ );
31
+
32
+ export const sliderRangeVariants = cva(
33
+ "absolute h-full rounded-full bg-gradient-to-r from-violet-500 to-indigo-400",
34
+ {
35
+ variants: {
36
+ appearance: {
37
+ default: "from-violet-500 to-indigo-400",
38
+ sky: "from-sky-500 to-indigo-400",
39
+ rose: "from-rose-500 to-indigo-400",
40
+ purple: "from-purple-500 to-indigo-400",
41
+ pink: "from-pink-500 to-indigo-400",
42
+ orange: "from-orange-500 to-indigo-400",
43
+ yellow: "from-yellow-500 to-indigo-400",
44
+ teal: "from-teal-500 to-indigo-400",
45
+ indigo: "from-indigo-500 to-indigo-400",
46
+ emerald: "from-emerald-500 to-teal-400",
47
+ amber: "from-amber-500 to-orange-400",
48
+ gray: "from-gray-500 to-indigo-400",
49
+ violet: "from-violet-500 to-indigo-400",
50
+ "gradient-blue": "from-blue-500 to-indigo-400",
51
+ "gradient-green": "from-green-500 to-indigo-400",
52
+ "gradient-red": "from-red-500 to-indigo-400",
53
+ "gradient-yellow": "from-yellow-500 to-indigo-400",
54
+ "gradient-purple": "from-purple-500 to-indigo-400",
55
+ "gradient-teal": "from-teal-500 to-indigo-400",
56
+ "gradient-indigo": "from-indigo-500 to-indigo-400",
57
+ "gradient-pink": "from-pink-500 to-indigo-400",
58
+ "gradient-orange": "from-orange-500 to-indigo-400",
59
+ },
60
+ },
61
+ defaultVariants: {
62
+ appearance: "default",
63
+ },
64
+ },
65
+ );
66
+
67
+ export const sliderThumbVariants = cva(
68
+ "block size-4 rounded-full border border-white/20 bg-white shadow-md ring-offset-2 ring-offset-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40 disabled:pointer-events-none disabled:opacity-40",
69
+ {
70
+ variants: {
71
+ size: {
72
+ sm: "size-3.5",
73
+ md: "size-4",
74
+ lg: "size-5",
75
+ },
76
+ },
77
+ defaultVariants: {
78
+ size: "md",
79
+ },
80
+ },
81
+ );
@@ -0,0 +1,5 @@
1
+ "use client";
2
+
3
+ export { Spinner } from "./spinner";
4
+ export type { SpinnerProps } from "./types";
5
+ export { spinnerVariants } from "./variants";
@@ -0,0 +1,41 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { Spinner } from "./spinner";
6
+
7
+ describe("Spinner", () => {
8
+ it("should expose displayName", () => {
9
+ expect(Spinner.displayName).toBe("Spinner");
10
+ });
11
+
12
+ it("should stamp data-slot and status role on the root", () => {
13
+ render(<Spinner />);
14
+ const root = document.querySelector('[data-slot="spinner"]');
15
+ expect(root).toBeTruthy();
16
+ expect(screen.getByRole("status", { name: "Loading" })).toBe(root);
17
+ });
18
+
19
+ it("should honor a custom aria-label", () => {
20
+ render(<Spinner aria-label="Saving" />);
21
+ expect(screen.getByRole("status", { name: "Saving" })).toBeInTheDocument();
22
+ });
23
+
24
+ it.each(["ring", "dots", "pulse", "bars"] as const)(
25
+ "should render without throwing for variant=%s",
26
+ (variant) => {
27
+ const { unmount } = render(<Spinner variant={variant} />);
28
+ expect(screen.getByRole("status")).toBeVisible();
29
+ unmount();
30
+ },
31
+ );
32
+
33
+ describe("ref forwarding", () => {
34
+ it("should attach ref to the root span", () => {
35
+ const ref = createRef<HTMLSpanElement>();
36
+ render(<Spinner ref={ref} />);
37
+ expect(ref.current).toBeInstanceOf(HTMLSpanElement);
38
+ expect(ref.current?.getAttribute("data-slot")).toBe("spinner");
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,143 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+
5
+ import { cn } from "../../../lib/utils";
6
+
7
+ import type { SpinnerProps } from "./types";
8
+ import { spinnerVariants } from "./variants";
9
+
10
+ export function Spinner(props: SpinnerProps) {
11
+ const {
12
+ className,
13
+ appearance,
14
+ size,
15
+ variant = "ring",
16
+ ref,
17
+ "aria-label": ariaLabel = "Loading",
18
+ ...rest
19
+ } = props;
20
+ const rootClass = cn(
21
+ spinnerVariants({ appearance, size, variant }),
22
+ className,
23
+ );
24
+
25
+ if (variant === "ring") {
26
+ return (
27
+ <motion.span
28
+ ref={ref}
29
+ role="status"
30
+ data-slot="spinner"
31
+ aria-label={ariaLabel}
32
+ className={cn(rootClass, "relative")}
33
+ initial={false}
34
+ {...rest}
35
+ >
36
+ <motion.span
37
+ className="block size-full rounded-full border-2 border-current border-t-transparent"
38
+ animate={{ rotate: 360 }}
39
+ transition={{ repeat: Infinity, ease: "linear", duration: 0.85 }}
40
+ aria-hidden
41
+ />
42
+ </motion.span>
43
+ );
44
+ }
45
+
46
+ if (variant === "dots") {
47
+ const dotSizes = {
48
+ xs: "size-1",
49
+ sm: "size-1.5",
50
+ md: "size-2",
51
+ lg: "size-2.5",
52
+ xl: "size-3",
53
+ };
54
+ const dotSize = dotSizes[size as keyof typeof dotSizes];
55
+ return (
56
+ <motion.span
57
+ ref={ref}
58
+ role="status"
59
+ data-slot="spinner"
60
+ aria-label={ariaLabel}
61
+ className={cn(rootClass, "gap-1")}
62
+ initial={false}
63
+ {...rest}
64
+ >
65
+ {[0, 1, 2].map((index) => (
66
+ <motion.span
67
+ key={index}
68
+ className={cn("rounded-full bg-current", dotSize)}
69
+ animate={{ y: [0, -5, 0], opacity: [0.45, 1, 0.45] }}
70
+ transition={{
71
+ repeat: Infinity,
72
+ duration: 0.55,
73
+ ease: "easeInOut",
74
+ delay: index * 0.12,
75
+ }}
76
+ aria-hidden
77
+ />
78
+ ))}
79
+ </motion.span>
80
+ );
81
+ }
82
+
83
+ if (variant === "pulse") {
84
+ return (
85
+ <motion.span
86
+ ref={ref}
87
+ role="status"
88
+ data-slot="spinner"
89
+ aria-label={ariaLabel}
90
+ className={cn(rootClass)}
91
+ initial={false}
92
+ {...rest}
93
+ >
94
+ <motion.span
95
+ className="block size-full rounded-full bg-current"
96
+ animate={{ scale: [0.75, 1, 0.75], opacity: [0.45, 1, 0.45] }}
97
+ transition={{ repeat: Infinity, duration: 0.9, ease: "easeInOut" }}
98
+ aria-hidden
99
+ />
100
+ </motion.span>
101
+ );
102
+ }
103
+
104
+ const barWidths = {
105
+ xs: "w-0.5",
106
+ sm: "w-0.5",
107
+ md: "w-1",
108
+ lg: "w-1.5",
109
+ xl: "w-2",
110
+ };
111
+
112
+ const barWidth =
113
+ barWidths[size as keyof typeof barWidths];
114
+
115
+ return (
116
+ <motion.span
117
+ ref={ref}
118
+ role="status"
119
+ data-slot="spinner"
120
+ aria-label={ariaLabel}
121
+ className={cn(rootClass, "gap-1")}
122
+ initial={false}
123
+ {...rest}
124
+ >
125
+ {[0, 1, 2, 3].map((index) => (
126
+ <motion.span
127
+ key={index}
128
+ className={cn("h-full max-h-[70%] rounded-full bg-current", barWidth)}
129
+ animate={{ scaleY: [0.35, 1, 0.35] }}
130
+ transition={{
131
+ repeat: Infinity,
132
+ duration: 0.65,
133
+ ease: "easeInOut",
134
+ delay: index * 0.1,
135
+ }}
136
+ aria-hidden
137
+ />
138
+ ))}
139
+ </motion.span>
140
+ );
141
+ }
142
+
143
+ Spinner.displayName = "Spinner";
@@ -0,0 +1,11 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { HTMLMotionProps } from "framer-motion";
3
+
4
+ import type { spinnerVariants } from "./variants";
5
+
6
+ type SpinnerVariantProps = VariantProps<typeof spinnerVariants>;
7
+
8
+ export type SpinnerProps = SpinnerVariantProps &
9
+ Omit<HTMLMotionProps<"span">, "children"> & {
10
+ "aria-label"?: string;
11
+ };
@@ -0,0 +1,50 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ const spinnerAppearances = {
4
+ default: "text-slate-50",
5
+ secondary: "text-slate-300",
6
+ destructive: "text-rose-400",
7
+ ghost: "text-slate-300",
8
+ emerald: "text-emerald-400",
9
+ indigo: "text-indigo-400",
10
+ purple: "text-purple-400",
11
+ pink: "text-pink-400",
12
+ rose: "text-rose-400",
13
+ sky: "text-sky-400",
14
+ teal: "text-teal-400",
15
+ yellow: "text-yellow-400",
16
+ orange: "text-orange-400",
17
+ "gradient-blue": "text-blue-400",
18
+ "gradient-green": "text-green-400",
19
+ "gradient-red": "text-red-400",
20
+ "gradient-yellow": "text-yellow-400",
21
+ "gradient-purple": "text-purple-400",
22
+ "gradient-teal": "text-teal-400",
23
+ "gradient-indigo": "text-indigo-400",
24
+ "gradient-pink": "text-pink-400",
25
+ "gradient-orange": "text-orange-400",
26
+ } as const;
27
+
28
+ export const spinnerVariants = cva("inline-flex items-center justify-center", {
29
+ variants: {
30
+ appearance: spinnerAppearances,
31
+ size: {
32
+ xs: "size-3",
33
+ sm: "size-4",
34
+ md: "size-6",
35
+ lg: "size-8",
36
+ xl: "size-10",
37
+ },
38
+ variant: {
39
+ ring: "",
40
+ dots: "",
41
+ pulse: "",
42
+ bars: "",
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ appearance: "default",
47
+ size: "md",
48
+ variant: "ring",
49
+ },
50
+ });
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ export {
4
+ Stepper,
5
+ StepperDescription,
6
+ StepperIndicator,
7
+ StepperItem,
8
+ StepperTitle,
9
+ } from "./stepper";
10
+ export type {
11
+ StepperDescriptionProps,
12
+ StepperIndicatorProps,
13
+ StepperItemProps,
14
+ StepperProps,
15
+ StepperTitleProps,
16
+ StepperAppearance,
17
+ } from "./types";
18
+ export {
19
+ stepperIndicatorVariants,
20
+ stepperItemVariants,
21
+ stepperVariants,
22
+ } from "./variants";
@@ -0,0 +1,183 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import {
6
+ Stepper,
7
+ StepperDescription,
8
+ StepperIndicator,
9
+ StepperItem,
10
+ StepperTitle,
11
+ } from "./stepper";
12
+
13
+ describe("Stepper", () => {
14
+ it("should expose displayName", () => {
15
+ expect(Stepper.displayName).toBe("Stepper");
16
+ expect(StepperItem.displayName).toBe("StepperItem");
17
+ expect(StepperIndicator.displayName).toBe("StepperIndicator");
18
+ expect(StepperTitle.displayName).toBe("StepperTitle");
19
+ expect(StepperDescription.displayName).toBe("StepperDescription");
20
+ });
21
+
22
+ it("should stamp data-slot on stepper root and use list semantics", () => {
23
+ render(
24
+ <Stepper>
25
+ <StepperItem>
26
+ <StepperIndicator />
27
+ <StepperTitle>One</StepperTitle>
28
+ </StepperItem>
29
+ </Stepper>,
30
+ );
31
+ const root = document.querySelector('[data-slot="stepper"]');
32
+ expect(root).toBeTruthy();
33
+ expect(root).toHaveAttribute("role", "list");
34
+ expect(document.querySelector('[data-slot="stepper-item"]')).toHaveAttribute(
35
+ "role",
36
+ "listitem",
37
+ );
38
+ });
39
+
40
+ it("should apply default upcoming appearance to indicators", () => {
41
+ render(
42
+ <Stepper>
43
+ <StepperItem>
44
+ <StepperIndicator />
45
+ <StepperTitle>Cart</StepperTitle>
46
+ </StepperItem>
47
+ <StepperItem>
48
+ <StepperIndicator />
49
+ <StepperTitle>Pay</StepperTitle>
50
+ </StepperItem>
51
+ </Stepper>,
52
+ );
53
+ const indicators = document.querySelectorAll(
54
+ '[data-slot="stepper-indicator"]',
55
+ );
56
+ expect(indicators).toHaveLength(2);
57
+ expect(indicators[0]?.className).toContain("border-white/15");
58
+ expect(indicators[1]?.className).toContain("border-white/15");
59
+ });
60
+
61
+ it("should honor appearance prop on each indicator", () => {
62
+ render(
63
+ <Stepper>
64
+ <StepperItem>
65
+ <StepperIndicator appearance="complete" />
66
+ <StepperTitle>Done</StepperTitle>
67
+ </StepperItem>
68
+ <StepperItem>
69
+ <StepperIndicator appearance="current" />
70
+ <StepperTitle>Active</StepperTitle>
71
+ </StepperItem>
72
+ </Stepper>,
73
+ );
74
+ const indicators = document.querySelectorAll(
75
+ '[data-slot="stepper-indicator"]',
76
+ );
77
+ expect(indicators[0]?.className).toMatch(/emerald/);
78
+ expect(indicators[1]?.className).toMatch(/violet/);
79
+ });
80
+
81
+ it("should render step index as indicator content when children are omitted", () => {
82
+ render(
83
+ <Stepper>
84
+ <StepperItem>
85
+ <StepperIndicator />
86
+ <StepperTitle>First</StepperTitle>
87
+ </StepperItem>
88
+ <StepperItem>
89
+ <StepperIndicator />
90
+ <StepperTitle>Second</StepperTitle>
91
+ </StepperItem>
92
+ </Stepper>,
93
+ );
94
+ const indicators = screen.getAllByText(/^[12]$/);
95
+ expect(indicators[0]).toHaveTextContent("1");
96
+ expect(indicators[1]).toHaveTextContent("2");
97
+ });
98
+
99
+ it("should render custom indicator children instead of the default index", () => {
100
+ render(
101
+ <Stepper>
102
+ <StepperItem>
103
+ <StepperIndicator>✓</StepperIndicator>
104
+ <StepperTitle>Check</StepperTitle>
105
+ </StepperItem>
106
+ </Stepper>,
107
+ );
108
+ expect(screen.getByText("✓")).toBeInTheDocument();
109
+ });
110
+
111
+ it("should apply vertical orientation classes on the root", () => {
112
+ const { container } = render(
113
+ <Stepper orientation="vertical">
114
+ <StepperItem>
115
+ <StepperIndicator />
116
+ <StepperTitle>V</StepperTitle>
117
+ </StepperItem>
118
+ </Stepper>,
119
+ );
120
+ const root = container.querySelector('[data-slot="stepper"]');
121
+ expect(root?.className).toMatch(/flex-col/);
122
+ });
123
+
124
+ it("should pass size from Stepper context to indicators", () => {
125
+ render(
126
+ <Stepper size="sm">
127
+ <StepperItem>
128
+ <StepperIndicator />
129
+ <StepperTitle>Small</StepperTitle>
130
+ </StepperItem>
131
+ </Stepper>,
132
+ );
133
+ const indicator = document.querySelector('[data-slot="stepper-indicator"]');
134
+ expect(indicator?.className).toMatch(/size-8/);
135
+ });
136
+
137
+ it("should stamp data-slot on title and description", () => {
138
+ render(
139
+ <Stepper>
140
+ <StepperItem>
141
+ <StepperIndicator />
142
+ <StepperTitle data-testid="t">T</StepperTitle>
143
+ <StepperDescription data-testid="d">D</StepperDescription>
144
+ </StepperItem>
145
+ </Stepper>,
146
+ );
147
+ expect(document.querySelector('[data-slot="stepper-title"]')).toBe(
148
+ screen.getByTestId("t"),
149
+ );
150
+ expect(document.querySelector('[data-slot="stepper-description"]')).toBe(
151
+ screen.getByTestId("d"),
152
+ );
153
+ });
154
+
155
+ it("should forward ref on Stepper", () => {
156
+ const ref = createRef<HTMLDivElement>();
157
+ render(
158
+ <Stepper ref={ref}>
159
+ <StepperItem>
160
+ <StepperIndicator />
161
+ <StepperTitle>A</StepperTitle>
162
+ </StepperItem>
163
+ </Stepper>,
164
+ );
165
+ expect(ref.current?.getAttribute("data-slot")).toBe("stepper");
166
+ });
167
+
168
+ it("should throw when StepperItem is used outside Stepper", () => {
169
+ expect(() =>
170
+ render(
171
+ <StepperItem>
172
+ <StepperIndicator />
173
+ </StepperItem>,
174
+ ),
175
+ ).toThrow(/must be used within <Stepper>/);
176
+ });
177
+
178
+ it("should throw when StepperIndicator is used outside Stepper", () => {
179
+ expect(() => render(<StepperIndicator />)).toThrow(
180
+ /must be used within <Stepper>/,
181
+ );
182
+ });
183
+ });