@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,144 @@
1
+ import React from 'react';
2
+
3
+ export interface TextProps {
4
+ /**
5
+ * Text content
6
+ */
7
+ children: React.ReactNode;
8
+
9
+ /**
10
+ * Semantic variant determines both color and meaning
11
+ * @default 'primary'
12
+ */
13
+ variant?: 'primary' | 'secondary' | 'muted';
14
+
15
+ /**
16
+ * Text size
17
+ * @default 'base'
18
+ */
19
+ size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl';
20
+
21
+ /**
22
+ * Font weight
23
+ * @default 'normal'
24
+ */
25
+ weight?: 'normal' | 'medium' | 'semibold' | 'bold';
26
+
27
+ /**
28
+ * HTML element to render as
29
+ * @default 'p'
30
+ */
31
+ as?: 'p' | 'span' | 'div' | 'label' | 'time';
32
+
33
+ /**
34
+ * Additional className for customization
35
+ */
36
+ className?: string;
37
+ }
38
+
39
+ /**
40
+ * Text Component
41
+ *
42
+ * Semantic text with automatic token-based styling.
43
+ * No need to manually apply CSS variable text color classes.
44
+ *
45
+ * **What it handles automatically:**
46
+ * - Theme-aware text colors
47
+ * - Semantic color variants (primary, secondary, muted)
48
+ * - Consistent sizing
49
+ * - Dark mode support
50
+ *
51
+ * **Variants:**
52
+ * - `primary`: Main content text (`--color-text-primary`)
53
+ * - `secondary`: Supporting text (`--color-text-secondary`)
54
+ * - `muted`: De-emphasized text (`--color-text-muted`)
55
+ *
56
+ * Usage:
57
+ * ```tsx
58
+ * // Primary content text
59
+ * <Text>Main paragraph content</Text>
60
+ *
61
+ * // Secondary supporting text
62
+ * <Text variant="secondary">Helper text or description</Text>
63
+ *
64
+ * // Muted text (least emphasis)
65
+ * <Text variant="muted">Footnote or metadata</Text>
66
+ *
67
+ * // Different sizes
68
+ * <Text size="lg">Large text</Text>
69
+ * <Text size="sm">Small text</Text>
70
+ *
71
+ * // As different element
72
+ * <Text as="span">Inline text</Text>
73
+ * <Text as="label">Form label</Text>
74
+ * ```
75
+ */
76
+ export const Text: React.FC<TextProps & { ref?: React.Ref<HTMLElement> }> = (
77
+ {
78
+ ref,
79
+ children,
80
+ variant = 'primary',
81
+ size = 'base',
82
+ weight = 'normal',
83
+ as: Component = 'p',
84
+ className = ''
85
+ }: TextProps & {
86
+ ref?: React.Ref<HTMLElement>;
87
+ }
88
+ ) => {
89
+ const variantStyles = {
90
+ primary: 'text-[var(--color-text-primary)]',
91
+ secondary: 'text-[var(--color-text-secondary)]',
92
+ muted: 'text-[var(--color-text-muted)]',
93
+ };
94
+
95
+ const variantInlineStyles: Record<string, React.CSSProperties> = {
96
+ primary: { color: 'var(--color-text-primary, #212121)' },
97
+ secondary: { color: 'var(--color-text-secondary, #5D5D5D)' },
98
+ muted: { color: 'var(--color-text-muted, #8891A7)' },
99
+ };
100
+
101
+ const sizeStyles = {
102
+ xs: 'text-xs', // 12px
103
+ sm: 'text-sm', // 14px
104
+ base: 'text-base', // 16px
105
+ lg: 'text-lg', // 18px
106
+ xl: 'text-xl', // 20px
107
+ };
108
+
109
+ const sizeInlineStyles: Record<string, React.CSSProperties> = {
110
+ xs: { fontSize: '0.75rem', lineHeight: String(1 / 0.75) },
111
+ sm: { fontSize: '0.875rem', lineHeight: String(1.25 / 0.875) },
112
+ base: { fontSize: '1rem', lineHeight: '1.5' },
113
+ lg: { fontSize: '1.125rem', lineHeight: String(1.75 / 1.125) },
114
+ xl: { fontSize: '1.25rem', lineHeight: String(1.75 / 1.25) },
115
+ };
116
+
117
+ const weightStyles = {
118
+ normal: 'font-normal',
119
+ medium: 'font-medium',
120
+ semibold: 'font-semibold',
121
+ bold: 'font-bold',
122
+ };
123
+
124
+ const weightInlineStyles: Record<string, React.CSSProperties> = {
125
+ normal: { fontWeight: 400 },
126
+ medium: { fontWeight: 500 },
127
+ semibold: { fontWeight: 600 },
128
+ bold: { fontWeight: 700 },
129
+ };
130
+
131
+ return React.createElement(
132
+ Component,
133
+ {
134
+ ref,
135
+ className: `${variantStyles[variant]} ${sizeStyles[size]} ${weightStyles[weight]} ${className}`,
136
+ style: {
137
+ ...variantInlineStyles[variant],
138
+ ...sizeInlineStyles[size],
139
+ ...weightInlineStyles[weight],
140
+ },
141
+ },
142
+ children
143
+ );
144
+ };
@@ -0,0 +1,194 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const timelineVariants = cva("relative", {
6
+ variants: {
7
+ orientation: {
8
+ vertical: "flex flex-col",
9
+ horizontal: "flex flex-row overflow-x-auto",
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ orientation: "vertical",
14
+ },
15
+ })
16
+
17
+ const timelineItemStatusVariants = cva(
18
+ "flex items-center justify-center rounded-full border-2 shrink-0",
19
+ {
20
+ variants: {
21
+ status: {
22
+ pending: "border-border bg-muted text-foreground-secondary",
23
+ active: "border-primary bg-primary text-primary-foreground",
24
+ completed: "border-primary bg-primary text-primary-foreground",
25
+ error: "border-destructive bg-destructive text-destructive-foreground",
26
+ },
27
+ size: {
28
+ sm: "h-6 w-6 [&>svg]:h-3 [&>svg]:w-3",
29
+ default: "h-8 w-8 [&>svg]:h-4 [&>svg]:w-4",
30
+ lg: "h-10 w-10 [&>svg]:h-5 [&>svg]:w-5",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ status: "pending",
35
+ size: "default",
36
+ },
37
+ }
38
+ )
39
+
40
+ export interface TimelineProps
41
+ extends React.HTMLAttributes<HTMLOListElement>,
42
+ VariantProps<typeof timelineVariants> {}
43
+
44
+ export interface TimelineItemProps
45
+ extends React.HTMLAttributes<HTMLLIElement> {
46
+ /** Event title */
47
+ title: string
48
+ /** Event description */
49
+ description?: string
50
+ /** Timestamp text */
51
+ timestamp?: string
52
+ /** Custom icon for the indicator */
53
+ icon?: React.ReactNode
54
+ /** Status of this event */
55
+ status?: "pending" | "active" | "completed" | "error"
56
+ /** Whether this is the last item (hides connector) */
57
+ isLast?: boolean
58
+ /** Size variant */
59
+ size?: "sm" | "default" | "lg"
60
+ }
61
+
62
+ const CheckIcon = () => (
63
+ <svg
64
+ xmlns="http://www.w3.org/2000/svg"
65
+ width="14"
66
+ height="14"
67
+ viewBox="0 0 24 24"
68
+ fill="none"
69
+ stroke="currentColor"
70
+ strokeWidth="2.5"
71
+ strokeLinecap="round"
72
+ strokeLinejoin="round"
73
+ aria-hidden="true"
74
+ >
75
+ <polyline points="20 6 9 17 4 12" />
76
+ </svg>
77
+ )
78
+
79
+ const XIcon = () => (
80
+ <svg
81
+ xmlns="http://www.w3.org/2000/svg"
82
+ width="14"
83
+ height="14"
84
+ viewBox="0 0 24 24"
85
+ fill="none"
86
+ stroke="currentColor"
87
+ strokeWidth="2.5"
88
+ strokeLinecap="round"
89
+ strokeLinejoin="round"
90
+ aria-hidden="true"
91
+ >
92
+ <line x1="18" y1="6" x2="6" y2="18" />
93
+ <line x1="6" y1="6" x2="18" y2="18" />
94
+ </svg>
95
+ )
96
+
97
+ const CircleIcon = () => (
98
+ <svg
99
+ xmlns="http://www.w3.org/2000/svg"
100
+ width="8"
101
+ height="8"
102
+ viewBox="0 0 24 24"
103
+ fill="currentColor"
104
+ aria-hidden="true"
105
+ >
106
+ <circle cx="12" cy="12" r="6" />
107
+ </svg>
108
+ )
109
+
110
+ function Timeline({
111
+ className,
112
+ orientation = "vertical",
113
+ children,
114
+ ...props
115
+ }: TimelineProps) {
116
+ return (
117
+ <ol
118
+ data-slot="timeline"
119
+ className={cn(timelineVariants({ orientation }), className)}
120
+ {...props}
121
+ >
122
+ {children}
123
+ </ol>
124
+ )
125
+ }
126
+
127
+ function TimelineItem({
128
+ className,
129
+ title,
130
+ description,
131
+ timestamp,
132
+ icon,
133
+ status = "pending",
134
+ isLast = false,
135
+ size = "default",
136
+ ...props
137
+ }: TimelineItemProps) {
138
+ const defaultIcon = (() => {
139
+ switch (status) {
140
+ case "completed":
141
+ return <CheckIcon />
142
+ case "error":
143
+ return <XIcon />
144
+ case "active":
145
+ return <CircleIcon />
146
+ default:
147
+ return <CircleIcon />
148
+ }
149
+ })()
150
+
151
+ return (
152
+ <li
153
+ data-slot="timeline-item"
154
+ className={cn("relative flex gap-4", className)}
155
+ aria-current={status === "active" ? "step" : undefined}
156
+ {...props}
157
+ >
158
+ {/* Indicator column */}
159
+ <div className="flex flex-col items-center">
160
+ <div
161
+ data-slot="timeline-icon"
162
+ className={cn(timelineItemStatusVariants({ status, size }))}
163
+ aria-hidden="true"
164
+ >
165
+ {icon ?? defaultIcon}
166
+ </div>
167
+ {!isLast && (
168
+ <div
169
+ data-slot="timeline-connector"
170
+ className={cn(
171
+ "w-0.5 grow bg-border",
172
+ size === "sm" ? "min-h-4" : size === "lg" ? "min-h-8" : "min-h-6"
173
+ )}
174
+ />
175
+ )}
176
+ </div>
177
+
178
+ {/* Content column */}
179
+ <div className={cn("pb-6", isLast && "pb-0")}>
180
+ <div className="flex items-center gap-2">
181
+ <p className="font-medium text-foreground leading-none">{title}</p>
182
+ {timestamp && (
183
+ <time className="text-xs text-foreground-secondary">{timestamp}</time>
184
+ )}
185
+ </div>
186
+ {description && (
187
+ <p className="mt-1 text-sm text-foreground-secondary">{description}</p>
188
+ )}
189
+ </div>
190
+ </li>
191
+ )
192
+ }
193
+
194
+ export { Timeline, TimelineItem, timelineVariants }
@@ -0,0 +1,226 @@
1
+ 'use client'
2
+
3
+ import * as React from "react"
4
+ import { cva } from "class-variance-authority"
5
+ import { cn } from "../../lib/utils"
6
+
7
+ export interface TreeNode {
8
+ /** Unique identifier */
9
+ id: string
10
+ /** Display label */
11
+ label: string
12
+ /** Optional icon */
13
+ icon?: React.ReactNode
14
+ /** Child nodes */
15
+ children?: TreeNode[]
16
+ /** Whether the node is disabled */
17
+ disabled?: boolean
18
+ }
19
+
20
+ export interface TreeViewProps extends React.HTMLAttributes<HTMLDivElement> {
21
+ /** Tree data structure */
22
+ nodes: TreeNode[]
23
+ /** Expanded node IDs (controlled) */
24
+ expanded?: string[]
25
+ /** Default expanded node IDs (uncontrolled) */
26
+ defaultExpanded?: string[]
27
+ /** Called when expanded state changes */
28
+ onExpandChange?: (expanded: string[]) => void
29
+ /** Currently selected node ID */
30
+ selected?: string
31
+ /** Called when selection changes */
32
+ onSelectChange?: (nodeId: string) => void
33
+ }
34
+
35
+ const treeNodeVariants = cva(
36
+ "flex items-center gap-2 py-1 px-2 rounded-md text-sm cursor-pointer select-none transition-colors",
37
+ {
38
+ variants: {
39
+ state: {
40
+ idle: "text-foreground hover:bg-muted",
41
+ selected: "text-foreground bg-primary/10 font-medium",
42
+ disabled: "text-foreground-secondary cursor-not-allowed opacity-60",
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ state: "idle",
47
+ },
48
+ }
49
+ )
50
+
51
+ const ChevronIcon = ({ expanded }: { expanded: boolean }) => (
52
+ <svg
53
+ xmlns="http://www.w3.org/2000/svg"
54
+ width="14"
55
+ height="14"
56
+ viewBox="0 0 24 24"
57
+ fill="none"
58
+ stroke="currentColor"
59
+ strokeWidth="2"
60
+ strokeLinecap="round"
61
+ strokeLinejoin="round"
62
+ aria-hidden="true"
63
+ className={cn(
64
+ "shrink-0 transition-transform",
65
+ expanded ? "rotate-90" : "rotate-0"
66
+ )}
67
+ >
68
+ <polyline points="9 18 15 12 9 6" />
69
+ </svg>
70
+ )
71
+
72
+ interface TreeViewContextValue {
73
+ expanded: Set<string>
74
+ selected: string | null
75
+ toggleExpand: (nodeId: string) => void
76
+ selectNode: (nodeId: string) => void
77
+ }
78
+
79
+ const TreeViewContext = React.createContext<TreeViewContextValue>({
80
+ expanded: new Set(),
81
+ selected: null,
82
+ toggleExpand: () => {},
83
+ selectNode: () => {},
84
+ })
85
+
86
+ function TreeView({
87
+ className,
88
+ nodes,
89
+ expanded: controlledExpanded,
90
+ defaultExpanded = [],
91
+ onExpandChange,
92
+ selected: controlledSelected,
93
+ onSelectChange,
94
+ ...props
95
+ }: TreeViewProps) {
96
+ const [internalExpanded, setInternalExpanded] = React.useState<Set<string>>(
97
+ new Set(defaultExpanded)
98
+ )
99
+ const [internalSelected, setInternalSelected] = React.useState<string | null>(null)
100
+
101
+ const isControlled = controlledExpanded !== undefined
102
+ const expanded = isControlled ? new Set(controlledExpanded) : internalExpanded
103
+ const selected = controlledSelected ?? internalSelected
104
+
105
+ const toggleExpand = React.useCallback(
106
+ (nodeId: string) => {
107
+ const newExpanded = new Set(expanded)
108
+ if (newExpanded.has(nodeId)) {
109
+ newExpanded.delete(nodeId)
110
+ } else {
111
+ newExpanded.add(nodeId)
112
+ }
113
+
114
+ if (!isControlled) {
115
+ setInternalExpanded(newExpanded)
116
+ }
117
+ onExpandChange?.(Array.from(newExpanded))
118
+ },
119
+ [expanded, isControlled, onExpandChange]
120
+ )
121
+
122
+ const selectNode = React.useCallback(
123
+ (nodeId: string) => {
124
+ if (controlledSelected === undefined) {
125
+ setInternalSelected(nodeId)
126
+ }
127
+ onSelectChange?.(nodeId)
128
+ },
129
+ [controlledSelected, onSelectChange]
130
+ )
131
+
132
+ return (
133
+ <TreeViewContext.Provider value={{ expanded, selected, toggleExpand, selectNode }}>
134
+ <div
135
+ data-slot="tree-view"
136
+ role="tree"
137
+ className={cn("space-y-0.5", className)}
138
+ {...props}
139
+ >
140
+ {nodes.map((node) => (
141
+ <TreeViewNodeComponent key={node.id} node={node} level={1} />
142
+ ))}
143
+ </div>
144
+ </TreeViewContext.Provider>
145
+ )
146
+ }
147
+
148
+ interface TreeViewNodeComponentProps {
149
+ node: TreeNode
150
+ level: number
151
+ }
152
+
153
+ function TreeViewNodeComponent({ node, level }: TreeViewNodeComponentProps) {
154
+ const { expanded, selected, toggleExpand, selectNode } = React.useContext(TreeViewContext)
155
+
156
+ const hasChildren = node.children && node.children.length > 0
157
+ const isExpanded = expanded.has(node.id)
158
+ const isSelected = selected === node.id
159
+
160
+ const state = node.disabled ? "disabled" : isSelected ? "selected" : "idle"
161
+
162
+ const handleClick = () => {
163
+ if (node.disabled) return
164
+ selectNode(node.id)
165
+ if (hasChildren) {
166
+ toggleExpand(node.id)
167
+ }
168
+ }
169
+
170
+ const handleKeyDown = (e: React.KeyboardEvent) => {
171
+ if (node.disabled) return
172
+
173
+ switch (e.key) {
174
+ case "Enter":
175
+ case " ":
176
+ e.preventDefault()
177
+ handleClick()
178
+ break
179
+ case "ArrowRight":
180
+ if (hasChildren && !isExpanded) {
181
+ e.preventDefault()
182
+ toggleExpand(node.id)
183
+ }
184
+ break
185
+ case "ArrowLeft":
186
+ if (hasChildren && isExpanded) {
187
+ e.preventDefault()
188
+ toggleExpand(node.id)
189
+ }
190
+ break
191
+ }
192
+ }
193
+
194
+ return (
195
+ <div data-slot="tree-view-node" role="treeitem" aria-expanded={hasChildren ? isExpanded : undefined} aria-level={level} aria-selected={isSelected}>
196
+ <div
197
+ className={cn(treeNodeVariants({ state }))}
198
+ style={{ paddingLeft: `${(level - 1) * 20 + 8}px` }}
199
+ onClick={handleClick}
200
+ onKeyDown={handleKeyDown}
201
+ tabIndex={node.disabled ? -1 : 0}
202
+ >
203
+ {hasChildren ? (
204
+ <ChevronIcon expanded={isExpanded} />
205
+ ) : (
206
+ <span className="w-3.5 shrink-0" aria-hidden="true" />
207
+ )}
208
+ {node.icon && (
209
+ <span className="shrink-0 text-foreground-secondary" aria-hidden="true">
210
+ {node.icon}
211
+ </span>
212
+ )}
213
+ <span className="truncate">{node.label}</span>
214
+ </div>
215
+ {hasChildren && isExpanded && (
216
+ <div role="group">
217
+ {node.children!.map((child) => (
218
+ <TreeViewNodeComponent key={child.id} node={child} level={level + 1} />
219
+ ))}
220
+ </div>
221
+ )}
222
+ </div>
223
+ )
224
+ }
225
+
226
+ export { TreeView, treeNodeVariants }
@@ -0,0 +1,119 @@
1
+ 'use client';
2
+
3
+ import { motion } from 'framer-motion';
4
+ import { useEffect, useState } from 'react';
5
+ import { cn } from '../../lib/utils';
6
+ import { useMotionPreference } from '../../hooks/useMotionPreference';
7
+
8
+ export interface TypewriterProps {
9
+ text: string | string[];
10
+ speed?: number;
11
+ delay?: number;
12
+ loop?: boolean;
13
+ loopDelay?: number;
14
+ cursor?: string;
15
+ showCursor?: boolean;
16
+ className?: string;
17
+ as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div';
18
+ }
19
+
20
+ export function Typewriter({
21
+ text,
22
+ speed = 0.05,
23
+ delay = 0,
24
+ loop = false,
25
+ loopDelay = 2,
26
+ cursor = '|',
27
+ showCursor = true,
28
+ className,
29
+ as: Component = 'span',
30
+ }: TypewriterProps) {
31
+ const { shouldAnimate, scale } = useMotionPreference();
32
+ const [displayedText, setDisplayedText] = useState('');
33
+
34
+ // Flatten text to array for easier handling
35
+ const textArray = Array.isArray(text) ? text : [text];
36
+
37
+ useEffect(() => {
38
+ // If reduced motion, show full text immediately
39
+ if (!shouldAnimate) {
40
+ setDisplayedText(textArray[0]);
41
+ return;
42
+ }
43
+
44
+ let timeoutId: ReturnType<typeof setTimeout>;
45
+ let isCancelled = false;
46
+
47
+ // State pointers
48
+ let stringIndex = 0;
49
+ let charIndex = 0;
50
+ let isDeleting = false;
51
+
52
+ const type = () => {
53
+ if (isCancelled) return;
54
+
55
+ const currentString = textArray[stringIndex];
56
+ const currentSpeed = (isDeleting ? speed / 2 : speed) * (scale > 0 ? (5 / scale) : 1) * 1000;
57
+
58
+ if (isDeleting) {
59
+ // DELETING
60
+ if (charIndex > 0) {
61
+ setDisplayedText(currentString.substring(0, charIndex - 1));
62
+ charIndex--;
63
+ timeoutId = setTimeout(type, currentSpeed);
64
+ } else {
65
+ // Start typing next string
66
+ isDeleting = false;
67
+ stringIndex = (stringIndex + 1) % textArray.length;
68
+ timeoutId = setTimeout(type, currentSpeed);
69
+ }
70
+ } else {
71
+ // TYPING
72
+ if (charIndex < currentString.length) {
73
+ setDisplayedText(currentString.substring(0, charIndex + 1));
74
+ charIndex++;
75
+ timeoutId = setTimeout(type, currentSpeed);
76
+ } else {
77
+ // FINISHED TYPING STRING
78
+ if (loop) {
79
+ isDeleting = true;
80
+ timeoutId = setTimeout(type, loopDelay * 1000);
81
+ } else if (stringIndex < textArray.length - 1) {
82
+ // If we have more strings to type in sequence (and not just looping one)
83
+ // Note: The original logic treated array as "loop through these options".
84
+ // To match previous behavior: array = specific sequence to loop?
85
+ // Usually typewriters with arrays toggle between them.
86
+ isDeleting = true;
87
+ timeoutId = setTimeout(type, loopDelay * 1000);
88
+ }
89
+ }
90
+ }
91
+ };
92
+
93
+ // Initial Start Delay
94
+ timeoutId = setTimeout(() => {
95
+ type();
96
+ }, delay * 1000);
97
+
98
+ return () => {
99
+ isCancelled = true;
100
+ clearTimeout(timeoutId);
101
+ };
102
+ }, [text, speed, delay, loop, loopDelay, shouldAnimate, scale]); // Re-run effect if props change
103
+
104
+ return (
105
+ <Component className={cn("inline", className)}>
106
+ <span>{displayedText}</span>
107
+ {showCursor && (
108
+ <motion.span
109
+ initial={{ opacity: 0 }}
110
+ animate={{ opacity: 1 }}
111
+ transition={{ duration: 0.5, repeat: Infinity, repeatType: "reverse" }}
112
+ className="ml-0.5 text-[var(--color-primary)] font-light"
113
+ >
114
+ {cursor}
115
+ </motion.span>
116
+ )}
117
+ </Component>
118
+ );
119
+ }