@opencosmos/ui 1.3.1 → 1.3.2

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 (46) hide show
  1. package/.claude/CLAUDE.md +47 -47
  2. package/LICENSE +21 -0
  3. package/README.md +21 -21
  4. package/dist/{hooks-CKW8vE9H.d.ts → hooks-CFPKFXhH.d.ts} +1 -1
  5. package/dist/{hooks-1b8WaQf1.d.mts → hooks-CeAuZ0i5.d.mts} +1 -1
  6. package/dist/hooks.d.mts +1 -1
  7. package/dist/hooks.d.ts +1 -1
  8. package/dist/hooks.js.map +1 -1
  9. package/dist/hooks.mjs.map +1 -1
  10. package/dist/index.d.mts +78 -11
  11. package/dist/index.d.ts +78 -11
  12. package/dist/index.js +585 -342
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.mjs +548 -312
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/{providers-CXPDMsl7.d.mts → providers-CzKisd2T.d.mts} +1 -1
  17. package/dist/{providers-Dn_Msjvz.d.ts → providers-D39-kwai.d.ts} +1 -1
  18. package/dist/providers.d.mts +1 -1
  19. package/dist/providers.d.ts +1 -1
  20. package/dist/providers.js.map +1 -1
  21. package/dist/providers.mjs.map +1 -1
  22. package/dist/tokens.js.map +1 -1
  23. package/dist/{utils-Cs04sxth.d.mts → utils-CkatYLG4.d.mts} +1 -1
  24. package/dist/{utils-CIIM7dAC.d.ts → utils-Y1Zi7biA.d.ts} +1 -1
  25. package/dist/utils.d.mts +1 -1
  26. package/dist/utils.d.ts +1 -1
  27. package/dist/utils.js.map +1 -1
  28. package/dist/utils.mjs.map +1 -1
  29. package/package.json +256 -256
  30. package/src/component-registry.ts +4 -4
  31. package/src/components/data-display/CollapsibleCodeBlock.tsx +1 -1
  32. package/src/components/data-display/OpenCosmosIcon.tsx +39 -0
  33. package/src/components/data-display/index.ts +1 -0
  34. package/src/components/layout/AppSidebar.tsx +277 -0
  35. package/src/components/layout/CustomizerPanel.tsx +2 -2
  36. package/src/components/layout/index.ts +1 -0
  37. package/src/hooks/useTheme.ts +1 -1
  38. package/src/hooks.ts +1 -1
  39. package/src/index.ts +6 -4
  40. package/src/lib/store/customizer.ts +1 -1
  41. package/src/lib/store/theme.ts +1 -1
  42. package/src/lib/syntax-parser/index.ts +1 -1
  43. package/src/providers/ThemeProvider.tsx +2 -2
  44. package/src/providers.ts +1 -1
  45. package/src/tokens.ts +3 -3
  46. package/src/utils.ts +1 -1
@@ -0,0 +1,277 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useState, useEffect } from 'react';
4
+ import { cn } from '../../lib/utils';
5
+ import { useMotionPreference } from '../../hooks/useMotionPreference';
6
+ import { PanelLeftClose } from 'lucide-react';
7
+
8
+ // ── Constants ─────────────────────────────────────────────────────────────────
9
+
10
+ const STORAGE_KEY = 'appsidebar:state';
11
+ export const APP_SIDEBAR_WIDTH = 280;
12
+ export const APP_SIDEBAR_WIDTH_COLLAPSED = 60;
13
+
14
+ // ── Context ───────────────────────────────────────────────────────────────────
15
+
16
+ interface AppSidebarContextValue {
17
+ isOpen: boolean;
18
+ toggle: () => void;
19
+ open: () => void;
20
+ close: () => void;
21
+ }
22
+
23
+ const AppSidebarContext = createContext<AppSidebarContextValue | null>(null);
24
+
25
+ // Safe default used when no provider is in scope.
26
+ //
27
+ // WHY this exists — do not change back to a throw:
28
+ //
29
+ // `next.config.mjs` sets `transpilePackages: ['@opencosmos/ui']`, which tells
30
+ // Next.js/webpack to bundle this package from SOURCE instead of dist. Webpack can
31
+ // then split this file and its consumer (e.g. the docs playground) into separate
32
+ // chunks. Each chunk gets its own module execution scope, so `createContext()` can
33
+ // run twice — producing two distinct AppSidebarContext objects. The Provider writes
34
+ // to instance A; AppSidebar reads from instance B → useContext returns null even
35
+ // though a Provider is present in the React tree.
36
+ //
37
+ // Throwing on null ctx (the common pattern) causes the entire page to 500.
38
+ // Returning a safe default lets the component render correctly in isolation.
39
+ // Consuming apps should still wrap with AppSidebarProvider for state persistence.
40
+ const DEFAULT_CONTEXT: AppSidebarContextValue = {
41
+ isOpen: true,
42
+ toggle: () => {},
43
+ open: () => {},
44
+ close: () => {},
45
+ };
46
+
47
+ export function useAppSidebar(): AppSidebarContextValue {
48
+ return useContext(AppSidebarContext) ?? DEFAULT_CONTEXT;
49
+ }
50
+
51
+ // ── AppSidebarProvider ────────────────────────────────────────────────────────
52
+
53
+ export interface AppSidebarProviderProps {
54
+ children: React.ReactNode;
55
+ /** Initial open state used on server and first render @default true */
56
+ defaultOpen?: boolean;
57
+ }
58
+
59
+ export function AppSidebarProvider({ children, defaultOpen = true }: AppSidebarProviderProps) {
60
+ const [isOpen, setIsOpen] = useState(defaultOpen);
61
+
62
+ useEffect(() => {
63
+ const stored = localStorage.getItem(STORAGE_KEY);
64
+ if (stored !== null) setIsOpen(stored === 'true');
65
+ }, []);
66
+
67
+ const persist = (value: boolean) => localStorage.setItem(STORAGE_KEY, String(value));
68
+
69
+ const toggle = () => setIsOpen(prev => { const next = !prev; persist(next); return next; });
70
+ const open = () => { setIsOpen(true); persist(true); };
71
+ const close = () => { setIsOpen(false); persist(false); };
72
+
73
+ return (
74
+ <AppSidebarContext.Provider value={{ isOpen, toggle, open, close }}>
75
+ {children}
76
+ </AppSidebarContext.Provider>
77
+ );
78
+ }
79
+
80
+ // ── AppSidebarInset ───────────────────────────────────────────────────────────
81
+
82
+ export function AppSidebarInset({
83
+ children,
84
+ className,
85
+ }: {
86
+ children: React.ReactNode;
87
+ className?: string;
88
+ }) {
89
+ const { isOpen } = useAppSidebar();
90
+ const { shouldAnimate, scale } = useMotionPreference();
91
+ const duration = shouldAnimate ? Math.round(300 * (5 / Math.max(scale, 0.1))) : 0;
92
+
93
+ return (
94
+ <div
95
+ className={cn('min-h-screen', className)}
96
+ style={{
97
+ marginLeft: isOpen ? APP_SIDEBAR_WIDTH : APP_SIDEBAR_WIDTH_COLLAPSED,
98
+ transition: shouldAnimate ? `margin-left ${duration}ms ease-out` : 'none',
99
+ }}
100
+ >
101
+ {children}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ // ── Nav item type ─────────────────────────────────────────────────────────────
107
+
108
+ export interface AppSidebarNavItem {
109
+ icon: React.ReactNode;
110
+ label: string;
111
+ href: string;
112
+ active?: boolean;
113
+ external?: boolean;
114
+ }
115
+
116
+ // ── AppSidebar ────────────────────────────────────────────────────────────────
117
+
118
+ export interface AppSidebarProps {
119
+ /** Icon element always visible (32×32). Clicking it toggles open/closed. */
120
+ logo?: React.ReactNode;
121
+ /** Wordmark shown next to the logo when expanded */
122
+ title?: string;
123
+ /** Navigation items */
124
+ items?: AppSidebarNavItem[];
125
+ /** Body slot — rendered in the scrollable mid-section (e.g. conversation history). Only visible when expanded. */
126
+ children?: React.ReactNode;
127
+ /** Footer slot — auth section, user avatar, sign-in prompt, etc. */
128
+ footer?: React.ReactNode;
129
+ /** Additional className for the <aside> */
130
+ className?: string;
131
+ }
132
+
133
+ export function AppSidebar({
134
+ logo,
135
+ title,
136
+ items = [],
137
+ children,
138
+ footer,
139
+ className,
140
+ }: AppSidebarProps) {
141
+ const { isOpen, toggle } = useAppSidebar();
142
+ const { shouldAnimate, scale } = useMotionPreference();
143
+ const duration = shouldAnimate ? Math.round(300 * (5 / Math.max(scale, 0.1))) : 0;
144
+
145
+ return (
146
+ <aside
147
+ className={cn(
148
+ 'fixed left-0 top-0 bottom-0 z-40 flex flex-col',
149
+ 'bg-background border-r border-foreground/8 overflow-hidden',
150
+ className
151
+ )}
152
+ style={{
153
+ width: isOpen ? APP_SIDEBAR_WIDTH : APP_SIDEBAR_WIDTH_COLLAPSED,
154
+ transition: shouldAnimate ? `width ${duration}ms ease-out` : 'none',
155
+ }}
156
+ >
157
+ {/* ── Header ─────────────────────────────────────────────────────── */}
158
+ <div className="flex items-center h-16 px-[10px] shrink-0">
159
+ {/* Logo + wordmark — clicking toggles in both states */}
160
+ <button
161
+ onClick={toggle}
162
+ className={cn(
163
+ 'flex items-center gap-2.5 flex-1 min-w-0',
164
+ 'rounded-lg p-1.5',
165
+ 'hover:bg-foreground/5 transition-colors duration-150',
166
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus)]'
167
+ )}
168
+ aria-label={isOpen ? (title ?? 'OpenCosmos') : 'Expand sidebar'}
169
+ >
170
+ <span className="w-8 h-8 shrink-0 flex items-center justify-center">
171
+ {logo}
172
+ </span>
173
+ <span
174
+ className="font-semibold text-sm text-foreground whitespace-nowrap"
175
+ style={{
176
+ opacity: isOpen ? 1 : 0,
177
+ width: isOpen ? 'auto' : 0,
178
+ overflow: 'hidden',
179
+ pointerEvents: isOpen ? 'auto' : 'none',
180
+ transition: shouldAnimate
181
+ ? `opacity ${Math.round(duration * 0.6)}ms ease-out`
182
+ : 'none',
183
+ }}
184
+ >
185
+ {title}
186
+ </span>
187
+ </button>
188
+
189
+ {/* Collapse icon — only shown/accessible when open */}
190
+ <button
191
+ onClick={toggle}
192
+ tabIndex={isOpen ? 0 : -1}
193
+ className={cn(
194
+ 'shrink-0 w-8 h-8 flex items-center justify-center rounded-lg',
195
+ 'text-foreground/35 hover:text-foreground/65 hover:bg-foreground/5',
196
+ 'transition-colors duration-150',
197
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus)]'
198
+ )}
199
+ style={{
200
+ opacity: isOpen ? 1 : 0,
201
+ pointerEvents: isOpen ? 'auto' : 'none',
202
+ transition: shouldAnimate
203
+ ? `opacity ${Math.round(duration * 0.5)}ms ease-out`
204
+ : 'none',
205
+ }}
206
+ aria-label="Collapse sidebar"
207
+ >
208
+ <PanelLeftClose className="w-4 h-4" />
209
+ </button>
210
+ </div>
211
+
212
+ {/* ── Nav items ──────────────────────────────────────────────────── */}
213
+ {items.length > 0 && (
214
+ <nav className="px-2 py-2 space-y-0.5 shrink-0" aria-label="Main navigation">
215
+ {items.map((item) => (
216
+ <a
217
+ key={item.label}
218
+ href={item.href}
219
+ target={item.external ? '_blank' : undefined}
220
+ rel={item.external ? 'noopener noreferrer' : undefined}
221
+ title={!isOpen ? item.label : undefined}
222
+ aria-label={!isOpen ? item.label : undefined}
223
+ className={cn(
224
+ 'flex items-center rounded-lg transition-colors duration-150',
225
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus)]',
226
+ isOpen
227
+ ? 'gap-3 px-3 py-2.5'
228
+ : 'justify-center w-9 h-9 mx-auto',
229
+ item.active
230
+ ? 'bg-foreground/8 text-foreground font-medium'
231
+ : 'text-[var(--color-text-secondary)] hover:bg-foreground/5 hover:text-[var(--color-text-primary)]'
232
+ )}
233
+ >
234
+ <span className="shrink-0 flex items-center justify-center w-4 h-4">
235
+ {item.icon}
236
+ </span>
237
+ <span
238
+ className="text-sm whitespace-nowrap"
239
+ style={{
240
+ opacity: isOpen ? 1 : 0,
241
+ width: isOpen ? 'auto' : 0,
242
+ overflow: 'hidden',
243
+ pointerEvents: isOpen ? 'auto' : 'none',
244
+ transition: shouldAnimate
245
+ ? `opacity ${Math.round(duration * 0.55)}ms ease-out`
246
+ : 'none',
247
+ }}
248
+ >
249
+ {item.label}
250
+ </span>
251
+ </a>
252
+ ))}
253
+ </nav>
254
+ )}
255
+
256
+ {/* ── Body (conversation history, etc.) ──────────────────────────── */}
257
+ <div
258
+ className="flex-1 overflow-y-auto overflow-x-hidden min-h-0"
259
+ style={{ opacity: isOpen ? 1 : 0, pointerEvents: isOpen ? 'auto' : 'none' }}
260
+ >
261
+ {children}
262
+ </div>
263
+
264
+ {/* ── Footer ─────────────────────────────────────────────────────── */}
265
+ {footer && (
266
+ <div
267
+ className={cn(
268
+ 'shrink-0 border-t border-foreground/8',
269
+ isOpen ? 'p-3' : 'px-2 py-3 flex justify-center'
270
+ )}
271
+ >
272
+ {footer}
273
+ </div>
274
+ )}
275
+ </aside>
276
+ );
277
+ }
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import React from 'react';
3
3
  import { SlidersHorizontal, Sun, Moon, SunMoon, Building2, Leaf, Zap, Rocket, X, Palette } from 'lucide-react';
4
- import { studioTokens, terraTokens, voltTokens, speedboatTokens, PUBLIC_THEME_NAMES } from '@thesage/tokens';
5
- import type { ThemeName } from '@thesage/tokens';
4
+ import { studioTokens, terraTokens, voltTokens, speedboatTokens, PUBLIC_THEME_NAMES } from '@opencosmos/tokens';
5
+ import type { ThemeName } from '@opencosmos/tokens';
6
6
  import { useCustomizer } from '../../lib/store/customizer';
7
7
  import { useThemeStore } from '../../lib/store/theme';
8
8
  import { ColorPicker } from '../forms/ColorPicker';
@@ -14,5 +14,6 @@ export * from './Resizable';
14
14
  export * from './ScrollArea';
15
15
  export * from './Separator';
16
16
  export * from './Sidebar';
17
+ export * from './AppSidebar';
17
18
  export * from './Stack';
18
19
  export * from './GlassSurface';
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useThemeStore } from '../lib/store/theme';
4
- import type { ThemeName, ColorMode } from '@thesage/tokens';
4
+ import type { ThemeName, ColorMode } from '@opencosmos/tokens';
5
5
 
6
6
  export interface ThemeHook {
7
7
  /**
package/src/hooks.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Hooks subpath export
3
- * Allows: import { ... } from '@thesage/ui/hooks'
3
+ * Allows: import { ... } from '@opencosmos/ui/hooks'
4
4
  */
5
5
 
6
6
  // Re-export all hooks
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  // ============================================================================
2
2
  // MAIN BARREL EXPORTS - Backward Compatible
3
3
  // ============================================================================
4
- // All components remain importable from '@thesage/ui' root
5
- // Example: import { Button, Card, Dialog } from '@thesage/ui'
4
+ // All components remain importable from '@opencosmos/ui' root
5
+ // Example: import { Button, Card, Dialog } from '@opencosmos/ui'
6
6
 
7
7
  // Actions
8
8
  export * from './components/actions/Button';
@@ -81,6 +81,7 @@ export * from './components/data-display/CollapsibleCodeBlock';
81
81
  export * from './components/data-display/DataTable';
82
82
  export * from './components/data-display/DescriptionList';
83
83
  export * from './components/data-display/GitHubIcon';
84
+ export * from './components/data-display/OpenCosmosIcon';
84
85
  export * from './components/data-display/Heading';
85
86
  export * from './components/data-display/Table';
86
87
  export * from './components/data-display/Text';
@@ -108,6 +109,7 @@ export * from './components/layout/Resizable';
108
109
  export * from './components/layout/ScrollArea';
109
110
  export * from './components/layout/Separator';
110
111
  export * from './components/layout/Sidebar';
112
+ export * from './components/layout/AppSidebar';
111
113
  export * from './components/layout/Stack';
112
114
  export * from './components/layout/GlassSurface';
113
115
 
@@ -142,7 +144,7 @@ export * from './lib/validation';
142
144
  export * from './lib/syntax-parser';
143
145
 
144
146
  // Tokens (selective re-exports)
145
- export { typographySystem } from '@thesage/tokens';
147
+ export { typographySystem } from '@opencosmos/tokens';
146
148
 
147
149
  // Component Registry (metadata)
148
150
  export * from './component-registry';
@@ -151,7 +153,7 @@ export * from './component-registry';
151
153
  // OPTIONAL CATEGORY-BASED EXPORTS (Future Use)
152
154
  // ============================================================================
153
155
  // These allow category-specific imports for better code organization
154
- // Example: import { Button } from '@thesage/ui/actions'
156
+ // Example: import { Button } from '@opencosmos/ui/actions'
155
157
  // Note: These require package.json exports configuration
156
158
 
157
159
  // Re-export categories for convenience
@@ -1,6 +1,6 @@
1
1
  import { create } from 'zustand';
2
2
  import { persist } from 'zustand/middleware';
3
- import { computeDerivedTokens, type FontTheme } from '@thesage/tokens';
3
+ import { computeDerivedTokens, type FontTheme } from '@opencosmos/tokens';
4
4
  import {
5
5
  generateColorScale,
6
6
  getOptimalForeground,
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { create } from 'zustand';
7
7
  import { persist } from 'zustand/middleware';
8
- import type { ThemeName, ColorMode } from '@thesage/tokens';
8
+ import type { ThemeName, ColorMode } from '@opencosmos/tokens';
9
9
 
10
10
  // Re-export types for convenience
11
11
  export type { ThemeName, ColorMode };
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * ```ts
9
- * import { parseCode } from '@thesage/ui';
9
+ * import { parseCode } from '@opencosmos/ui';
10
10
  *
11
11
  * const tokens = parseCode('const greeting = "Hello World";');
12
12
  * // Use with CollapsibleCodeBlock:
@@ -8,8 +8,8 @@
8
8
  import { useEffect, useState } from 'react';
9
9
  import { useThemeStore } from '../lib/store/theme';
10
10
  import { useCustomizer, type ColorPalette } from '../lib/store/customizer';
11
- import { studioTokens, terraTokens, voltTokens, speedboatTokens, syntaxColors, codeColors } from '@thesage/tokens';
12
- import type { ThemeName, ColorMode } from '@thesage/tokens';
11
+ import { studioTokens, terraTokens, voltTokens, speedboatTokens, syntaxColors, codeColors } from '@opencosmos/tokens';
12
+ import type { ThemeName, ColorMode } from '@opencosmos/tokens';
13
13
 
14
14
  // ── Type-safe token access ──────────────────────────────────────────────────
15
15
 
package/src/providers.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Providers subpath export
3
- * Allows: import { ... } from '@thesage/ui/providers'
3
+ * Allows: import { ... } from '@opencosmos/ui/providers'
4
4
  */
5
5
 
6
6
  // Re-export all providers
package/src/tokens.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Tokens subpath export
3
- * Allows: import { ... } from '@thesage/ui/tokens'
3
+ * Allows: import { ... } from '@opencosmos/ui/tokens'
4
4
  */
5
5
 
6
- // Re-export all tokens from @thesage/tokens
7
- export * from '@thesage/tokens';
6
+ // Re-export all tokens from @opencosmos/tokens
7
+ export * from '@opencosmos/tokens';
package/src/utils.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Utils subpath export
3
- * Allows: import { ... } from '@thesage/ui/utils'
3
+ * Allows: import { ... } from '@opencosmos/ui/utils'
4
4
  */
5
5
 
6
6
  // Re-export all utilities