@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,158 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Header, type HeaderNavLink } from './Header/Header';
|
|
5
|
+
import { Breadcrumbs, type BreadcrumbItemLegacy } from '../navigation/Breadcrumbs';
|
|
6
|
+
import { SecondaryNav, type SecondaryNavItem } from '../navigation/SecondaryNav';
|
|
7
|
+
import { PageLayout } from './PageLayout';
|
|
8
|
+
import { CustomizerPanel } from './CustomizerPanel';
|
|
9
|
+
import { Heading } from '../data-display/Heading';
|
|
10
|
+
import { Text } from '../data-display/Text';
|
|
11
|
+
|
|
12
|
+
export interface PageTemplateHeaderConfig {
|
|
13
|
+
/** Logo or brand element */
|
|
14
|
+
logo: React.ReactNode;
|
|
15
|
+
/** Navigation links with optional dropdowns */
|
|
16
|
+
navLinks?: HeaderNavLink[];
|
|
17
|
+
/** Actions (e.g., Sign In button, CTA) */
|
|
18
|
+
actions?: React.ReactNode;
|
|
19
|
+
/** Whether header should be sticky (default: true) */
|
|
20
|
+
sticky?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PageTemplateSecondaryNavConfig {
|
|
24
|
+
/** Secondary navigation items */
|
|
25
|
+
items: SecondaryNavItem[];
|
|
26
|
+
/** Currently active item ID */
|
|
27
|
+
activeId: string;
|
|
28
|
+
/** Callback when item changes */
|
|
29
|
+
onItemChange: (id: string) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PageTemplateProps {
|
|
33
|
+
/** Header configuration */
|
|
34
|
+
header: PageTemplateHeaderConfig;
|
|
35
|
+
|
|
36
|
+
/** Page title (required) */
|
|
37
|
+
title: string;
|
|
38
|
+
|
|
39
|
+
/** Optional page subtitle */
|
|
40
|
+
subtitle?: string;
|
|
41
|
+
|
|
42
|
+
/** Breadcrumb navigation items */
|
|
43
|
+
breadcrumbs: BreadcrumbItemLegacy[];
|
|
44
|
+
|
|
45
|
+
/** Optional secondary navigation */
|
|
46
|
+
secondaryNav?: PageTemplateSecondaryNavConfig;
|
|
47
|
+
|
|
48
|
+
/** Main page content */
|
|
49
|
+
children: React.ReactNode;
|
|
50
|
+
|
|
51
|
+
/** Optional footer */
|
|
52
|
+
footer?: React.ReactNode;
|
|
53
|
+
|
|
54
|
+
/** Show customizer panel (default: true) */
|
|
55
|
+
showCustomizer?: boolean;
|
|
56
|
+
|
|
57
|
+
/** Content width variant */
|
|
58
|
+
variant?: 'standard' | 'wide' | 'narrow';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* PageTemplate Component
|
|
63
|
+
*
|
|
64
|
+
* An opinionated page layout template based on Swiss Grid Design principles.
|
|
65
|
+
* This template provides a structured, clean layout with sensible defaults for
|
|
66
|
+
* standard pages (blogs, docs, app pages).
|
|
67
|
+
*
|
|
68
|
+
* Swiss Grid Design Principles:
|
|
69
|
+
* - Structured spacing: 48-96px between major sections
|
|
70
|
+
* - Clear typography hierarchy: 36-48px title, 18px subtitle
|
|
71
|
+
* - Grid-based alignment with consistent content widths
|
|
72
|
+
* - Generous whitespace for breathing room
|
|
73
|
+
* - Minimal, functional aesthetic
|
|
74
|
+
*
|
|
75
|
+
* Features:
|
|
76
|
+
* - Sticky header with glass morphism effect
|
|
77
|
+
* - Breadcrumbs positioned below page title (static)
|
|
78
|
+
* - Always-sticky secondary navigation
|
|
79
|
+
* - Optional customizer panel
|
|
80
|
+
* - Three width variants: standard (1280px), wide (1440px), narrow (896px)
|
|
81
|
+
* - Automatic Swiss Grid spacing
|
|
82
|
+
*
|
|
83
|
+
* Example:
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <PageTemplate
|
|
86
|
+
* header={{
|
|
87
|
+
* logo: <Link href="/">Brand</Link>,
|
|
88
|
+
* navLinks: navigationItems,
|
|
89
|
+
* sticky: true
|
|
90
|
+
* }}
|
|
91
|
+
* title="Welcome to Our Platform"
|
|
92
|
+
* subtitle="Build amazing experiences with our tools"
|
|
93
|
+
* breadcrumbs={[
|
|
94
|
+
* { label: 'Home', href: '/' },
|
|
95
|
+
* { label: 'Platform' }
|
|
96
|
+
* ]}
|
|
97
|
+
* >
|
|
98
|
+
* <article>Your content here</article>
|
|
99
|
+
* </PageTemplate>
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export function PageTemplate({
|
|
103
|
+
header,
|
|
104
|
+
title,
|
|
105
|
+
subtitle,
|
|
106
|
+
breadcrumbs,
|
|
107
|
+
secondaryNav,
|
|
108
|
+
children,
|
|
109
|
+
footer,
|
|
110
|
+
showCustomizer = true,
|
|
111
|
+
variant = 'standard',
|
|
112
|
+
}: PageTemplateProps) {
|
|
113
|
+
// Determine content width based on variant
|
|
114
|
+
const maxWidthClass = {
|
|
115
|
+
standard: 'max-w-7xl', // 1280px - default, matches most pages
|
|
116
|
+
wide: 'max-w-[1440px]', // 1440px - for dashboard-like pages
|
|
117
|
+
narrow: 'max-w-4xl', // 896px - for reading-focused pages
|
|
118
|
+
}[variant] as 'max-w-7xl' | 'max-w-[1440px]' | 'max-w-4xl';
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
<PageLayout
|
|
123
|
+
header={
|
|
124
|
+
<Header
|
|
125
|
+
logo={header.logo}
|
|
126
|
+
navLinks={header.navLinks}
|
|
127
|
+
actions={header.actions}
|
|
128
|
+
sticky={header.sticky ?? true}
|
|
129
|
+
maxWidth={maxWidthClass}
|
|
130
|
+
/>
|
|
131
|
+
}
|
|
132
|
+
stickyHeader={header.sticky ?? true}
|
|
133
|
+
breadcrumbsPosition="below-title"
|
|
134
|
+
breadcrumbs={<Breadcrumbs items={breadcrumbs} variant="subtle" />}
|
|
135
|
+
title={<Heading level={1}>{title}</Heading>}
|
|
136
|
+
subtitle={subtitle ? <Text size="lg" variant="secondary">{subtitle}</Text> : undefined}
|
|
137
|
+
secondaryNav={
|
|
138
|
+
secondaryNav ? (
|
|
139
|
+
<SecondaryNav
|
|
140
|
+
items={secondaryNav.items}
|
|
141
|
+
activeId={secondaryNav.activeId}
|
|
142
|
+
onItemChange={secondaryNav.onItemChange}
|
|
143
|
+
maxWidth={maxWidthClass}
|
|
144
|
+
/>
|
|
145
|
+
) : undefined
|
|
146
|
+
}
|
|
147
|
+
footer={footer}
|
|
148
|
+
swissGridSpacing
|
|
149
|
+
contentMaxWidth={maxWidthClass}
|
|
150
|
+
>
|
|
151
|
+
{children}
|
|
152
|
+
</PageLayout>
|
|
153
|
+
|
|
154
|
+
{/* Customizer - Swiss Grid: sticky overlay, bottom right */}
|
|
155
|
+
{showCustomizer && <CustomizerPanel />}
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { GripVertical } from "lucide-react"
|
|
5
|
+
import { Group as PanelGroup, Panel, Separator as PanelResizeHandle } from "react-resizable-panels"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
const ResizablePanelGroup = ({
|
|
10
|
+
className,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof PanelGroup>) => {
|
|
13
|
+
return (
|
|
14
|
+
<PanelGroup
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ResizablePanel = Panel
|
|
25
|
+
|
|
26
|
+
const ResizableHandle = ({
|
|
27
|
+
withHandle,
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof PanelResizeHandle> & { withHandle?: boolean }) => {
|
|
31
|
+
return (
|
|
32
|
+
<PanelResizeHandle
|
|
33
|
+
className={cn(
|
|
34
|
+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{withHandle && (
|
|
40
|
+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-xs border bg-border">
|
|
41
|
+
<GripVertical className="h-2.5 w-2.5" />
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</PanelResizeHandle>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const ScrollArea = (
|
|
8
|
+
{
|
|
9
|
+
ref,
|
|
10
|
+
className,
|
|
11
|
+
children,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & {
|
|
14
|
+
ref?: React.Ref<React.ElementRef<typeof ScrollAreaPrimitive.Root>>;
|
|
15
|
+
}
|
|
16
|
+
) => (<ScrollAreaPrimitive.Root
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn("relative overflow-hidden", className)}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
|
22
|
+
{children}
|
|
23
|
+
</ScrollAreaPrimitive.Viewport>
|
|
24
|
+
<ScrollBar />
|
|
25
|
+
<ScrollAreaPrimitive.Corner />
|
|
26
|
+
</ScrollAreaPrimitive.Root>)
|
|
27
|
+
|
|
28
|
+
const ScrollBar = (
|
|
29
|
+
{
|
|
30
|
+
ref,
|
|
31
|
+
className,
|
|
32
|
+
orientation = "vertical",
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Scrollbar> & {
|
|
35
|
+
ref?: React.Ref<React.ElementRef<typeof ScrollAreaPrimitive.Scrollbar>>;
|
|
36
|
+
}
|
|
37
|
+
) => (<ScrollAreaPrimitive.Scrollbar
|
|
38
|
+
ref={ref}
|
|
39
|
+
orientation={orientation}
|
|
40
|
+
className={cn(
|
|
41
|
+
"flex touch-none select-none transition-colors",
|
|
42
|
+
orientation === "vertical" &&
|
|
43
|
+
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
|
44
|
+
orientation === "horizontal" &&
|
|
45
|
+
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
<ScrollAreaPrimitive.Thumb className="relative flex-1 rounded-full bg-border" />
|
|
51
|
+
</ScrollAreaPrimitive.Scrollbar>)
|
|
52
|
+
|
|
53
|
+
export { ScrollArea, ScrollBar }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { render } from '@testing-library/react'
|
|
2
|
+
import { describe, it, expect } from 'vitest'
|
|
3
|
+
import { Separator } from './Separator'
|
|
4
|
+
|
|
5
|
+
describe('Separator', () => {
|
|
6
|
+
it('renders with horizontal orientation by default', () => {
|
|
7
|
+
const { container } = render(<Separator />)
|
|
8
|
+
const separator = container.firstChild as HTMLElement
|
|
9
|
+
expect(separator).toBeInTheDocument()
|
|
10
|
+
expect(separator).toHaveAttribute('data-orientation', 'horizontal')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('renders with vertical orientation', () => {
|
|
14
|
+
const { container } = render(<Separator orientation="vertical" />)
|
|
15
|
+
const separator = container.firstChild as HTMLElement
|
|
16
|
+
expect(separator).toHaveAttribute('data-orientation', 'vertical')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('applies custom className', () => {
|
|
20
|
+
const { container } = render(<Separator className="custom-sep" />)
|
|
21
|
+
expect(container.firstChild).toHaveClass('custom-sep')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('has separator role when not decorative', () => {
|
|
25
|
+
const { container } = render(<Separator decorative={false} />)
|
|
26
|
+
expect(container.querySelector('[role="separator"]')).toBeInTheDocument()
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const Separator = (
|
|
8
|
+
{
|
|
9
|
+
ref,
|
|
10
|
+
className,
|
|
11
|
+
orientation = "horizontal",
|
|
12
|
+
decorative = true,
|
|
13
|
+
...props
|
|
14
|
+
}: React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> & {
|
|
15
|
+
ref?: React.Ref<React.ElementRef<typeof SeparatorPrimitive.Root>>;
|
|
16
|
+
}
|
|
17
|
+
) => (<SeparatorPrimitive.Root
|
|
18
|
+
ref={ref}
|
|
19
|
+
decorative={decorative}
|
|
20
|
+
orientation={orientation}
|
|
21
|
+
className={cn(
|
|
22
|
+
"shrink-0 bg-border",
|
|
23
|
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>)
|
|
28
|
+
|
|
29
|
+
export { Separator }
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
5
|
+
|
|
6
|
+
const Sidebar = (
|
|
7
|
+
{
|
|
8
|
+
ref,
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
isOpen = true,
|
|
12
|
+
...props
|
|
13
|
+
}: React.HTMLAttributes<HTMLDivElement> & { isOpen?: boolean } & {
|
|
14
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
15
|
+
}
|
|
16
|
+
) => (<aside
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn(
|
|
19
|
+
"fixed top-0 left-0 h-full w-[280px] bg-background border-r border-border z-40 transition-transform duration-300 transform",
|
|
20
|
+
isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
<div className="flex flex-col h-full">{children}</div>
|
|
26
|
+
</aside>)
|
|
27
|
+
|
|
28
|
+
const SidebarOverlay = (
|
|
29
|
+
{
|
|
30
|
+
ref,
|
|
31
|
+
className,
|
|
32
|
+
isOpen,
|
|
33
|
+
onDismiss,
|
|
34
|
+
...props
|
|
35
|
+
}: React.HTMLAttributes<HTMLDivElement> & { isOpen?: boolean; onDismiss?: () => void } & {
|
|
36
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
37
|
+
}
|
|
38
|
+
) => {
|
|
39
|
+
if (!isOpen) return null
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn(
|
|
45
|
+
"fixed inset-0 bg-black/50 z-40 lg:hidden",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
onClick={onDismiss}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const SidebarHeader = (
|
|
55
|
+
{
|
|
56
|
+
ref,
|
|
57
|
+
className,
|
|
58
|
+
...props
|
|
59
|
+
}: React.HTMLAttributes<HTMLDivElement> & {
|
|
60
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
61
|
+
}
|
|
62
|
+
) => (<div
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn("flex items-center justify-between px-4 py-4 border-b border-border", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>)
|
|
67
|
+
|
|
68
|
+
const SidebarContent = (
|
|
69
|
+
{
|
|
70
|
+
ref,
|
|
71
|
+
className,
|
|
72
|
+
...props
|
|
73
|
+
}: React.HTMLAttributes<HTMLDivElement> & {
|
|
74
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
75
|
+
}
|
|
76
|
+
) => (<div
|
|
77
|
+
ref={ref}
|
|
78
|
+
className={cn("flex-1 min-h-0 overflow-y-auto py-4 px-3", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>)
|
|
81
|
+
|
|
82
|
+
const SidebarFooter = (
|
|
83
|
+
{
|
|
84
|
+
ref,
|
|
85
|
+
className,
|
|
86
|
+
...props
|
|
87
|
+
}: React.HTMLAttributes<HTMLDivElement> & {
|
|
88
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
89
|
+
}
|
|
90
|
+
) => (<div
|
|
91
|
+
ref={ref}
|
|
92
|
+
className={cn("px-4 py-4 space-y-3", className)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>)
|
|
95
|
+
|
|
96
|
+
interface SidebarItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
97
|
+
isActive?: boolean
|
|
98
|
+
icon?: React.ReactNode
|
|
99
|
+
showIcon?: boolean
|
|
100
|
+
depth?: number
|
|
101
|
+
hasChildren?: boolean
|
|
102
|
+
isExpanded?: boolean
|
|
103
|
+
asChild?: boolean
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const SidebarItem = (
|
|
107
|
+
{
|
|
108
|
+
ref,
|
|
109
|
+
className,
|
|
110
|
+
isActive,
|
|
111
|
+
icon,
|
|
112
|
+
showIcon = true,
|
|
113
|
+
depth = 0,
|
|
114
|
+
hasChildren,
|
|
115
|
+
isExpanded,
|
|
116
|
+
children,
|
|
117
|
+
asChild = false,
|
|
118
|
+
...props
|
|
119
|
+
}: SidebarItemProps & {
|
|
120
|
+
ref?: React.Ref<HTMLButtonElement>;
|
|
121
|
+
}
|
|
122
|
+
) => {
|
|
123
|
+
const Comp = asChild ? Slot : "button"
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Comp
|
|
127
|
+
ref={ref}
|
|
128
|
+
className={cn(
|
|
129
|
+
"w-full flex items-center gap-2 px-3 py-2 text-sm transition-colors rounded-md sage-interactive",
|
|
130
|
+
isActive
|
|
131
|
+
? "bg-primary text-primary-foreground font-medium"
|
|
132
|
+
: "text-muted-foreground hover:text-foreground",
|
|
133
|
+
depth === 0 && !isActive ? "font-medium text-foreground" : "",
|
|
134
|
+
className
|
|
135
|
+
)}
|
|
136
|
+
style={{ paddingLeft: `${12 + depth * 16}px` }}
|
|
137
|
+
{...props}
|
|
138
|
+
>
|
|
139
|
+
{hasChildren && (
|
|
140
|
+
<svg
|
|
141
|
+
className={cn(
|
|
142
|
+
"w-4 h-4 flex-shrink-0 transition-transform",
|
|
143
|
+
isExpanded ? "rotate-90" : ""
|
|
144
|
+
)}
|
|
145
|
+
fill="none"
|
|
146
|
+
stroke="currentColor"
|
|
147
|
+
viewBox="0 0 24 24"
|
|
148
|
+
>
|
|
149
|
+
<path
|
|
150
|
+
strokeLinecap="round"
|
|
151
|
+
strokeLinejoin="round"
|
|
152
|
+
strokeWidth={2}
|
|
153
|
+
d="M9 5l7 7-7 7"
|
|
154
|
+
/>
|
|
155
|
+
</svg>
|
|
156
|
+
)}
|
|
157
|
+
{!hasChildren && depth > 0 && <span className="w-4 flex-shrink-0" />}
|
|
158
|
+
{showIcon && icon && <span className="flex-shrink-0">{icon}</span>}
|
|
159
|
+
<span className="flex-1 text-left truncate">{children}</span>
|
|
160
|
+
</Comp>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export {
|
|
165
|
+
Sidebar,
|
|
166
|
+
SidebarOverlay,
|
|
167
|
+
SidebarHeader,
|
|
168
|
+
SidebarContent,
|
|
169
|
+
SidebarFooter,
|
|
170
|
+
SidebarItem,
|
|
171
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react'
|
|
2
|
+
import { describe, it, expect } from 'vitest'
|
|
3
|
+
import { Stack } from './Stack'
|
|
4
|
+
|
|
5
|
+
describe('Stack', () => {
|
|
6
|
+
it('renders children in a flex container', () => {
|
|
7
|
+
render(
|
|
8
|
+
<Stack>
|
|
9
|
+
<div>Item 1</div>
|
|
10
|
+
<div>Item 2</div>
|
|
11
|
+
</Stack>
|
|
12
|
+
)
|
|
13
|
+
expect(screen.getByText('Item 1')).toBeInTheDocument()
|
|
14
|
+
expect(screen.getByText('Item 2')).toBeInTheDocument()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('defaults to column direction', () => {
|
|
18
|
+
const { container } = render(<Stack><div>Child</div></Stack>)
|
|
19
|
+
expect(container.firstChild).toHaveClass('flex-col')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('renders row direction', () => {
|
|
23
|
+
const { container } = render(<Stack direction="row"><div>Child</div></Stack>)
|
|
24
|
+
expect(container.firstChild).toHaveClass('flex-row')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('applies alignment', () => {
|
|
28
|
+
const { container } = render(<Stack align="center"><div>Child</div></Stack>)
|
|
29
|
+
expect(container.firstChild).toHaveClass('items-center')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('applies justify', () => {
|
|
33
|
+
const { container } = render(<Stack justify="between"><div>Child</div></Stack>)
|
|
34
|
+
expect(container.firstChild).toHaveClass('justify-between')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('applies custom className', () => {
|
|
38
|
+
const { container } = render(<Stack className="custom-stack"><div>Child</div></Stack>)
|
|
39
|
+
expect(container.firstChild).toHaveClass('custom-stack')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface StackProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
/**
|
|
5
|
+
* Content to arrange
|
|
6
|
+
*/
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Direction of flow
|
|
10
|
+
* @default 'column'
|
|
11
|
+
*/
|
|
12
|
+
direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
13
|
+
/**
|
|
14
|
+
* Spacing between items (scale: 0-12, or px values)
|
|
15
|
+
* Maps to Tailwind gap utility (e.g. 4 -> gap-4)
|
|
16
|
+
* @default 4
|
|
17
|
+
*/
|
|
18
|
+
gap?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Alignment on the cross axis (align-items)
|
|
21
|
+
*/
|
|
22
|
+
align?: 'start' | 'end' | 'center' | 'baseline' | 'stretch';
|
|
23
|
+
/**
|
|
24
|
+
* Distribution on the main axis (justify-content)
|
|
25
|
+
*/
|
|
26
|
+
justify?: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly';
|
|
27
|
+
/**
|
|
28
|
+
* Whether to allow wrapping (for row layouts)
|
|
29
|
+
*/
|
|
30
|
+
wrap?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* HTML element to render as
|
|
33
|
+
* @default 'div'
|
|
34
|
+
*/
|
|
35
|
+
as?: any;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const Stack = (
|
|
39
|
+
{
|
|
40
|
+
ref,
|
|
41
|
+
children,
|
|
42
|
+
direction = 'column',
|
|
43
|
+
gap = 4,
|
|
44
|
+
align = 'stretch',
|
|
45
|
+
justify = 'start',
|
|
46
|
+
wrap = false,
|
|
47
|
+
as: Component = 'div',
|
|
48
|
+
className = '',
|
|
49
|
+
...props
|
|
50
|
+
}: StackProps & {
|
|
51
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
52
|
+
}
|
|
53
|
+
) => {
|
|
54
|
+
|
|
55
|
+
const styles = {
|
|
56
|
+
direction: {
|
|
57
|
+
row: 'flex-row',
|
|
58
|
+
column: 'flex-col',
|
|
59
|
+
'row-reverse': 'flex-row-reverse',
|
|
60
|
+
'column-reverse': 'flex-col-reverse',
|
|
61
|
+
},
|
|
62
|
+
align: {
|
|
63
|
+
start: 'items-start',
|
|
64
|
+
end: 'items-end',
|
|
65
|
+
center: 'items-center',
|
|
66
|
+
baseline: 'items-baseline',
|
|
67
|
+
stretch: 'items-stretch',
|
|
68
|
+
},
|
|
69
|
+
justify: {
|
|
70
|
+
start: 'justify-start',
|
|
71
|
+
end: 'justify-end',
|
|
72
|
+
center: 'justify-center',
|
|
73
|
+
between: 'justify-between',
|
|
74
|
+
around: 'justify-around',
|
|
75
|
+
evenly: 'justify-evenly',
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Component
|
|
81
|
+
ref={ref}
|
|
82
|
+
className={`flex ${styles.direction[direction]} ${styles.align[align]} ${styles.justify[justify]} ${wrap ? 'flex-wrap' : 'flex-nowrap'
|
|
83
|
+
} gap-${gap} ${className}`}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
{children}
|
|
87
|
+
</Component>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* ── GlassSurface — Liquid Glass ──
|
|
2
|
+
Apple macOS 26 glass materials: same blur, same base color,
|
|
3
|
+
five thickness tiers that differ only in background opacity.
|
|
4
|
+
12 percentage-point steps: 28 → 38 → 48 → 58 → 68.
|
|
5
|
+
|
|
6
|
+
`backface-visibility: hidden` forces a GPU compositing layer so
|
|
7
|
+
the effect renders identically across environments.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* ── tokens ── */
|
|
11
|
+
:root {
|
|
12
|
+
--glass-border: rgba(255, 255, 255, 0.35);
|
|
13
|
+
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.10);
|
|
14
|
+
}
|
|
15
|
+
.dark {
|
|
16
|
+
--glass-border: rgba(255, 255, 255, 0.10);
|
|
17
|
+
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.30);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Resolve light/dark tint — component sets both inline, CSS picks the right one */
|
|
21
|
+
.glass-surface { --glass-tint-rgb: var(--glass-tint-light, 200, 200, 200); }
|
|
22
|
+
.dark .glass-surface { --glass-tint-rgb: var(--glass-tint-dark, 57, 57, 57); }
|
|
23
|
+
|
|
24
|
+
/* ── base ── */
|
|
25
|
+
/* backdrop-filter is applied inline in GlassSurface.tsx to prevent
|
|
26
|
+
Lightning CSS from stripping the standard property and mangling
|
|
27
|
+
the -webkit- prefix value in production builds. */
|
|
28
|
+
.glass-surface {
|
|
29
|
+
-webkit-backface-visibility: hidden;
|
|
30
|
+
backface-visibility: hidden;
|
|
31
|
+
background: transparent;
|
|
32
|
+
transition:
|
|
33
|
+
background 0.3s ease,
|
|
34
|
+
box-shadow 0.3s ease,
|
|
35
|
+
border-color 0.3s ease;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── thickness classes — opacity driven by tint color from component ── */
|
|
39
|
+
.glass-surface--active { background: rgba(var(--glass-tint-rgb), 0.38); }
|
|
40
|
+
.glass-surface--active.glass-surface--ultrathin { background: rgba(var(--glass-tint-rgb), 0.28); }
|
|
41
|
+
.glass-surface--active.glass-surface--thin { background: rgba(var(--glass-tint-rgb), 0.38); }
|
|
42
|
+
.glass-surface--active.glass-surface--medium { background: rgba(var(--glass-tint-rgb), 0.48); }
|
|
43
|
+
.glass-surface--active.glass-surface--thick { background: rgba(var(--glass-tint-rgb), 0.58); }
|
|
44
|
+
.glass-surface--active.glass-surface--ultrathick { background: rgba(var(--glass-tint-rgb), 0.68); }
|
|
45
|
+
|
|
46
|
+
/* ── shadow + inset highlight ── */
|
|
47
|
+
.glass-surface--active {
|
|
48
|
+
box-shadow: var(--glass-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.40);
|
|
49
|
+
}
|
|
50
|
+
.dark .glass-surface--active {
|
|
51
|
+
box-shadow: var(--glass-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── position borders ── */
|
|
55
|
+
.glass-surface--top.glass-surface--active {
|
|
56
|
+
border-bottom: 1px solid var(--glass-border);
|
|
57
|
+
}
|
|
58
|
+
.glass-surface--bottom.glass-surface--active {
|
|
59
|
+
border-top: 1px solid var(--glass-border);
|
|
60
|
+
}
|