@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,243 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
4
+ import { useMotionPreference } from '../../../hooks/useMotionPreference';
5
+
6
+ export type ToastType = 'success' | 'error' | 'warning' | 'info';
7
+
8
+ export interface Toast {
9
+ id: string;
10
+ message: string;
11
+ type: ToastType;
12
+ duration?: number;
13
+ }
14
+
15
+ interface ToastContextValue {
16
+ toasts: Toast[];
17
+ addToast: (message: string, type?: ToastType, duration?: number) => void;
18
+ removeToast: (id: string) => void;
19
+ }
20
+
21
+ const ToastContext = createContext<ToastContextValue | undefined>(undefined);
22
+
23
+ export interface ToastProviderProps {
24
+ children: React.ReactNode;
25
+ /**
26
+ * Maximum number of toasts to show at once
27
+ * @default 3
28
+ */
29
+ maxToasts?: number;
30
+ /**
31
+ * Default duration in milliseconds
32
+ * @default 5000
33
+ */
34
+ defaultDuration?: number;
35
+ /**
36
+ * Position of toast container
37
+ * @default 'bottom-right'
38
+ */
39
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center';
40
+ }
41
+
42
+ /**
43
+ * ToastProvider Component
44
+ *
45
+ * Provides toast notification functionality to the app.
46
+ * Wrap your app with this provider to enable toast notifications.
47
+ *
48
+ * Example:
49
+ * ```tsx
50
+ * <ToastProvider position="bottom-right" maxToasts={3}>
51
+ * <App />
52
+ * </ToastProvider>
53
+ * ```
54
+ */
55
+ export const ToastProvider: React.FC<ToastProviderProps> = ({
56
+ children,
57
+ maxToasts = 3,
58
+ defaultDuration = 5000,
59
+ position = 'bottom-right',
60
+ }) => {
61
+ const [toasts, setToasts] = useState<Toast[]>([]);
62
+ const { shouldAnimate } = useMotionPreference();
63
+
64
+ const removeToast = useCallback((id: string) => {
65
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
66
+ }, []);
67
+
68
+ // Inject animation styles
69
+ useEffect(() => {
70
+ if (typeof document !== 'undefined' && !document.getElementById('sds-toast-animations')) {
71
+ const style = document.createElement('style');
72
+ style.id = 'sds-toast-animations';
73
+ style.textContent = `
74
+ @keyframes slide-in-right {
75
+ from {
76
+ transform: translateX(100%);
77
+ opacity: 0;
78
+ }
79
+ to {
80
+ transform: translateX(0);
81
+ opacity: 1;
82
+ }
83
+ }
84
+ .animate-slide-in-right {
85
+ animation: slide-in-right 0.3s ease-out;
86
+ }
87
+ `;
88
+ document.head.appendChild(style);
89
+ }
90
+ }, []);
91
+
92
+ const addToast = useCallback(
93
+ (message: string, type: ToastType = 'info', duration = defaultDuration) => {
94
+ const id = Math.random().toString(36).substring(2, 9);
95
+ const newToast: Toast = { id, message, type, duration };
96
+
97
+ setToasts((prev) => {
98
+ const updated = [...prev, newToast];
99
+ return updated.slice(-maxToasts);
100
+ });
101
+
102
+ if (duration > 0) {
103
+ setTimeout(() => removeToast(id), duration);
104
+ }
105
+ },
106
+ [defaultDuration, maxToasts, removeToast]
107
+ );
108
+
109
+ const positionClasses = {
110
+ 'top-left': 'top-4 left-4',
111
+ 'top-right': 'top-4 right-4',
112
+ 'bottom-left': 'bottom-4 left-4',
113
+ 'bottom-right': 'bottom-4 right-4',
114
+ 'top-center': 'top-4 left-1/2 -translate-x-1/2',
115
+ 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
116
+ };
117
+
118
+ return (
119
+ <ToastContext.Provider value={{ toasts, addToast, removeToast }}>
120
+ {children}
121
+ <div
122
+ className={`fixed ${positionClasses[position]} z-[9999] flex flex-col gap-2 pointer-events-none`}
123
+ aria-live="polite"
124
+ aria-atomic="true"
125
+ >
126
+ {toasts.map((toast) => (
127
+ <ToastItem
128
+ key={toast.id}
129
+ toast={toast}
130
+ onClose={() => removeToast(toast.id)}
131
+ shouldAnimate={shouldAnimate}
132
+ />
133
+ ))}
134
+ </div>
135
+ </ToastContext.Provider>
136
+ );
137
+ };
138
+
139
+ interface ToastItemProps {
140
+ toast: Toast;
141
+ onClose: () => void;
142
+ shouldAnimate: boolean;
143
+ }
144
+
145
+ const ToastItem: React.FC<ToastItemProps> = ({ toast, onClose, shouldAnimate }) => {
146
+ const typeStyles = {
147
+ success: 'bg-[var(--color-success)] text-[var(--color-success-foreground)] border-[var(--color-success)]',
148
+ error: 'bg-[var(--color-error)] text-[var(--color-error-foreground)] border-[var(--color-error)]',
149
+ warning: 'bg-[var(--color-warning)] text-[var(--color-warning-foreground)] border-[var(--color-warning)]',
150
+ info: 'bg-[var(--color-info)] text-[var(--color-info-foreground)] border-[var(--color-info)]',
151
+ };
152
+
153
+ const icons = {
154
+ success: (
155
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
156
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
157
+ <polyline points="22 4 12 14.01 9 11.01" />
158
+ </svg>
159
+ ),
160
+ error: (
161
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
162
+ <circle cx="12" cy="12" r="10" />
163
+ <line x1="15" y1="9" x2="9" y2="15" />
164
+ <line x1="9" y1="9" x2="15" y2="15" />
165
+ </svg>
166
+ ),
167
+ warning: (
168
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
169
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
170
+ <line x1="12" y1="9" x2="12" y2="13" />
171
+ <line x1="12" y1="17" x2="12.01" y2="17" />
172
+ </svg>
173
+ ),
174
+ info: (
175
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
176
+ <circle cx="12" cy="12" r="10" />
177
+ <line x1="12" y1="16" x2="12" y2="12" />
178
+ <line x1="12" y1="8" x2="12.01" y2="8" />
179
+ </svg>
180
+ ),
181
+ };
182
+
183
+ return (
184
+ <div
185
+ className={`
186
+ ${typeStyles[toast.type]}
187
+ flex items-center gap-3 px-4 py-3 rounded-lg shadow-lg border-2
188
+ min-w-[300px] max-w-[400px] pointer-events-auto
189
+ ${shouldAnimate ? 'animate-slide-in-right' : ''}
190
+ `}
191
+ role="alert"
192
+ >
193
+ <div className="flex-shrink-0" aria-hidden="true">
194
+ {icons[toast.type]}
195
+ </div>
196
+ <p className="flex-1 text-sm font-medium">{toast.message}</p>
197
+ <button
198
+ onClick={onClose}
199
+ className="flex-shrink-0 text-current hover:opacity-70 transition-opacity"
200
+ aria-label="Close notification"
201
+ >
202
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
203
+ <line x1="18" y1="6" x2="6" y2="18" />
204
+ <line x1="6" y1="6" x2="18" y2="18" />
205
+ </svg>
206
+ </button>
207
+ </div>
208
+ );
209
+ };
210
+
211
+ /**
212
+ * useToast Hook
213
+ *
214
+ * Hook to trigger toast notifications from anywhere in your app.
215
+ *
216
+ * Example:
217
+ * ```tsx
218
+ * function MyComponent() {
219
+ * const { toast } = useToast();
220
+ *
221
+ * const handleClick = () => {
222
+ * toast('Settings saved!', 'success');
223
+ * };
224
+ *
225
+ * return <button onClick={handleClick}>Save</button>;
226
+ * }
227
+ * ```
228
+ */
229
+ export const useToast = () => {
230
+ const context = useContext(ToastContext);
231
+
232
+ if (!context) {
233
+ throw new Error('useToast must be used within a ToastProvider');
234
+ }
235
+
236
+ return {
237
+ toast: context.addToast,
238
+ removeToast: context.removeToast,
239
+ toasts: context.toasts,
240
+ };
241
+ };
242
+
243
+
@@ -0,0 +1,2 @@
1
+ export { ToastProvider, useToast } from './Toast';
2
+ export type { Toast, ToastType, ToastProviderProps } from './Toast';
@@ -0,0 +1,9 @@
1
+ export * from './Alert';
2
+ export * from './Progress';
3
+ export * from './ProgressBar';
4
+ export * from './Skeleton';
5
+ export * from './Sonner';
6
+ export * from './Spinner';
7
+ export * from './Toast';
8
+ export * from './EmptyState';
9
+ export * from './Stepper';
@@ -0,0 +1,40 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+ import { describe, it, expect, vi } from 'vitest'
4
+ import { Checkbox } from './Checkbox'
5
+
6
+ describe('Checkbox', () => {
7
+ it('renders as a checkbox', () => {
8
+ render(<Checkbox aria-label="Accept terms" />)
9
+ expect(screen.getByRole('checkbox')).toBeInTheDocument()
10
+ })
11
+
12
+ it('handles checked state', async () => {
13
+ const user = userEvent.setup()
14
+ const onCheckedChange = vi.fn()
15
+ render(<Checkbox onCheckedChange={onCheckedChange} aria-label="Accept" />)
16
+
17
+ await user.click(screen.getByRole('checkbox'))
18
+ expect(onCheckedChange).toHaveBeenCalledWith(true)
19
+ })
20
+
21
+ it('renders checked state', () => {
22
+ render(<Checkbox checked aria-label="Accept" />)
23
+ expect(screen.getByRole('checkbox')).toHaveAttribute('data-state', 'checked')
24
+ })
25
+
26
+ it('renders unchecked state', () => {
27
+ render(<Checkbox checked={false} aria-label="Accept" />)
28
+ expect(screen.getByRole('checkbox')).toHaveAttribute('data-state', 'unchecked')
29
+ })
30
+
31
+ it('can be disabled', () => {
32
+ render(<Checkbox disabled aria-label="Accept" />)
33
+ expect(screen.getByRole('checkbox')).toBeDisabled()
34
+ })
35
+
36
+ it('applies custom className', () => {
37
+ render(<Checkbox className="custom-check" aria-label="Accept" />)
38
+ expect(screen.getByRole('checkbox')).toHaveClass('custom-check')
39
+ })
40
+ })
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import * as React from "react"
3
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
4
+ import { Check } from "lucide-react"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ const Checkbox = (
9
+ {
10
+ ref,
11
+ className,
12
+ ...props
13
+ }: React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & {
14
+ ref?: React.Ref<React.ElementRef<typeof CheckboxPrimitive.Root>>;
15
+ }
16
+ ) => (<CheckboxPrimitive.Root
17
+ ref={ref}
18
+ className={cn(
19
+ "peer h-4 w-4 shrink-0 rounded-xs border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
20
+ className
21
+ )}
22
+ {...props}
23
+ >
24
+ <CheckboxPrimitive.Indicator
25
+ className={cn("flex items-center justify-center text-current")}
26
+ >
27
+ <Check className="h-4 w-4" />
28
+ </CheckboxPrimitive.Indicator>
29
+ </CheckboxPrimitive.Root>)
30
+
31
+ export { Checkbox }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * ColorPicker Component
5
+ * Combined visual color picker and hex input for selecting colors
6
+ */
7
+
8
+ import { useState, useEffect } from 'react';
9
+ import { Label } from './Label';
10
+ import { Input } from './Input';
11
+
12
+ export interface ColorPickerProps {
13
+ label?: string;
14
+ description?: string;
15
+ value: string; // Hex color value
16
+ onChange: (hex: string) => void;
17
+ optional?: boolean;
18
+ disabled?: boolean;
19
+ }
20
+
21
+ export function ColorPicker({
22
+ label,
23
+ description,
24
+ value,
25
+ onChange,
26
+ optional = false,
27
+ disabled = false,
28
+ }: ColorPickerProps) {
29
+ const [hexInput, setHexInput] = useState(value);
30
+ const [isValid, setIsValid] = useState(true);
31
+
32
+ // Sync hex input with value prop
33
+ useEffect(() => {
34
+ setHexInput(value);
35
+ }, [value]);
36
+
37
+ // Validate hex color format
38
+ const validateHex = (hex: string): boolean => {
39
+ return /^#[0-9A-Fa-f]{6}$/.test(hex);
40
+ };
41
+
42
+ const handleHexChange = (newHex: string) => {
43
+ setHexInput(newHex);
44
+
45
+ if (validateHex(newHex)) {
46
+ setIsValid(true);
47
+ onChange(newHex);
48
+ } else {
49
+ setIsValid(false);
50
+ }
51
+ };
52
+
53
+ const handleColorPickerChange = (e: React.ChangeEvent<HTMLInputElement>) => {
54
+ const newColor = e.target.value;
55
+ setHexInput(newColor);
56
+ setIsValid(true);
57
+ onChange(newColor);
58
+ };
59
+
60
+ return (
61
+ <div className="space-y-2">
62
+ {label && (
63
+ <div className="flex items-baseline justify-between">
64
+ <Label className="text-sm font-medium mb-0">
65
+ {label}
66
+ {optional && <span className="text-xs text-muted-foreground ml-1">(optional)</span>}
67
+ </Label>
68
+ </div>
69
+ )}
70
+
71
+ {description && (
72
+ <p className="text-xs text-muted-foreground">
73
+ {description}
74
+ </p>
75
+ )}
76
+
77
+ <div className="flex gap-3 items-center">
78
+ {/* Visual Color Picker */}
79
+ <div className="relative">
80
+ <input
81
+ type="color"
82
+ value={hexInput}
83
+ onChange={handleColorPickerChange}
84
+ disabled={disabled}
85
+ className="
86
+ w-16 h-16
87
+ rounded-lg
88
+ cursor-pointer
89
+ border-2 border-border
90
+ disabled:opacity-50 disabled:cursor-not-allowed
91
+ transition-all
92
+ hover:scale-105
93
+ "
94
+ title="Pick a color"
95
+ />
96
+ </div>
97
+
98
+ {/* Hex Input */}
99
+ <div className="flex-1">
100
+ <Input
101
+ type="text"
102
+ value={hexInput}
103
+ onChange={(e) => handleHexChange(e.target.value)}
104
+ placeholder="#000000"
105
+ disabled={disabled}
106
+ className={`font-mono ${!isValid ? 'border-error' : ''}`}
107
+ aria-invalid={!isValid}
108
+ />
109
+ {!isValid && (
110
+ <p className="text-xs text-error mt-1">
111
+ Invalid hex format (use #RRGGBB)
112
+ </p>
113
+ )}
114
+ </div>
115
+ </div>
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,96 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Check, ChevronsUpDown } from "lucide-react"
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Button } from "../actions/Button"
8
+ import {
9
+ Command,
10
+ CommandEmpty,
11
+ CommandGroup,
12
+ CommandInput,
13
+ CommandItem,
14
+ CommandList,
15
+ } from "../navigation/Command"
16
+ import {
17
+ Popover,
18
+ PopoverContent,
19
+ PopoverTrigger,
20
+ } from "../overlays/Popover"
21
+
22
+ export interface ComboboxOption {
23
+ value: string
24
+ label: string
25
+ }
26
+
27
+ export interface ComboboxProps {
28
+ options: ComboboxOption[]
29
+ value?: string
30
+ onValueChange?: (value: string) => void
31
+ placeholder?: string
32
+ searchPlaceholder?: string
33
+ emptyMessage?: string
34
+ className?: string
35
+ disabled?: boolean
36
+ }
37
+
38
+ export function Combobox({
39
+ options = [],
40
+ value,
41
+ onValueChange,
42
+ placeholder = "Select option...",
43
+ searchPlaceholder = "Search...",
44
+ emptyMessage = "No option found.",
45
+ className,
46
+ disabled = false,
47
+ }: ComboboxProps) {
48
+ const [open, setOpen] = React.useState(false)
49
+
50
+ return (
51
+ <Popover open={open} onOpenChange={setOpen}>
52
+ <PopoverTrigger asChild>
53
+ <Button
54
+ variant="outline"
55
+ role="combobox"
56
+ aria-expanded={open}
57
+ className={cn("w-full justify-between", className)}
58
+ disabled={disabled}
59
+ >
60
+ {value
61
+ ? options.find((option) => option.value === value)?.label
62
+ : placeholder}
63
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
64
+ </Button>
65
+ </PopoverTrigger>
66
+ <PopoverContent className="w-[--radix-popover-trigger-width] p-0">
67
+ <Command>
68
+ <CommandInput placeholder={searchPlaceholder} />
69
+ <CommandList>
70
+ <CommandEmpty>{emptyMessage}</CommandEmpty>
71
+ <CommandGroup>
72
+ {options.map((option) => (
73
+ <CommandItem
74
+ key={option.value}
75
+ value={option.value}
76
+ onSelect={(currentValue) => {
77
+ onValueChange?.(currentValue === value ? "" : currentValue)
78
+ setOpen(false)
79
+ }}
80
+ >
81
+ <Check
82
+ className={cn(
83
+ "mr-2 h-4 w-4",
84
+ value === option.value ? "opacity-100" : "opacity-0"
85
+ )}
86
+ />
87
+ {option.label}
88
+ </CommandItem>
89
+ ))}
90
+ </CommandGroup>
91
+ </CommandList>
92
+ </Command>
93
+ </PopoverContent>
94
+ </Popover>
95
+ )
96
+ }