@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.
- package/.claude/CLAUDE.md +239 -0
- package/README.md +161 -0
- package/dist/cli.mjs +151 -0
- package/dist/dates.d.mts +20 -0
- package/dist/dates.d.ts +20 -0
- package/dist/dates.js +240 -0
- package/dist/dates.js.map +1 -0
- package/dist/dates.mjs +203 -0
- package/dist/dates.mjs.map +1 -0
- package/dist/dnd.d.mts +126 -0
- package/dist/dnd.d.ts +126 -0
- package/dist/dnd.js +274 -0
- package/dist/dnd.js.map +1 -0
- package/dist/dnd.mjs +250 -0
- package/dist/dnd.mjs.map +1 -0
- package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
- package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
- package/dist/forms.d.mts +38 -0
- package/dist/forms.d.ts +38 -0
- package/dist/forms.js +198 -0
- package/dist/forms.js.map +1 -0
- package/dist/forms.mjs +159 -0
- package/dist/forms.mjs.map +1 -0
- package/dist/hooks-1b8WaQf1.d.mts +225 -0
- package/dist/hooks-CKW8vE9H.d.ts +225 -0
- package/dist/hooks.d.mts +3 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +971 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hooks.mjs +943 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/index-DscTIrZ2.d.mts +29 -0
- package/dist/index-DscTIrZ2.d.ts +29 -0
- package/dist/index.d.mts +3382 -0
- package/dist/index.d.ts +3382 -0
- package/dist/index.js +15146 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14802 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers-CXPDMsl7.d.mts +30 -0
- package/dist/providers-Dn_Msjvz.d.ts +30 -0
- package/dist/providers.d.mts +3 -0
- package/dist/providers.d.ts +3 -0
- package/dist/providers.js +1885 -0
- package/dist/providers.js.map +1 -0
- package/dist/providers.mjs +1859 -0
- package/dist/providers.mjs.map +1 -0
- package/dist/tables.d.mts +10 -0
- package/dist/tables.d.ts +10 -0
- package/dist/tables.js +248 -0
- package/dist/tables.js.map +1 -0
- package/dist/tables.mjs +218 -0
- package/dist/tables.mjs.map +1 -0
- package/dist/tokens.d.mts +1065 -0
- package/dist/tokens.d.ts +1065 -0
- package/dist/tokens.js +2637 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tokens.mjs +2555 -0
- package/dist/tokens.mjs.map +1 -0
- package/dist/utils-CIIM7dAC.d.ts +986 -0
- package/dist/utils-Cs04sxth.d.mts +986 -0
- package/dist/utils.d.mts +4 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +874 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +806 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/validation-Bj1ye-v_.d.mts +114 -0
- package/dist/validation-Bj1ye-v_.d.ts +114 -0
- package/dist/webgl.d.mts +104 -0
- package/dist/webgl.d.ts +104 -0
- package/dist/webgl.js +226 -0
- package/dist/webgl.js.map +1 -0
- package/dist/webgl.mjs +195 -0
- package/dist/webgl.mjs.map +1 -0
- package/package.json +267 -0
- package/src/cli.ts +206 -0
- package/src/component-registry.ts +183 -0
- package/src/components/actions/Button.test.tsx +61 -0
- package/src/components/actions/Button.tsx +70 -0
- package/src/components/actions/Link.tsx +78 -0
- package/src/components/actions/Magnetic.tsx +68 -0
- package/src/components/actions/Toggle.test.tsx +40 -0
- package/src/components/actions/Toggle.tsx +47 -0
- package/src/components/actions/ToggleGroup.tsx +70 -0
- package/src/components/actions/index.ts +5 -0
- package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
- package/src/components/backgrounds/OrbBackground.tsx +424 -0
- package/src/components/backgrounds/WarpBackground.tsx +358 -0
- package/src/components/backgrounds/index.ts +3 -0
- package/src/components/blocks/Hero.tsx +142 -0
- package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
- package/src/components/cursor/SplashCursor.tsx +1315 -0
- package/src/components/cursor/TargetCursor.tsx +187 -0
- package/src/components/cursor/index.ts +2 -0
- package/src/components/data-display/AspectImage.tsx +73 -0
- package/src/components/data-display/Avatar.test.tsx +35 -0
- package/src/components/data-display/Avatar.tsx +55 -0
- package/src/components/data-display/Badge.test.tsx +43 -0
- package/src/components/data-display/Badge.tsx +84 -0
- package/src/components/data-display/Brand.tsx +123 -0
- package/src/components/data-display/Calendar.tsx +70 -0
- package/src/components/data-display/Card.test.tsx +92 -0
- package/src/components/data-display/Card.tsx +115 -0
- package/src/components/data-display/Code.tsx +210 -0
- package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
- package/src/components/data-display/DataTable.tsx +119 -0
- package/src/components/data-display/DescriptionList.tsx +41 -0
- package/src/components/data-display/GitHubIcon.tsx +44 -0
- package/src/components/data-display/Heading.test.tsx +36 -0
- package/src/components/data-display/Heading.tsx +83 -0
- package/src/components/data-display/StatCard.tsx +195 -0
- package/src/components/data-display/Table.tsx +133 -0
- package/src/components/data-display/Text.test.tsx +48 -0
- package/src/components/data-display/Text.tsx +144 -0
- package/src/components/data-display/Timeline.tsx +194 -0
- package/src/components/data-display/TreeView.tsx +226 -0
- package/src/components/data-display/Typewriter.tsx +119 -0
- package/src/components/data-display/VariableWeightText.tsx +130 -0
- package/src/components/data-display/index.ts +19 -0
- package/src/components/feedback/Alert.test.tsx +44 -0
- package/src/components/feedback/Alert.tsx +65 -0
- package/src/components/feedback/EmptyState.tsx +113 -0
- package/src/components/feedback/Progress.test.tsx +60 -0
- package/src/components/feedback/Progress.tsx +30 -0
- package/src/components/feedback/ProgressBar.tsx +158 -0
- package/src/components/feedback/Skeleton.test.tsx +39 -0
- package/src/components/feedback/Skeleton.tsx +45 -0
- package/src/components/feedback/Sonner.tsx +28 -0
- package/src/components/feedback/Spinner.test.tsx +33 -0
- package/src/components/feedback/Spinner.tsx +99 -0
- package/src/components/feedback/Stepper.tsx +307 -0
- package/src/components/feedback/Toast/Toast.tsx +243 -0
- package/src/components/feedback/Toast/index.ts +2 -0
- package/src/components/feedback/index.ts +9 -0
- package/src/components/forms/Checkbox.test.tsx +40 -0
- package/src/components/forms/Checkbox.tsx +31 -0
- package/src/components/forms/ColorPicker.tsx +118 -0
- package/src/components/forms/Combobox.tsx +96 -0
- package/src/components/forms/DragDrop.tsx +440 -0
- package/src/components/forms/FileUpload.tsx +252 -0
- package/src/components/forms/FilterButton.tsx +65 -0
- package/src/components/forms/Form.tsx +197 -0
- package/src/components/forms/Input.test.tsx +46 -0
- package/src/components/forms/Input.tsx +43 -0
- package/src/components/forms/InputOTP.tsx +81 -0
- package/src/components/forms/Label.test.tsx +20 -0
- package/src/components/forms/Label.tsx +25 -0
- package/src/components/forms/RadioGroup.tsx +51 -0
- package/src/components/forms/SearchBar.tsx +215 -0
- package/src/components/forms/Select.test.tsx +118 -0
- package/src/components/forms/Select.tsx +274 -0
- package/src/components/forms/Slider.tsx +29 -0
- package/src/components/forms/Switch.test.tsx +76 -0
- package/src/components/forms/Switch.tsx +30 -0
- package/src/components/forms/TextField.tsx +152 -0
- package/src/components/forms/Textarea.test.tsx +41 -0
- package/src/components/forms/Textarea.tsx +29 -0
- package/src/components/forms/ThemeSwitcher.tsx +290 -0
- package/src/components/forms/ThemeToggle.tsx +151 -0
- package/src/components/forms/index.ts +19 -0
- package/src/components/layout/Accordion.test.tsx +66 -0
- package/src/components/layout/Accordion.tsx +64 -0
- package/src/components/layout/AspectRatio.tsx +7 -0
- package/src/components/layout/Carousel.tsx +277 -0
- package/src/components/layout/Collapsible.test.tsx +40 -0
- package/src/components/layout/Collapsible.tsx +31 -0
- package/src/components/layout/Container.test.tsx +45 -0
- package/src/components/layout/Container.tsx +99 -0
- package/src/components/layout/CustomizerPanel.tsx +400 -0
- package/src/components/layout/DatePicker.tsx +57 -0
- package/src/components/layout/Footer/Footer.tsx +175 -0
- package/src/components/layout/Footer/index.ts +2 -0
- package/src/components/layout/GlassSurface.tsx +82 -0
- package/src/components/layout/Grid.test.tsx +31 -0
- package/src/components/layout/Grid.tsx +130 -0
- package/src/components/layout/Header/Header.tsx +450 -0
- package/src/components/layout/Header/index.ts +2 -0
- package/src/components/layout/PageLayout.tsx +180 -0
- package/src/components/layout/PageTemplate.tsx +158 -0
- package/src/components/layout/Resizable.tsx +48 -0
- package/src/components/layout/ScrollArea.tsx +53 -0
- package/src/components/layout/Separator.test.tsx +28 -0
- package/src/components/layout/Separator.tsx +29 -0
- package/src/components/layout/Sidebar.tsx +171 -0
- package/src/components/layout/Stack.test.tsx +41 -0
- package/src/components/layout/Stack.tsx +89 -0
- package/src/components/layout/glass-surface.css +60 -0
- package/src/components/layout/index.ts +18 -0
- package/src/components/motion/AnimatedBeam.tsx +159 -0
- package/src/components/navigation/Breadcrumb.test.tsx +57 -0
- package/src/components/navigation/Breadcrumb.tsx +119 -0
- package/src/components/navigation/Breadcrumbs.tsx +221 -0
- package/src/components/navigation/Command.tsx +159 -0
- package/src/components/navigation/Menubar.tsx +115 -0
- package/src/components/navigation/NavLink.tsx +55 -0
- package/src/components/navigation/NavigationMenu.tsx +125 -0
- package/src/components/navigation/Pagination.tsx +121 -0
- package/src/components/navigation/SecondaryNav.tsx +100 -0
- package/src/components/navigation/Tabs.test.tsx +47 -0
- package/src/components/navigation/Tabs.tsx +60 -0
- package/src/components/navigation/TertiaryNav.tsx +90 -0
- package/src/components/navigation/index.ts +10 -0
- package/src/components/overlays/AlertDialog.test.tsx +69 -0
- package/src/components/overlays/AlertDialog.tsx +166 -0
- package/src/components/overlays/ContextMenu.tsx +243 -0
- package/src/components/overlays/Dialog.test.tsx +79 -0
- package/src/components/overlays/Dialog.tsx +158 -0
- package/src/components/overlays/Drawer.tsx +128 -0
- package/src/components/overlays/Dropdown.tsx +253 -0
- package/src/components/overlays/DropdownMenu.tsx +242 -0
- package/src/components/overlays/HoverCard.tsx +32 -0
- package/src/components/overlays/Modal.tsx +250 -0
- package/src/components/overlays/NotificationCenter.tsx +364 -0
- package/src/components/overlays/Popover.test.tsx +40 -0
- package/src/components/overlays/Popover.tsx +46 -0
- package/src/components/overlays/Sheet.tsx +163 -0
- package/src/components/overlays/Tooltip.test.tsx +33 -0
- package/src/components/overlays/Tooltip.tsx +32 -0
- package/src/components/overlays/index.ts +12 -0
- package/src/dates.ts +2 -0
- package/src/dnd.ts +1 -0
- package/src/forms.ts +1 -0
- package/src/globals.css +187 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useForm.ts +247 -0
- package/src/hooks/useMotionPreference.test.ts +102 -0
- package/src/hooks/useMotionPreference.ts +78 -0
- package/src/hooks/useTheme.ts +58 -0
- package/src/hooks.ts +9 -0
- package/src/index.ts +168 -0
- package/src/lib/animations.ts +356 -0
- package/src/lib/breadcrumbs.ts +94 -0
- package/src/lib/colors.ts +493 -0
- package/src/lib/store/customizer.ts +482 -0
- package/src/lib/store/index.ts +3 -0
- package/src/lib/store/theme.ts +55 -0
- package/src/lib/syntax-parser/index.ts +50 -0
- package/src/lib/syntax-parser/patterns.ts +64 -0
- package/src/lib/syntax-parser/tokenizer.ts +117 -0
- package/src/lib/syntax-parser/types.ts +27 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.ts +204 -0
- package/src/lib/webgl/Color.ts +11 -0
- package/src/lib/webgl/Mesh.ts +41 -0
- package/src/lib/webgl/Program.ts +118 -0
- package/src/lib/webgl/Renderer.ts +51 -0
- package/src/lib/webgl/Triangle.ts +27 -0
- package/src/lib/webgl/Vec3.ts +18 -0
- package/src/lib/webgl/index.ts +13 -0
- package/src/nativewind-env.d.ts +1 -0
- package/src/providers/ThemeProvider.tsx +461 -0
- package/src/providers/index.ts +1 -0
- package/src/providers.ts +7 -0
- package/src/tables.ts +1 -0
- package/src/test/setup.ts +39 -0
- package/src/theme.css +158 -0
- package/src/tokens.ts +7 -0
- package/src/utils.ts +12 -0
- 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,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
|
+
};
|