@opencosmos/ui 1.3.1

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 (260) hide show
  1. package/.claude/CLAUDE.md +239 -0
  2. package/README.md +161 -0
  3. package/dist/cli.mjs +151 -0
  4. package/dist/dates.d.mts +20 -0
  5. package/dist/dates.d.ts +20 -0
  6. package/dist/dates.js +240 -0
  7. package/dist/dates.js.map +1 -0
  8. package/dist/dates.mjs +203 -0
  9. package/dist/dates.mjs.map +1 -0
  10. package/dist/dnd.d.mts +126 -0
  11. package/dist/dnd.d.ts +126 -0
  12. package/dist/dnd.js +274 -0
  13. package/dist/dnd.js.map +1 -0
  14. package/dist/dnd.mjs +250 -0
  15. package/dist/dnd.mjs.map +1 -0
  16. package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
  17. package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
  18. package/dist/forms.d.mts +38 -0
  19. package/dist/forms.d.ts +38 -0
  20. package/dist/forms.js +198 -0
  21. package/dist/forms.js.map +1 -0
  22. package/dist/forms.mjs +159 -0
  23. package/dist/forms.mjs.map +1 -0
  24. package/dist/hooks-1b8WaQf1.d.mts +225 -0
  25. package/dist/hooks-CKW8vE9H.d.ts +225 -0
  26. package/dist/hooks.d.mts +3 -0
  27. package/dist/hooks.d.ts +3 -0
  28. package/dist/hooks.js +971 -0
  29. package/dist/hooks.js.map +1 -0
  30. package/dist/hooks.mjs +943 -0
  31. package/dist/hooks.mjs.map +1 -0
  32. package/dist/index-DscTIrZ2.d.mts +29 -0
  33. package/dist/index-DscTIrZ2.d.ts +29 -0
  34. package/dist/index.d.mts +3382 -0
  35. package/dist/index.d.ts +3382 -0
  36. package/dist/index.js +15146 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +14802 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/providers-CXPDMsl7.d.mts +30 -0
  41. package/dist/providers-Dn_Msjvz.d.ts +30 -0
  42. package/dist/providers.d.mts +3 -0
  43. package/dist/providers.d.ts +3 -0
  44. package/dist/providers.js +1885 -0
  45. package/dist/providers.js.map +1 -0
  46. package/dist/providers.mjs +1859 -0
  47. package/dist/providers.mjs.map +1 -0
  48. package/dist/tables.d.mts +10 -0
  49. package/dist/tables.d.ts +10 -0
  50. package/dist/tables.js +248 -0
  51. package/dist/tables.js.map +1 -0
  52. package/dist/tables.mjs +218 -0
  53. package/dist/tables.mjs.map +1 -0
  54. package/dist/tokens.d.mts +1065 -0
  55. package/dist/tokens.d.ts +1065 -0
  56. package/dist/tokens.js +2637 -0
  57. package/dist/tokens.js.map +1 -0
  58. package/dist/tokens.mjs +2555 -0
  59. package/dist/tokens.mjs.map +1 -0
  60. package/dist/utils-CIIM7dAC.d.ts +986 -0
  61. package/dist/utils-Cs04sxth.d.mts +986 -0
  62. package/dist/utils.d.mts +4 -0
  63. package/dist/utils.d.ts +4 -0
  64. package/dist/utils.js +874 -0
  65. package/dist/utils.js.map +1 -0
  66. package/dist/utils.mjs +806 -0
  67. package/dist/utils.mjs.map +1 -0
  68. package/dist/validation-Bj1ye-v_.d.mts +114 -0
  69. package/dist/validation-Bj1ye-v_.d.ts +114 -0
  70. package/dist/webgl.d.mts +104 -0
  71. package/dist/webgl.d.ts +104 -0
  72. package/dist/webgl.js +226 -0
  73. package/dist/webgl.js.map +1 -0
  74. package/dist/webgl.mjs +195 -0
  75. package/dist/webgl.mjs.map +1 -0
  76. package/package.json +267 -0
  77. package/src/cli.ts +206 -0
  78. package/src/component-registry.ts +183 -0
  79. package/src/components/actions/Button.test.tsx +61 -0
  80. package/src/components/actions/Button.tsx +70 -0
  81. package/src/components/actions/Link.tsx +78 -0
  82. package/src/components/actions/Magnetic.tsx +68 -0
  83. package/src/components/actions/Toggle.test.tsx +40 -0
  84. package/src/components/actions/Toggle.tsx +47 -0
  85. package/src/components/actions/ToggleGroup.tsx +70 -0
  86. package/src/components/actions/index.ts +5 -0
  87. package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
  88. package/src/components/backgrounds/OrbBackground.tsx +424 -0
  89. package/src/components/backgrounds/WarpBackground.tsx +358 -0
  90. package/src/components/backgrounds/index.ts +3 -0
  91. package/src/components/blocks/Hero.tsx +142 -0
  92. package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
  93. package/src/components/cursor/SplashCursor.tsx +1315 -0
  94. package/src/components/cursor/TargetCursor.tsx +187 -0
  95. package/src/components/cursor/index.ts +2 -0
  96. package/src/components/data-display/AspectImage.tsx +73 -0
  97. package/src/components/data-display/Avatar.test.tsx +35 -0
  98. package/src/components/data-display/Avatar.tsx +55 -0
  99. package/src/components/data-display/Badge.test.tsx +43 -0
  100. package/src/components/data-display/Badge.tsx +84 -0
  101. package/src/components/data-display/Brand.tsx +123 -0
  102. package/src/components/data-display/Calendar.tsx +70 -0
  103. package/src/components/data-display/Card.test.tsx +92 -0
  104. package/src/components/data-display/Card.tsx +115 -0
  105. package/src/components/data-display/Code.tsx +210 -0
  106. package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
  107. package/src/components/data-display/DataTable.tsx +119 -0
  108. package/src/components/data-display/DescriptionList.tsx +41 -0
  109. package/src/components/data-display/GitHubIcon.tsx +44 -0
  110. package/src/components/data-display/Heading.test.tsx +36 -0
  111. package/src/components/data-display/Heading.tsx +83 -0
  112. package/src/components/data-display/StatCard.tsx +195 -0
  113. package/src/components/data-display/Table.tsx +133 -0
  114. package/src/components/data-display/Text.test.tsx +48 -0
  115. package/src/components/data-display/Text.tsx +144 -0
  116. package/src/components/data-display/Timeline.tsx +194 -0
  117. package/src/components/data-display/TreeView.tsx +226 -0
  118. package/src/components/data-display/Typewriter.tsx +119 -0
  119. package/src/components/data-display/VariableWeightText.tsx +130 -0
  120. package/src/components/data-display/index.ts +19 -0
  121. package/src/components/feedback/Alert.test.tsx +44 -0
  122. package/src/components/feedback/Alert.tsx +65 -0
  123. package/src/components/feedback/EmptyState.tsx +113 -0
  124. package/src/components/feedback/Progress.test.tsx +60 -0
  125. package/src/components/feedback/Progress.tsx +30 -0
  126. package/src/components/feedback/ProgressBar.tsx +158 -0
  127. package/src/components/feedback/Skeleton.test.tsx +39 -0
  128. package/src/components/feedback/Skeleton.tsx +45 -0
  129. package/src/components/feedback/Sonner.tsx +28 -0
  130. package/src/components/feedback/Spinner.test.tsx +33 -0
  131. package/src/components/feedback/Spinner.tsx +99 -0
  132. package/src/components/feedback/Stepper.tsx +307 -0
  133. package/src/components/feedback/Toast/Toast.tsx +243 -0
  134. package/src/components/feedback/Toast/index.ts +2 -0
  135. package/src/components/feedback/index.ts +9 -0
  136. package/src/components/forms/Checkbox.test.tsx +40 -0
  137. package/src/components/forms/Checkbox.tsx +31 -0
  138. package/src/components/forms/ColorPicker.tsx +118 -0
  139. package/src/components/forms/Combobox.tsx +96 -0
  140. package/src/components/forms/DragDrop.tsx +440 -0
  141. package/src/components/forms/FileUpload.tsx +252 -0
  142. package/src/components/forms/FilterButton.tsx +65 -0
  143. package/src/components/forms/Form.tsx +197 -0
  144. package/src/components/forms/Input.test.tsx +46 -0
  145. package/src/components/forms/Input.tsx +43 -0
  146. package/src/components/forms/InputOTP.tsx +81 -0
  147. package/src/components/forms/Label.test.tsx +20 -0
  148. package/src/components/forms/Label.tsx +25 -0
  149. package/src/components/forms/RadioGroup.tsx +51 -0
  150. package/src/components/forms/SearchBar.tsx +215 -0
  151. package/src/components/forms/Select.test.tsx +118 -0
  152. package/src/components/forms/Select.tsx +274 -0
  153. package/src/components/forms/Slider.tsx +29 -0
  154. package/src/components/forms/Switch.test.tsx +76 -0
  155. package/src/components/forms/Switch.tsx +30 -0
  156. package/src/components/forms/TextField.tsx +152 -0
  157. package/src/components/forms/Textarea.test.tsx +41 -0
  158. package/src/components/forms/Textarea.tsx +29 -0
  159. package/src/components/forms/ThemeSwitcher.tsx +290 -0
  160. package/src/components/forms/ThemeToggle.tsx +151 -0
  161. package/src/components/forms/index.ts +19 -0
  162. package/src/components/layout/Accordion.test.tsx +66 -0
  163. package/src/components/layout/Accordion.tsx +64 -0
  164. package/src/components/layout/AspectRatio.tsx +7 -0
  165. package/src/components/layout/Carousel.tsx +277 -0
  166. package/src/components/layout/Collapsible.test.tsx +40 -0
  167. package/src/components/layout/Collapsible.tsx +31 -0
  168. package/src/components/layout/Container.test.tsx +45 -0
  169. package/src/components/layout/Container.tsx +99 -0
  170. package/src/components/layout/CustomizerPanel.tsx +400 -0
  171. package/src/components/layout/DatePicker.tsx +57 -0
  172. package/src/components/layout/Footer/Footer.tsx +175 -0
  173. package/src/components/layout/Footer/index.ts +2 -0
  174. package/src/components/layout/GlassSurface.tsx +82 -0
  175. package/src/components/layout/Grid.test.tsx +31 -0
  176. package/src/components/layout/Grid.tsx +130 -0
  177. package/src/components/layout/Header/Header.tsx +450 -0
  178. package/src/components/layout/Header/index.ts +2 -0
  179. package/src/components/layout/PageLayout.tsx +180 -0
  180. package/src/components/layout/PageTemplate.tsx +158 -0
  181. package/src/components/layout/Resizable.tsx +48 -0
  182. package/src/components/layout/ScrollArea.tsx +53 -0
  183. package/src/components/layout/Separator.test.tsx +28 -0
  184. package/src/components/layout/Separator.tsx +29 -0
  185. package/src/components/layout/Sidebar.tsx +171 -0
  186. package/src/components/layout/Stack.test.tsx +41 -0
  187. package/src/components/layout/Stack.tsx +89 -0
  188. package/src/components/layout/glass-surface.css +60 -0
  189. package/src/components/layout/index.ts +18 -0
  190. package/src/components/motion/AnimatedBeam.tsx +159 -0
  191. package/src/components/navigation/Breadcrumb.test.tsx +57 -0
  192. package/src/components/navigation/Breadcrumb.tsx +119 -0
  193. package/src/components/navigation/Breadcrumbs.tsx +221 -0
  194. package/src/components/navigation/Command.tsx +159 -0
  195. package/src/components/navigation/Menubar.tsx +115 -0
  196. package/src/components/navigation/NavLink.tsx +55 -0
  197. package/src/components/navigation/NavigationMenu.tsx +125 -0
  198. package/src/components/navigation/Pagination.tsx +121 -0
  199. package/src/components/navigation/SecondaryNav.tsx +100 -0
  200. package/src/components/navigation/Tabs.test.tsx +47 -0
  201. package/src/components/navigation/Tabs.tsx +60 -0
  202. package/src/components/navigation/TertiaryNav.tsx +90 -0
  203. package/src/components/navigation/index.ts +10 -0
  204. package/src/components/overlays/AlertDialog.test.tsx +69 -0
  205. package/src/components/overlays/AlertDialog.tsx +166 -0
  206. package/src/components/overlays/ContextMenu.tsx +243 -0
  207. package/src/components/overlays/Dialog.test.tsx +79 -0
  208. package/src/components/overlays/Dialog.tsx +158 -0
  209. package/src/components/overlays/Drawer.tsx +128 -0
  210. package/src/components/overlays/Dropdown.tsx +253 -0
  211. package/src/components/overlays/DropdownMenu.tsx +242 -0
  212. package/src/components/overlays/HoverCard.tsx +32 -0
  213. package/src/components/overlays/Modal.tsx +250 -0
  214. package/src/components/overlays/NotificationCenter.tsx +364 -0
  215. package/src/components/overlays/Popover.test.tsx +40 -0
  216. package/src/components/overlays/Popover.tsx +46 -0
  217. package/src/components/overlays/Sheet.tsx +163 -0
  218. package/src/components/overlays/Tooltip.test.tsx +33 -0
  219. package/src/components/overlays/Tooltip.tsx +32 -0
  220. package/src/components/overlays/index.ts +12 -0
  221. package/src/dates.ts +2 -0
  222. package/src/dnd.ts +1 -0
  223. package/src/forms.ts +1 -0
  224. package/src/globals.css +187 -0
  225. package/src/hooks/index.ts +6 -0
  226. package/src/hooks/useForm.ts +247 -0
  227. package/src/hooks/useMotionPreference.test.ts +102 -0
  228. package/src/hooks/useMotionPreference.ts +78 -0
  229. package/src/hooks/useTheme.ts +58 -0
  230. package/src/hooks.ts +9 -0
  231. package/src/index.ts +168 -0
  232. package/src/lib/animations.ts +356 -0
  233. package/src/lib/breadcrumbs.ts +94 -0
  234. package/src/lib/colors.ts +493 -0
  235. package/src/lib/store/customizer.ts +482 -0
  236. package/src/lib/store/index.ts +3 -0
  237. package/src/lib/store/theme.ts +55 -0
  238. package/src/lib/syntax-parser/index.ts +50 -0
  239. package/src/lib/syntax-parser/patterns.ts +64 -0
  240. package/src/lib/syntax-parser/tokenizer.ts +117 -0
  241. package/src/lib/syntax-parser/types.ts +27 -0
  242. package/src/lib/utils.ts +6 -0
  243. package/src/lib/validation.ts +204 -0
  244. package/src/lib/webgl/Color.ts +11 -0
  245. package/src/lib/webgl/Mesh.ts +41 -0
  246. package/src/lib/webgl/Program.ts +118 -0
  247. package/src/lib/webgl/Renderer.ts +51 -0
  248. package/src/lib/webgl/Triangle.ts +27 -0
  249. package/src/lib/webgl/Vec3.ts +18 -0
  250. package/src/lib/webgl/index.ts +13 -0
  251. package/src/nativewind-env.d.ts +1 -0
  252. package/src/providers/ThemeProvider.tsx +461 -0
  253. package/src/providers/index.ts +1 -0
  254. package/src/providers.ts +7 -0
  255. package/src/tables.ts +1 -0
  256. package/src/test/setup.ts +39 -0
  257. package/src/theme.css +158 -0
  258. package/src/tokens.ts +7 -0
  259. package/src/utils.ts +12 -0
  260. package/src/webgl.ts +1 -0
@@ -0,0 +1,45 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+
4
+ export interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ /**
6
+ * Variant of the skeleton
7
+ * @default "default"
8
+ */
9
+ variant?: "default" | "circular" | "rectangular" | "text"
10
+ /**
11
+ * Width of the skeleton (CSS value)
12
+ */
13
+ width?: string | number
14
+ /**
15
+ * Height of the skeleton (CSS value)
16
+ */
17
+ height?: string | number
18
+ }
19
+
20
+ function Skeleton({ className, variant = "default", width, height, style, ...props }: SkeletonProps) {
21
+ const variantStyles = {
22
+ default: "rounded-md",
23
+ circular: "rounded-full",
24
+ rectangular: "rounded-none",
25
+ text: "rounded-xs h-4",
26
+ }
27
+
28
+ return (
29
+ <div
30
+ className={cn(
31
+ "animate-pulse bg-muted",
32
+ variantStyles[variant],
33
+ className
34
+ )}
35
+ style={{
36
+ width,
37
+ height: variant === "text" ? undefined : height,
38
+ ...style,
39
+ }}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+
45
+ export { Skeleton }
@@ -0,0 +1,28 @@
1
+ "use client"
2
+
3
+ import { Toaster as Sonner } from "sonner"
4
+
5
+ type ToasterProps = React.ComponentProps<typeof Sonner>
6
+
7
+ const Toaster = ({ ...props }: ToasterProps) => {
8
+ return (
9
+ <Sonner
10
+ theme="system"
11
+ className="toaster group"
12
+ toastOptions={{
13
+ classNames: {
14
+ toast:
15
+ "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
16
+ description: "group-[.toast]:text-muted-foreground",
17
+ actionButton:
18
+ "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
19
+ cancelButton:
20
+ "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
21
+ },
22
+ }}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ export { Toaster }
@@ -0,0 +1,33 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Spinner } from './Spinner'
4
+
5
+ describe('Spinner', () => {
6
+ it('renders with status role', () => {
7
+ render(<Spinner />)
8
+ expect(screen.getByRole('status')).toBeInTheDocument()
9
+ })
10
+
11
+ it('has default loading label for screen readers', () => {
12
+ render(<Spinner />)
13
+ expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'Loading...')
14
+ expect(screen.getByText('Loading...')).toHaveClass('sr-only')
15
+ })
16
+
17
+ it('accepts custom label', () => {
18
+ render(<Spinner label="Saving data..." />)
19
+ expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'Saving data...')
20
+ })
21
+
22
+ it('renders SVG spinner', () => {
23
+ render(<Spinner />)
24
+ const svg = screen.getByRole('status').querySelector('svg')
25
+ expect(svg).toBeInTheDocument()
26
+ expect(svg).toHaveClass('animate-spin')
27
+ })
28
+
29
+ it('applies custom className', () => {
30
+ render(<Spinner className="custom-spinner" />)
31
+ expect(screen.getByRole('status')).toHaveClass('custom-spinner')
32
+ })
33
+ })
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+
3
+ export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ /**
5
+ * Size of the spinner
6
+ * @default 'md'
7
+ */
8
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
9
+
10
+ /**
11
+ * Color variant
12
+ * @default 'primary'
13
+ */
14
+ variant?: 'primary' | 'secondary' | 'inherit';
15
+
16
+ /**
17
+ * Optional label for screen readers
18
+ */
19
+ label?: string;
20
+ }
21
+
22
+ /**
23
+ * Spinner Component
24
+ *
25
+ * A loading indicator with smooth animation.
26
+ *
27
+ * Features:
28
+ * - Five size variants
29
+ * - Theme-aware colors
30
+ * - Uses current text color option
31
+ * - Accessible label for screen readers
32
+ * - Respects reduced motion preferences
33
+ *
34
+ * Example:
35
+ * ```tsx
36
+ * <Spinner />
37
+ * <Spinner size="lg" variant="primary" label="Loading content..." />
38
+ * <Spinner size="sm" variant="inherit" />
39
+ * ```
40
+ */
41
+ export const Spinner = (
42
+ {
43
+ ref,
44
+ size = 'md',
45
+ variant = 'primary',
46
+ label = 'Loading...',
47
+ className = '',
48
+ ...props
49
+ }: SpinnerProps & {
50
+ ref?: React.Ref<HTMLDivElement>;
51
+ }
52
+ ) => {
53
+ const sizes = {
54
+ xs: 'w-4 h-4',
55
+ sm: 'w-5 h-5',
56
+ md: 'w-6 h-6',
57
+ lg: 'w-8 h-8',
58
+ xl: 'w-12 h-12',
59
+ };
60
+
61
+ const variants = {
62
+ primary: 'text-[var(--color-primary)]',
63
+ secondary: 'text-[var(--color-text-secondary)]',
64
+ inherit: 'text-current',
65
+ };
66
+
67
+ return (
68
+ <div
69
+ ref={ref}
70
+ className={`inline-block ${className}`}
71
+ role="status"
72
+ aria-label={label}
73
+ {...props}
74
+ >
75
+ <svg
76
+ className={`${sizes[size]} ${variants[variant]} animate-spin`}
77
+ xmlns="http://www.w3.org/2000/svg"
78
+ fill="none"
79
+ viewBox="0 0 24 24"
80
+ aria-hidden="true"
81
+ >
82
+ <circle
83
+ className="opacity-25"
84
+ cx="12"
85
+ cy="12"
86
+ r="10"
87
+ stroke="currentColor"
88
+ strokeWidth="4"
89
+ />
90
+ <path
91
+ className="opacity-75"
92
+ fill="currentColor"
93
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
94
+ />
95
+ </svg>
96
+ <span className="sr-only">{label}</span>
97
+ </div>
98
+ );
99
+ };
@@ -0,0 +1,307 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const stepperVariants = cva("flex", {
6
+ variants: {
7
+ orientation: {
8
+ horizontal: "flex-row items-start",
9
+ vertical: "flex-col",
10
+ },
11
+ size: {
12
+ sm: "gap-2",
13
+ default: "gap-3",
14
+ lg: "gap-4",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ orientation: "horizontal",
19
+ size: "default",
20
+ },
21
+ })
22
+
23
+ const stepperIndicatorVariants = cva(
24
+ "flex items-center justify-center rounded-full border-2 shrink-0 font-medium transition-colors",
25
+ {
26
+ variants: {
27
+ status: {
28
+ pending: "border-border bg-muted text-foreground-secondary",
29
+ active: "border-primary bg-primary text-primary-foreground",
30
+ completed: "border-primary bg-primary text-primary-foreground",
31
+ error: "border-destructive bg-destructive text-destructive-foreground",
32
+ },
33
+ size: {
34
+ sm: "h-7 w-7 text-xs",
35
+ default: "h-9 w-9 text-sm",
36
+ lg: "h-11 w-11 text-base",
37
+ },
38
+ },
39
+ defaultVariants: {
40
+ status: "pending",
41
+ size: "default",
42
+ },
43
+ }
44
+ )
45
+
46
+ export interface StepperProps
47
+ extends React.HTMLAttributes<HTMLOListElement>,
48
+ VariantProps<typeof stepperVariants> {
49
+ /** Zero-based index of the current step */
50
+ currentStep: number
51
+ /** Called when a step is clicked (only when clickable is true) */
52
+ onStepClick?: (step: number) => void
53
+ /** Allow clicking steps to navigate */
54
+ clickable?: boolean
55
+ }
56
+
57
+ export interface StepperStepProps
58
+ extends React.HTMLAttributes<HTMLLIElement> {
59
+ /** Step label text */
60
+ label: string
61
+ /** Optional description below the label */
62
+ description?: string
63
+ /** Custom icon for the indicator */
64
+ icon?: React.ReactNode
65
+ /** Status (auto-computed from currentStep if not provided) */
66
+ status?: "pending" | "active" | "completed" | "error"
67
+ /** Mark step as optional */
68
+ optional?: boolean
69
+ }
70
+
71
+ const CheckIcon = () => (
72
+ <svg
73
+ xmlns="http://www.w3.org/2000/svg"
74
+ width="16"
75
+ height="16"
76
+ viewBox="0 0 24 24"
77
+ fill="none"
78
+ stroke="currentColor"
79
+ strokeWidth="2.5"
80
+ strokeLinecap="round"
81
+ strokeLinejoin="round"
82
+ aria-hidden="true"
83
+ >
84
+ <polyline points="20 6 9 17 4 12" />
85
+ </svg>
86
+ )
87
+
88
+ const XIcon = () => (
89
+ <svg
90
+ xmlns="http://www.w3.org/2000/svg"
91
+ width="16"
92
+ height="16"
93
+ viewBox="0 0 24 24"
94
+ fill="none"
95
+ stroke="currentColor"
96
+ strokeWidth="2.5"
97
+ strokeLinecap="round"
98
+ strokeLinejoin="round"
99
+ aria-hidden="true"
100
+ >
101
+ <line x1="18" y1="6" x2="6" y2="18" />
102
+ <line x1="6" y1="6" x2="18" y2="18" />
103
+ </svg>
104
+ )
105
+
106
+ interface StepperContextValue {
107
+ currentStep: number
108
+ orientation: "horizontal" | "vertical"
109
+ size: "sm" | "default" | "lg"
110
+ clickable: boolean
111
+ onStepClick?: (step: number) => void
112
+ }
113
+
114
+ const StepperContext = React.createContext<StepperContextValue>({
115
+ currentStep: 0,
116
+ orientation: "horizontal",
117
+ size: "default",
118
+ clickable: false,
119
+ })
120
+
121
+ function Stepper({
122
+ className,
123
+ orientation = "horizontal",
124
+ size = "default",
125
+ currentStep,
126
+ onStepClick,
127
+ clickable = false,
128
+ children,
129
+ ...props
130
+ }: StepperProps) {
131
+ return (
132
+ <StepperContext.Provider
133
+ value={{ currentStep, orientation: orientation ?? "horizontal", size: size ?? "default", clickable, onStepClick }}
134
+ >
135
+ <ol
136
+ data-slot="stepper"
137
+ className={cn(stepperVariants({ orientation, size }), className)}
138
+ aria-label="Progress"
139
+ {...props}
140
+ >
141
+ {React.Children.map(children, (child, index) => {
142
+ if (!React.isValidElement(child)) return child
143
+
144
+ const totalSteps = React.Children.count(children)
145
+ const isLast = index === totalSteps - 1
146
+
147
+ const childStatus =
148
+ (child.props as StepperStepProps).status ??
149
+ (index < currentStep
150
+ ? "completed"
151
+ : index === currentStep
152
+ ? "active"
153
+ : "pending")
154
+
155
+ return (
156
+ <StepperStepInternal
157
+ {...(child.props as StepperStepProps)}
158
+ status={childStatus}
159
+ stepIndex={index}
160
+ isLast={isLast}
161
+ />
162
+ )
163
+ })}
164
+ </ol>
165
+ </StepperContext.Provider>
166
+ )
167
+ }
168
+
169
+ interface StepperStepInternalProps extends StepperStepProps {
170
+ stepIndex: number
171
+ isLast: boolean
172
+ }
173
+
174
+ function StepperStepInternal({
175
+ className,
176
+ label,
177
+ description,
178
+ icon,
179
+ status = "pending",
180
+ optional = false,
181
+ stepIndex,
182
+ isLast,
183
+ ...props
184
+ }: StepperStepInternalProps) {
185
+ const { orientation, size, clickable, onStepClick } = React.useContext(StepperContext)
186
+
187
+ const indicatorContent = (() => {
188
+ if (icon) return icon
189
+ if (status === "completed") return <CheckIcon />
190
+ if (status === "error") return <XIcon />
191
+ return stepIndex + 1
192
+ })()
193
+
194
+ const isHorizontal = orientation === "horizontal"
195
+
196
+ const handleClick = () => {
197
+ if (clickable && onStepClick) {
198
+ onStepClick(stepIndex)
199
+ }
200
+ }
201
+
202
+ const handleKeyDown = (e: React.KeyboardEvent) => {
203
+ if (clickable && onStepClick && (e.key === "Enter" || e.key === " ")) {
204
+ e.preventDefault()
205
+ onStepClick(stepIndex)
206
+ }
207
+ }
208
+
209
+ return (
210
+ <li
211
+ data-slot="stepper-step"
212
+ className={cn(
213
+ "flex",
214
+ isHorizontal ? "flex-1 items-start" : "items-start",
215
+ className
216
+ )}
217
+ aria-current={status === "active" ? "step" : undefined}
218
+ {...props}
219
+ >
220
+ <div
221
+ className={cn(
222
+ "flex",
223
+ isHorizontal ? "flex-col items-center flex-1" : "flex-row items-start gap-3"
224
+ )}
225
+ >
226
+ {/* Indicator + Separator row */}
227
+ <div
228
+ className={cn(
229
+ "flex items-center",
230
+ isHorizontal ? "w-full" : "flex-col"
231
+ )}
232
+ >
233
+ {/* Indicator */}
234
+ <button
235
+ type="button"
236
+ className={cn(
237
+ stepperIndicatorVariants({ status, size: size ?? "default" }),
238
+ clickable
239
+ ? "cursor-pointer hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
240
+ : "cursor-default"
241
+ )}
242
+ onClick={handleClick}
243
+ onKeyDown={handleKeyDown}
244
+ tabIndex={clickable ? 0 : -1}
245
+ aria-label={`Step ${stepIndex + 1}: ${label}${status === "completed" ? " (completed)" : status === "active" ? " (current)" : status === "error" ? " (error)" : ""}`}
246
+ >
247
+ {indicatorContent}
248
+ </button>
249
+
250
+ {/* Separator */}
251
+ {!isLast && isHorizontal && (
252
+ <div
253
+ data-slot="stepper-separator"
254
+ className={cn(
255
+ "h-0.5 flex-1 mx-2",
256
+ status === "completed" ? "bg-primary" : "bg-border"
257
+ )}
258
+ aria-hidden="true"
259
+ />
260
+ )}
261
+ {!isLast && !isHorizontal && (
262
+ <div
263
+ data-slot="stepper-separator"
264
+ className={cn(
265
+ "w-0.5 min-h-6 my-1",
266
+ status === "completed" ? "bg-primary" : "bg-border"
267
+ )}
268
+ aria-hidden="true"
269
+ />
270
+ )}
271
+ </div>
272
+
273
+ {/* Label + Description */}
274
+ <div
275
+ className={cn(
276
+ isHorizontal ? "mt-2 text-center" : "",
277
+ "min-w-0"
278
+ )}
279
+ >
280
+ <p
281
+ className={cn(
282
+ "text-sm font-medium",
283
+ status === "active" ? "text-foreground" : "text-foreground-secondary"
284
+ )}
285
+ >
286
+ {label}
287
+ </p>
288
+ {(description || optional) && (
289
+ <p className="text-xs text-foreground-secondary mt-0.5">
290
+ {description}
291
+ {optional && !description && "Optional"}
292
+ {optional && description && " (Optional)"}
293
+ </p>
294
+ )}
295
+ </div>
296
+ </div>
297
+ </li>
298
+ )
299
+ }
300
+
301
+ /** Public sub-component for declaring steps */
302
+ function StepperStep(_props: StepperStepProps) {
303
+ // Rendered internally by Stepper - this is just for the public API
304
+ return null
305
+ }
306
+
307
+ export { Stepper, StepperStep, stepperVariants }