@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,175 @@
1
+ 'use client';;
2
+ import React from 'react';
3
+ import { GitHubIcon } from '../../data-display/GitHubIcon';
4
+
5
+ export interface FooterLink {
6
+ label: string;
7
+ href: string;
8
+ external?: boolean;
9
+ }
10
+
11
+ export interface FooterSection {
12
+ title: string;
13
+ links: FooterLink[];
14
+ }
15
+
16
+ export interface FooterProps {
17
+ /**
18
+ * Brand/logo element or text
19
+ */
20
+ logo?: React.ReactNode;
21
+ /**
22
+ * Sections with links organized in columns
23
+ */
24
+ sections?: FooterSection[];
25
+ /**
26
+ * Social links (will be displayed with icons)
27
+ */
28
+ socialLinks?: {
29
+ github?: string;
30
+ linkedin?: string;
31
+ email?: string;
32
+ };
33
+ /**
34
+ * Copyright text
35
+ */
36
+ copyright?: string;
37
+ /**
38
+ * Additional className for customization
39
+ */
40
+ className?: string;
41
+ }
42
+
43
+ /**
44
+ * Footer Organism
45
+ *
46
+ * Inspired by Swiss Grid design principles:
47
+ * - 8px base unit spacing system
48
+ * - Clear typographic hierarchy with structured columns
49
+ * - Generous whitespace for breathing room
50
+ * - Grid-based layout with precise alignment
51
+ * - Minimal, functional aesthetic
52
+ *
53
+ * Features:
54
+ * - Responsive multi-column layout
55
+ * - Social links with icons
56
+ * - Organized content sections
57
+ * - Clean typography and spacing
58
+ */
59
+ export const Footer = (
60
+ {
61
+ ref,
62
+ logo,
63
+ sections = [],
64
+ socialLinks,
65
+ copyright,
66
+ className = ''
67
+ }: FooterProps & {
68
+ ref?: React.Ref<HTMLElement>;
69
+ }
70
+ ) => {
71
+ const currentYear = new Date().getFullYear();
72
+
73
+ return (
74
+ <footer
75
+ ref={ref}
76
+ className={`
77
+ border-t border-[var(--color-border)]
78
+ bg-[var(--color-background)]
79
+ ${className}
80
+ `}
81
+ >
82
+ {/* Main Footer Content - Swiss Grid: 8px base units */}
83
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 sm:py-20 lg:py-24">
84
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-12 lg:gap-8">
85
+ {/* Brand Section - Swiss Grid: Takes 4 columns on large screens */}
86
+ <div className="lg:col-span-4">
87
+ {logo && (
88
+ <div className="mb-6 text-2xl font-bold text-[var(--color-text-primary)]">
89
+ {logo}
90
+ </div>
91
+ )}
92
+ </div>
93
+
94
+ {/* Navigation Sections - Swiss Grid: Evenly distributed columns */}
95
+ {sections.map((section, index) => (
96
+ <div key={section.title} className="lg:col-span-2">
97
+ <h3 className="text-sm font-semibold text-[var(--color-text-primary)] uppercase tracking-wider mb-4">
98
+ {section.title}
99
+ </h3>
100
+ <ul className="space-y-3">
101
+ {section.links.map((link) => (
102
+ <li key={link.label}>
103
+ <a
104
+ href={link.href}
105
+ target={link.external ? '_blank' : undefined}
106
+ rel={link.external ? 'noopener noreferrer' : undefined}
107
+ className="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors duration-200 text-sm"
108
+ >
109
+ {link.label}
110
+ </a>
111
+ </li>
112
+ ))}
113
+ </ul>
114
+ </div>
115
+ ))}
116
+
117
+ {/* Social Links - Swiss Grid: Takes remaining columns */}
118
+ {socialLinks && (
119
+ <div className="lg:col-span-2">
120
+ <h3 className="text-sm font-semibold text-[var(--color-text-primary)] uppercase tracking-wider mb-4">
121
+ Connect
122
+ </h3>
123
+ <ul className="space-y-3">
124
+ {socialLinks.github && (
125
+ <li>
126
+ <a
127
+ href={socialLinks.github}
128
+ target="_blank"
129
+ rel="noopener noreferrer"
130
+ className="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors duration-200 text-sm flex items-center gap-2"
131
+ >
132
+ <GitHubIcon size={16} />
133
+ GitHub
134
+ </a>
135
+ </li>
136
+ )}
137
+ {socialLinks.linkedin && (
138
+ <li>
139
+ <a
140
+ href={socialLinks.linkedin}
141
+ target="_blank"
142
+ rel="noopener noreferrer"
143
+ className="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors duration-200 text-sm"
144
+ >
145
+ LinkedIn
146
+ </a>
147
+ </li>
148
+ )}
149
+ {socialLinks.email && (
150
+ <li>
151
+ <a
152
+ href={`mailto:${socialLinks.email}`}
153
+ className="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors duration-200 text-sm"
154
+ >
155
+ Email
156
+ </a>
157
+ </li>
158
+ )}
159
+ </ul>
160
+ </div>
161
+ )}
162
+ </div>
163
+ </div>
164
+
165
+ {/* Bottom Bar - Swiss Grid: 8px base unit spacing */}
166
+ <div className="border-t border-[var(--color-border)]">
167
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
168
+ <p className="text-sm text-[var(--color-text-secondary)] text-center">
169
+ {copyright || `© ${currentYear} All rights reserved.`}
170
+ </p>
171
+ </div>
172
+ </div>
173
+ </footer>
174
+ );
175
+ };
@@ -0,0 +1,2 @@
1
+ export { Footer } from './Footer';
2
+ export type { FooterProps, FooterSection, FooterLink } from './Footer';
@@ -0,0 +1,82 @@
1
+ import type { CSSProperties, ReactNode, Ref } from 'react'
2
+
3
+ export type GlassThickness = 'ultrathin' | 'thin' | 'medium' | 'thick' | 'ultrathick'
4
+
5
+ /** 0 = pure white, 9 = pure black. 10 even steps. */
6
+ export type GlassTint = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
7
+
8
+ const TINT_COLORS: Record<GlassTint, string> = {
9
+ 0: '255, 255, 255',
10
+ 1: '227, 227, 227',
11
+ 2: '200, 200, 200',
12
+ 3: '170, 170, 170',
13
+ 4: '142, 142, 142',
14
+ 5: '128, 128, 128',
15
+ 6: '113, 113, 113',
16
+ 7: '85, 85, 85',
17
+ 8: '57, 57, 57',
18
+ 9: '0, 0, 0',
19
+ }
20
+
21
+ export interface GlassSurfaceProps {
22
+ /** Whether the frosted glass effect is active (background + shadow). */
23
+ active?: boolean
24
+ /** Shadow direction: 'top' casts downward, 'bottom' casts upward. */
25
+ position?: 'top' | 'bottom'
26
+ /** Glass opacity tier (default 'thin').
27
+ * ultrathin 28% → thin 38% → medium 48% → thick 58% → ultrathick 68%. */
28
+ thickness?: GlassThickness
29
+ /** Glass fill color for light mode: 0 = white, 9 = black (default 2). */
30
+ tint?: GlassTint
31
+ /** Glass fill color for dark mode. Defaults to `9 - tint` (auto-invert). */
32
+ darkTint?: GlassTint
33
+ className?: string
34
+ style?: CSSProperties
35
+ children: ReactNode
36
+ ref?: Ref<HTMLDivElement>
37
+ }
38
+
39
+ /**
40
+ * Liquid glass surface with five thickness tiers and a 10-step tint scale.
41
+ *
42
+ * `backdrop-filter: blur(20px) saturate(180%)` provides the frosted
43
+ * blur; `thickness` controls opacity; `tint` controls the fill color
44
+ * from pure white (0) through neutral gray to pure black (9).
45
+ */
46
+ export function GlassSurface({
47
+ active = true,
48
+ position = 'top',
49
+ thickness = 'thin',
50
+ tint = 2,
51
+ darkTint,
52
+ className,
53
+ style,
54
+ children,
55
+ ref,
56
+ }: GlassSurfaceProps) {
57
+ const resolvedDarkTint = darkTint ?? (9 - tint) as GlassTint
58
+
59
+ const classes = [
60
+ 'glass-surface',
61
+ position === 'top' ? 'glass-surface--top' : 'glass-surface--bottom',
62
+ `glass-surface--${thickness}`,
63
+ active && 'glass-surface--active',
64
+ className,
65
+ ].filter(Boolean).join(' ')
66
+
67
+ const inlineStyles: CSSProperties = {
68
+ WebkitBackdropFilter: 'blur(20px) saturate(180%)',
69
+ backdropFilter: 'blur(20px) saturate(180%)',
70
+ ...(active ? {
71
+ '--glass-tint-light': TINT_COLORS[tint],
72
+ '--glass-tint-dark': TINT_COLORS[resolvedDarkTint],
73
+ } as CSSProperties : undefined),
74
+ ...style,
75
+ }
76
+
77
+ return (
78
+ <div ref={ref} className={classes} style={inlineStyles}>
79
+ {children}
80
+ </div>
81
+ )
82
+ }
@@ -0,0 +1,31 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Grid } from './Grid'
4
+
5
+ describe('Grid', () => {
6
+ it('renders children in a grid container', () => {
7
+ render(
8
+ <Grid>
9
+ <div>Cell 1</div>
10
+ <div>Cell 2</div>
11
+ </Grid>
12
+ )
13
+ expect(screen.getByText('Cell 1')).toBeInTheDocument()
14
+ expect(screen.getByText('Cell 2')).toBeInTheDocument()
15
+ })
16
+
17
+ it('defaults to 1 column', () => {
18
+ const { container } = render(<Grid><div>Child</div></Grid>)
19
+ expect(container.firstChild).toHaveClass('grid', 'grid-cols-1')
20
+ })
21
+
22
+ it('renders specified columns', () => {
23
+ const { container } = render(<Grid columns={3}><div>Child</div></Grid>)
24
+ expect(container.firstChild).toHaveClass('grid-cols-3')
25
+ })
26
+
27
+ it('applies custom className', () => {
28
+ const { container } = render(<Grid className="custom-grid"><div>Child</div></Grid>)
29
+ expect(container.firstChild).toHaveClass('custom-grid')
30
+ })
31
+ })
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+
3
+ type ResponsiveValue<T> = T | { base?: T; sm?: T; md?: T; lg?: T; xl?: T };
4
+
5
+ export interface GridProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ children: React.ReactNode;
7
+ /**
8
+ * Number of columns. Supports responsive object.
9
+ * @example columns={3} or columns={{ base: 1, md: 3 }}
10
+ */
11
+ columns?: ResponsiveValue<number>;
12
+ /**
13
+ * Gap between items. Supports responsive object.
14
+ * Maps to Tailwind gap scale.
15
+ */
16
+ gap?: ResponsiveValue<number>;
17
+ /**
18
+ * HTML element to render as
19
+ */
20
+ as?: any;
21
+ }
22
+
23
+ const mapResponsive = (prop: ResponsiveValue<number>, prefix: string, mapFunc: (v: number) => string) => {
24
+ if (typeof prop === 'number') {
25
+ return mapFunc(prop);
26
+ }
27
+ const classes = [];
28
+ if (prop.base) classes.push(mapFunc(prop.base));
29
+ if (prop.sm) classes.push(`sm:${mapFunc(prop.sm)}`);
30
+ if (prop.md) classes.push(`md:${mapFunc(prop.md)}`);
31
+ if (prop.lg) classes.push(`lg:${mapFunc(prop.lg)}`);
32
+ if (prop.xl) classes.push(`xl:${mapFunc(prop.xl)}`);
33
+ return classes.join(' ');
34
+ };
35
+
36
+ export const Grid = (
37
+ {
38
+ ref,
39
+ children,
40
+ columns = 1,
41
+ gap = 4,
42
+ as: Component = 'div',
43
+ className = '',
44
+ ...props
45
+ }: GridProps & {
46
+ ref?: React.Ref<HTMLDivElement>;
47
+ }
48
+ ) => {
49
+
50
+ // Safe mapping for Tailwind scanner
51
+ const getColClass = (n: number) => {
52
+ const map: Record<number, string> = {
53
+ 1: 'grid-cols-1', 2: 'grid-cols-2', 3: 'grid-cols-3', 4: 'grid-cols-4',
54
+ 5: 'grid-cols-5', 6: 'grid-cols-6', 7: 'grid-cols-7', 8: 'grid-cols-8',
55
+ 9: 'grid-cols-9', 10: 'grid-cols-10', 11: 'grid-cols-11', 12: 'grid-cols-12'
56
+ };
57
+ return map[n] || 'grid-cols-1';
58
+ };
59
+
60
+ const getGapClass = (n: number) => `gap-${n}`;
61
+
62
+ const colClasses = mapResponsive(columns, 'grid-cols', getColClass);
63
+ const gapClasses = mapResponsive(gap, 'gap', getGapClass);
64
+
65
+ return (
66
+ <Component
67
+ ref={ref}
68
+ className={`grid ${colClasses} ${gapClasses} ${className}`}
69
+ {...props}
70
+ >
71
+ {children}
72
+ </Component>
73
+ );
74
+ };
75
+
76
+ export interface GridItemProps extends React.HTMLAttributes<HTMLDivElement> {
77
+ children: React.ReactNode;
78
+ colSpan?: ResponsiveValue<number>;
79
+ rowSpan?: ResponsiveValue<number>;
80
+ colStart?: ResponsiveValue<number>;
81
+ as?: any;
82
+ }
83
+
84
+ export const GridItem = (
85
+ {
86
+ ref,
87
+ children,
88
+ colSpan,
89
+ rowSpan,
90
+ colStart,
91
+ as: Component = 'div',
92
+ className = '',
93
+ ...props
94
+ }: GridItemProps & {
95
+ ref?: React.Ref<HTMLDivElement>;
96
+ }
97
+ ) => {
98
+
99
+ const getSpanClass = (n: number) => {
100
+ const map: Record<number, string> = {
101
+ 1: 'col-span-1', 2: 'col-span-2', 3: 'col-span-3', 4: 'col-span-4',
102
+ 5: 'col-span-5', 6: 'col-span-6', 7: 'col-span-7', 8: 'col-span-8',
103
+ 9: 'col-span-9', 10: 'col-span-10', 11: 'col-span-11', 12: 'col-span-12'
104
+ };
105
+ return map[n] || '';
106
+ };
107
+
108
+ const getRowSpanClass = (n: number) => {
109
+ const map: Record<number, string> = {
110
+ 1: 'row-span-1', 2: 'row-span-2', 3: 'row-span-3', 4: 'row-span-4',
111
+ 5: 'row-span-5', 6: 'row-span-6'
112
+ };
113
+ return map[n] || '';
114
+ };
115
+
116
+ const getColStartClass = (n: number) => `col-start-${n}`;
117
+
118
+ const classes = [
119
+ colSpan ? mapResponsive(colSpan, 'col-span', getSpanClass) : '',
120
+ rowSpan ? mapResponsive(rowSpan, 'row-span', getRowSpanClass) : '',
121
+ colStart ? mapResponsive(colStart, 'col-start', getColStartClass) : '',
122
+ className
123
+ ].filter(Boolean).join(' ');
124
+
125
+ return (
126
+ <Component ref={ref} className={classes} {...props}>
127
+ {children}
128
+ </Component>
129
+ );
130
+ };