@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,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
+ }