@medalsocial/meda 0.3.0 → 1.0.0
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/README.md +26 -0
- package/dist/brand/index.d.ts +2 -0
- package/dist/brand/index.js +1 -0
- package/dist/brand/medal-social-mark.d.ts +7 -0
- package/dist/brand/medal-social-mark.js +6 -0
- package/dist/brand/public.d.ts +2 -0
- package/dist/brand/public.js +1 -0
- package/dist/chat/latency-badge.js +6 -2
- package/dist/chat/tool-call-block.js +1 -1
- package/dist/chat/turn-card.js +3 -1
- package/dist/components/ui/command.d.ts +19 -0
- package/dist/components/ui/command.js +37 -0
- package/dist/components/ui/dialog.d.ts +26 -0
- package/dist/components/ui/dialog.js +39 -0
- package/dist/components/ui/drawer.d.ts +16 -0
- package/dist/components/ui/drawer.js +35 -0
- package/dist/components/ui/dropdown-menu.d.ts +32 -0
- package/dist/components/ui/dropdown-menu.js +68 -0
- package/dist/components/ui/tooltip.d.ts +6 -0
- package/dist/components/ui/tooltip.js +17 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/marketing/index.d.ts +5 -0
- package/dist/marketing/index.js +4 -0
- package/dist/marketing/marketing-callout.d.ts +2 -0
- package/dist/marketing/marketing-callout.js +8 -0
- package/dist/marketing/marketing-contact.d.ts +2 -0
- package/dist/marketing/marketing-contact.js +19 -0
- package/dist/marketing/marketing-cta-list.d.ts +2 -0
- package/dist/marketing/marketing-cta-list.js +21 -0
- package/dist/marketing/marketing-lead-magnet.d.ts +2 -0
- package/dist/marketing/marketing-lead-magnet.js +9 -0
- package/dist/marketing/public.d.ts +1 -0
- package/dist/marketing/public.js +1 -0
- package/dist/marketing/types.d.ts +67 -0
- package/dist/marketing/types.js +1 -0
- package/dist/marketing/utils.d.ts +2 -0
- package/dist/marketing/utils.js +6 -0
- package/dist/panel/inspector-json.js +5 -3
- package/dist/shell/app-shell.d.ts +9 -0
- package/dist/shell/app-shell.js +11 -0
- package/dist/shell/command-palette.d.ts +23 -0
- package/dist/shell/command-palette.js +138 -0
- package/dist/shell/context-rail.d.ts +31 -0
- package/dist/shell/context-rail.js +81 -0
- package/dist/shell/extras/index.d.ts +4 -0
- package/dist/shell/extras/index.js +4 -0
- package/dist/shell/extras/shell-desktop-panel-dock.d.ts +14 -0
- package/dist/shell/extras/shell-desktop-panel-dock.js +43 -0
- package/dist/shell/extras/shell-scrollable-content.d.ts +9 -0
- package/dist/shell/extras/shell-scrollable-content.js +7 -0
- package/dist/shell/extras/shell-tab-bar.d.ts +18 -0
- package/dist/shell/extras/shell-tab-bar.js +27 -0
- package/dist/shell/extras/types.d.ts +64 -0
- package/dist/shell/extras/types.js +2 -0
- package/dist/shell/extras/workbench-layout.d.ts +10 -0
- package/dist/shell/extras/workbench-layout.js +29 -0
- package/dist/shell/icon-rail.d.ts +29 -0
- package/dist/shell/icon-rail.js +38 -0
- package/dist/shell/index.d.ts +24 -15
- package/dist/shell/index.js +35 -15
- package/dist/shell/layout-state.d.ts +32 -0
- package/dist/shell/layout-state.js +98 -0
- package/dist/shell/mobile/mobile-bottom-nav.d.ts +15 -0
- package/dist/shell/mobile/mobile-bottom-nav.js +76 -0
- package/dist/shell/mobile/mobile-drawers.d.ts +20 -0
- package/dist/shell/mobile/mobile-drawers.js +62 -0
- package/dist/shell/mobile/mobile-header.d.ts +23 -0
- package/dist/shell/mobile/mobile-header.js +22 -0
- package/dist/shell/motion.d.ts +22 -0
- package/dist/shell/motion.js +19 -0
- package/dist/shell/public.d.ts +16 -2
- package/dist/shell/public.js +20 -1
- package/dist/shell/resizable-shell.d.ts +45 -0
- package/dist/shell/resizable-shell.js +15 -0
- package/dist/shell/right-panel.d.ts +15 -0
- package/dist/shell/right-panel.js +126 -0
- package/dist/shell/shell-auth-frame.d.ts +22 -0
- package/dist/shell/shell-auth-frame.js +60 -0
- package/dist/shell/shell-header.d.ts +10 -9
- package/dist/shell/shell-header.js +57 -14
- package/dist/shell/shell-layout-utils.d.ts +2 -2
- package/dist/shell/shell-main.d.ts +8 -0
- package/dist/shell/shell-main.js +11 -0
- package/dist/shell/shell-provider.d.ts +65 -0
- package/dist/shell/shell-provider.js +152 -0
- package/dist/shell/shell-route-utils.d.ts +1 -1
- package/dist/shell/theme-next-themes.d.ts +4 -0
- package/dist/shell/theme-next-themes.js +24 -0
- package/dist/shell/theme.d.ts +9 -0
- package/dist/shell/theme.js +58 -0
- package/dist/shell/types.d.ts +41 -41
- package/dist/shell/types.js +1 -0
- package/dist/shell/use-shell-viewport.d.ts +2 -0
- package/dist/shell/use-shell-viewport.js +40 -0
- package/dist/shell/utils.d.ts +1 -1
- package/dist/styles/globals.css +26 -0
- package/dist/styles/theme.css +147 -0
- package/dist/styles/tokens.css +338 -0
- package/dist/timeline/date-switcher.js +3 -1
- package/dist/timeline/event-card.js +5 -13
- package/dist/timeline/live-indicator.js +3 -1
- package/dist/timeline/scrub-bar.js +8 -9
- package/dist/timeline/timeline-rail.js +6 -4
- package/dist/timeline/timeline-tape.js +7 -8
- package/dist/voice/voice-level.js +3 -1
- package/dist/voice/voice-orb-scene.js +1 -0
- package/dist/voice/voice-orb.js +9 -11
- package/dist/voice/voice-status-pill.js +4 -9
- package/package.json +53 -6
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { LayoutGrid, Menu, PanelTop, Sparkles } from 'lucide-react';
|
|
4
|
+
import { createContext, lazy, Suspense, useContext, useMemo, useState, } from 'react';
|
|
5
|
+
import { createLocalStorageAdapter, useShellLayoutState, } from './layout-state.js';
|
|
6
|
+
import { DefaultThemeProvider, ThemeCtx } from './theme.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Theme adapter wiring
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Lazy-loaded so that importing next-themes only happens when the consumer
|
|
11
|
+
// passes themeAdapter='next-themes'. Default-adapter consumers never trigger
|
|
12
|
+
// the import.
|
|
13
|
+
const NextThemesAdapter = lazy(() => import('./theme-next-themes.js').then((m) => ({ default: m.NextThemesAdapter })));
|
|
14
|
+
function CustomThemeBridge({ adapter, children }) {
|
|
15
|
+
return _jsx(ThemeCtx.Provider, { value: adapter, children: children });
|
|
16
|
+
}
|
|
17
|
+
function pickTheme(themeAdapter, children) {
|
|
18
|
+
const adapter = themeAdapter ?? 'default';
|
|
19
|
+
if (adapter === 'default') {
|
|
20
|
+
return _jsx(DefaultThemeProvider, { children: children });
|
|
21
|
+
}
|
|
22
|
+
if (adapter === 'next-themes') {
|
|
23
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(NextThemesAdapter, { children: children }) }));
|
|
24
|
+
}
|
|
25
|
+
// Custom ThemeAdapter object
|
|
26
|
+
return _jsx(CustomThemeBridge, { adapter: adapter, children: children });
|
|
27
|
+
}
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Default mobile bottom nav (canonical 4-item set, per spec §16)
|
|
30
|
+
// Full icons ship here; the per-app customisation point is the mobileBottomNav
|
|
31
|
+
// prop. For RC.1 the canonical drawer wire-up is a Phase 13 concern — only
|
|
32
|
+
// the shape is locked here.
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
const defaultMobileBottomNav = [
|
|
35
|
+
{ id: 'menu', label: 'Menu', icon: Menu, opens: 'menu-drawer' },
|
|
36
|
+
{ id: 'module', label: 'Module', icon: LayoutGrid, opens: 'module-drawer' },
|
|
37
|
+
{ id: 'panels', label: 'Panels', icon: PanelTop, opens: 'panels-drawer' },
|
|
38
|
+
{ id: 'ai', label: 'AI', icon: Sparkles, opens: 'ai-drawer' },
|
|
39
|
+
];
|
|
40
|
+
const Ctx = createContext(null);
|
|
41
|
+
export function useMedaShell() {
|
|
42
|
+
const v = useContext(Ctx);
|
|
43
|
+
if (!v)
|
|
44
|
+
throw new Error('useMedaShell must be used inside <MedaShellProvider>');
|
|
45
|
+
return v;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Selection bridge between main workspace and right panel views (spec §17).
|
|
49
|
+
*
|
|
50
|
+
* `T` is consumer-typed: setSelection writes whatever shape you pass; the
|
|
51
|
+
* matching getter narrows to `T | null`. The shell does not validate the
|
|
52
|
+
* shape — coordinate types between writer and readers in your app.
|
|
53
|
+
*/
|
|
54
|
+
export function useShellSelection() {
|
|
55
|
+
const ctx = useMedaShell();
|
|
56
|
+
return [ctx.selection, ctx.setSelection];
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Provider implementation
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
export function MedaShellProvider(props) {
|
|
62
|
+
// Guard: apps array must not be empty
|
|
63
|
+
if (props.apps.length === 0) {
|
|
64
|
+
throw new Error('MedaShellProvider: apps must contain at least one AppDefinition');
|
|
65
|
+
}
|
|
66
|
+
// Stable storage reference — prevents useShellLayoutState's hydration
|
|
67
|
+
// effect from looping when an inline adapter would change identity each render.
|
|
68
|
+
const storage = useMemo(() => props.storage ?? createLocalStorageAdapter(), [props.storage]);
|
|
69
|
+
const [activeAppId, setActiveApp] = useState(props.defaultActiveApp ?? props.apps[0]?.id ?? '');
|
|
70
|
+
const [selection, setSelection] = useState(null);
|
|
71
|
+
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(null);
|
|
72
|
+
const mobileDrawer = useMemo(() => ({
|
|
73
|
+
open: mobileDrawerOpen,
|
|
74
|
+
setOpen: setMobileDrawerOpen,
|
|
75
|
+
}), [mobileDrawerOpen]);
|
|
76
|
+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
77
|
+
const commandPalette = useMemo(() => ({
|
|
78
|
+
open: commandPaletteOpen,
|
|
79
|
+
setOpen: setCommandPaletteOpen,
|
|
80
|
+
}), [commandPaletteOpen]);
|
|
81
|
+
const [layoutState, setLayoutState] = useShellLayoutState({
|
|
82
|
+
workspaceId: props.workspace.id,
|
|
83
|
+
appId: activeAppId,
|
|
84
|
+
storage,
|
|
85
|
+
});
|
|
86
|
+
const panel = useMemo(() => ({
|
|
87
|
+
mode: layoutState.rightPanel.mode,
|
|
88
|
+
activeView: layoutState.rightPanel.activeView,
|
|
89
|
+
width: layoutState.rightPanel.width,
|
|
90
|
+
setMode: (mode) => setLayoutState((prev) => ({
|
|
91
|
+
...prev,
|
|
92
|
+
rightPanel: { ...prev.rightPanel, mode },
|
|
93
|
+
})),
|
|
94
|
+
setActiveView: (activeView) => setLayoutState((prev) => ({
|
|
95
|
+
...prev,
|
|
96
|
+
rightPanel: { ...prev.rightPanel, activeView },
|
|
97
|
+
})),
|
|
98
|
+
setWidth: (width) => setLayoutState((prev) => ({
|
|
99
|
+
...prev,
|
|
100
|
+
rightPanel: { ...prev.rightPanel, width },
|
|
101
|
+
})),
|
|
102
|
+
// focus(viewId) — opens panel + switches to view in one call.
|
|
103
|
+
// Only flips closed → panel; preserves expanded / fullscreen modes.
|
|
104
|
+
focus: (viewId) => setLayoutState((prev) => {
|
|
105
|
+
const nextMode = prev.rightPanel.mode === 'closed' ? 'panel' : prev.rightPanel.mode;
|
|
106
|
+
return {
|
|
107
|
+
...prev,
|
|
108
|
+
rightPanel: { ...prev.rightPanel, mode: nextMode, activeView: viewId },
|
|
109
|
+
};
|
|
110
|
+
}),
|
|
111
|
+
}), [layoutState, setLayoutState]);
|
|
112
|
+
const contextRail = useMemo(() => ({
|
|
113
|
+
width: layoutState.contextRail.width,
|
|
114
|
+
collapsed: layoutState.contextRail.collapsed,
|
|
115
|
+
setWidth: (width) => setLayoutState((prev) => ({
|
|
116
|
+
...prev,
|
|
117
|
+
contextRail: { ...prev.contextRail, width },
|
|
118
|
+
})),
|
|
119
|
+
setCollapsed: (collapsed) => setLayoutState((prev) => ({
|
|
120
|
+
...prev,
|
|
121
|
+
contextRail: { ...prev.contextRail, collapsed },
|
|
122
|
+
})),
|
|
123
|
+
}), [layoutState, setLayoutState]);
|
|
124
|
+
const value = useMemo(() => ({
|
|
125
|
+
workspace: props.workspace,
|
|
126
|
+
workspaces: props.workspaces ?? [props.workspace],
|
|
127
|
+
apps: props.apps,
|
|
128
|
+
activeAppId,
|
|
129
|
+
setActiveApp,
|
|
130
|
+
panel,
|
|
131
|
+
contextRail,
|
|
132
|
+
mobileBottomNav: props.mobileBottomNav ?? defaultMobileBottomNav,
|
|
133
|
+
mobileDrawer,
|
|
134
|
+
commandPalette,
|
|
135
|
+
commandPaletteHotkey: props.commandPaletteHotkey ?? 'mod+k',
|
|
136
|
+
selection,
|
|
137
|
+
setSelection,
|
|
138
|
+
}), [
|
|
139
|
+
props.workspace,
|
|
140
|
+
props.workspaces,
|
|
141
|
+
props.apps,
|
|
142
|
+
activeAppId,
|
|
143
|
+
panel,
|
|
144
|
+
contextRail,
|
|
145
|
+
props.mobileBottomNav,
|
|
146
|
+
mobileDrawer,
|
|
147
|
+
commandPalette,
|
|
148
|
+
props.commandPaletteHotkey,
|
|
149
|
+
selection,
|
|
150
|
+
]);
|
|
151
|
+
return _jsx(Ctx.Provider, { value: value, children: pickTheme(props.themeAdapter, props.children) });
|
|
152
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { ShellContentLayout, ShellTab } from './types.js';
|
|
2
|
+
import type { ShellContentLayout, ShellTab } from './extras/types.js';
|
|
3
3
|
interface ShellRouteMatchLike {
|
|
4
4
|
handle?: unknown;
|
|
5
5
|
params?: Record<string, string | undefined>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ThemeProvider as NextThemesProvider, useTheme as useNextTheme } from 'next-themes';
|
|
4
|
+
import { ThemeCtx } from './theme.js';
|
|
5
|
+
function narrow(value) {
|
|
6
|
+
if (value === 'light' || value === 'dark' || value === 'system')
|
|
7
|
+
return value;
|
|
8
|
+
return 'system';
|
|
9
|
+
}
|
|
10
|
+
function narrowResolved(value) {
|
|
11
|
+
return value === 'dark' ? 'dark' : 'light';
|
|
12
|
+
}
|
|
13
|
+
function NextThemesBridge({ children }) {
|
|
14
|
+
const { theme, setTheme, resolvedTheme } = useNextTheme();
|
|
15
|
+
const value = {
|
|
16
|
+
theme: narrow(theme),
|
|
17
|
+
setTheme: (t) => setTheme(t),
|
|
18
|
+
resolvedTheme: narrowResolved(resolvedTheme),
|
|
19
|
+
};
|
|
20
|
+
return _jsx(ThemeCtx.Provider, { value: value, children: children });
|
|
21
|
+
}
|
|
22
|
+
export function NextThemesAdapter({ children }) {
|
|
23
|
+
return (_jsx(NextThemesProvider, { attribute: "class", enableSystem: true, defaultTheme: "system", children: _jsx(NextThemesBridge, { children: children }) }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ThemeAdapter } from './types.js';
|
|
3
|
+
/** @internal – used by theme adapters; not part of the public API. */
|
|
4
|
+
export declare const ThemeCtx: import("react").Context<ThemeAdapter | null>;
|
|
5
|
+
export declare function DefaultThemeProvider({ children }: {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function useTheme(): ThemeAdapter;
|
|
9
|
+
export declare function ThemeToggle(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Monitor, Moon, Sun } from 'lucide-react';
|
|
4
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
5
|
+
const NEXT = {
|
|
6
|
+
light: 'dark',
|
|
7
|
+
dark: 'system',
|
|
8
|
+
system: 'light',
|
|
9
|
+
};
|
|
10
|
+
const ICON = {
|
|
11
|
+
light: Sun,
|
|
12
|
+
dark: Moon,
|
|
13
|
+
system: Monitor,
|
|
14
|
+
};
|
|
15
|
+
const LABEL = {
|
|
16
|
+
light: 'Switch to dark theme',
|
|
17
|
+
dark: 'Switch to system theme',
|
|
18
|
+
system: 'Switch to light theme',
|
|
19
|
+
};
|
|
20
|
+
/** @internal – used by theme adapters; not part of the public API. */
|
|
21
|
+
export const ThemeCtx = createContext(null);
|
|
22
|
+
export function DefaultThemeProvider({ children }) {
|
|
23
|
+
const [theme, setThemeState] = useState('system');
|
|
24
|
+
const [resolvedTheme, setResolvedTheme] = useState('light');
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const saved = window.localStorage.getItem('meda:theme');
|
|
27
|
+
if (saved === 'light' || saved === 'dark' || saved === 'system') {
|
|
28
|
+
setThemeState(saved);
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
33
|
+
const apply = () => {
|
|
34
|
+
const resolved = theme === 'system' ? (mq.matches ? 'dark' : 'light') : theme;
|
|
35
|
+
setResolvedTheme(resolved);
|
|
36
|
+
document.documentElement.classList.toggle('dark', resolved === 'dark');
|
|
37
|
+
};
|
|
38
|
+
apply();
|
|
39
|
+
mq.addEventListener('change', apply);
|
|
40
|
+
return () => mq.removeEventListener('change', apply);
|
|
41
|
+
}, [theme]);
|
|
42
|
+
const setTheme = (t) => {
|
|
43
|
+
setThemeState(t);
|
|
44
|
+
window.localStorage.setItem('meda:theme', t);
|
|
45
|
+
};
|
|
46
|
+
return (_jsx(ThemeCtx.Provider, { value: { theme, setTheme, resolvedTheme }, children: children }));
|
|
47
|
+
}
|
|
48
|
+
export function useTheme() {
|
|
49
|
+
const v = useContext(ThemeCtx);
|
|
50
|
+
if (!v)
|
|
51
|
+
throw new Error('useTheme must be used inside <MedaShellProvider>');
|
|
52
|
+
return v;
|
|
53
|
+
}
|
|
54
|
+
export function ThemeToggle() {
|
|
55
|
+
const { theme, setTheme } = useTheme();
|
|
56
|
+
const Icon = ICON[theme];
|
|
57
|
+
return (_jsx("button", { type: "button", "aria-label": LABEL[theme], onClick: () => setTheme(NEXT[theme]), children: _jsx(Icon, { "aria-hidden": "true" }) }));
|
|
58
|
+
}
|
package/dist/shell/types.d.ts
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
1
|
import type { LucideIcon } from 'lucide-react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
export
|
|
4
|
-
export type ShellViewportBand = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'ultrawide';
|
|
5
|
-
export interface ShellTab {
|
|
3
|
+
export interface AppDefinition {
|
|
6
4
|
id: string;
|
|
7
5
|
label: string;
|
|
8
|
-
to?: string;
|
|
9
|
-
icon?: LucideIcon;
|
|
10
|
-
}
|
|
11
|
-
export interface ShellNavItem {
|
|
12
|
-
to: string;
|
|
13
|
-
label: string;
|
|
14
|
-
description?: string;
|
|
15
6
|
icon: LucideIcon;
|
|
16
|
-
shortcut?: string;
|
|
17
7
|
}
|
|
18
|
-
export interface
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
icon:
|
|
8
|
+
export interface WorkspaceDefinition {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
icon: ReactNode;
|
|
22
12
|
}
|
|
23
|
-
export interface
|
|
13
|
+
export interface ContextModule {
|
|
24
14
|
id: string;
|
|
25
15
|
label: string;
|
|
26
|
-
description
|
|
27
|
-
items:
|
|
28
|
-
headerTabs?: ShellTab[];
|
|
16
|
+
description?: string;
|
|
17
|
+
items: ContextItem[];
|
|
29
18
|
}
|
|
30
|
-
export interface
|
|
19
|
+
export interface ContextItem {
|
|
31
20
|
id: string;
|
|
32
21
|
label: string;
|
|
33
22
|
icon: LucideIcon;
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
to: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
shortcut?: string;
|
|
36
26
|
}
|
|
37
|
-
export interface
|
|
27
|
+
export interface PanelView {
|
|
38
28
|
id: string;
|
|
39
29
|
label: string;
|
|
40
|
-
to: string;
|
|
41
30
|
icon: LucideIcon;
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
render: (ctx: ShellRenderContext) => ReactNode;
|
|
32
|
+
}
|
|
33
|
+
export type PanelMode = 'closed' | 'panel' | 'expanded' | 'fullscreen';
|
|
34
|
+
export type ShellMainLayout = 'workspace' | 'centered' | 'fullbleed';
|
|
35
|
+
export type ShellViewport = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'ultrawide';
|
|
36
|
+
export interface MobileBottomNavItem {
|
|
37
|
+
id: string;
|
|
38
|
+
label: string | (() => string);
|
|
39
|
+
icon: LucideIcon;
|
|
40
|
+
opens: 'menu-drawer' | 'module-drawer' | 'panels-drawer' | 'ai-drawer' | ((close: () => void) => ReactNode);
|
|
44
41
|
}
|
|
45
42
|
export interface ShellRenderContext {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
selectedProductId: string | null;
|
|
43
|
+
workspaceId: string;
|
|
44
|
+
appId: string;
|
|
49
45
|
}
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
export interface ShellLinkRenderArgs {
|
|
47
|
+
item: ContextItem;
|
|
48
|
+
isActive: boolean;
|
|
49
|
+
className: string;
|
|
50
|
+
children: ReactNode;
|
|
54
51
|
}
|
|
55
|
-
export interface
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
export interface CommandDefinition {
|
|
53
|
+
id: string;
|
|
54
|
+
label: string;
|
|
55
|
+
icon?: LucideIcon;
|
|
56
|
+
group: string;
|
|
57
|
+
shortcut?: string;
|
|
58
|
+
run: () => void | Promise<void>;
|
|
58
59
|
}
|
|
59
|
-
export interface
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
shellPanelViews?: string[];
|
|
60
|
+
export interface ThemeAdapter {
|
|
61
|
+
theme: 'light' | 'dark' | 'system';
|
|
62
|
+
setTheme: (t: 'light' | 'dark' | 'system') => void;
|
|
63
|
+
resolvedTheme: 'light' | 'dark';
|
|
64
64
|
}
|
package/dist/shell/types.js
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
const BREAKPOINTS = {
|
|
4
|
+
mobile: '(max-width: 767px)',
|
|
5
|
+
tablet: '(min-width: 768px) and (max-width: 1023px)',
|
|
6
|
+
desktop: '(min-width: 1024px) and (max-width: 1279px)',
|
|
7
|
+
wide: '(min-width: 1280px) and (max-width: 1535px)',
|
|
8
|
+
ultrawide: '(min-width: 1536px)',
|
|
9
|
+
};
|
|
10
|
+
function detectViewport() {
|
|
11
|
+
if (typeof window === 'undefined')
|
|
12
|
+
return 'desktop';
|
|
13
|
+
for (const [band, query] of Object.entries(BREAKPOINTS)) {
|
|
14
|
+
if (window.matchMedia(query).matches)
|
|
15
|
+
return band;
|
|
16
|
+
}
|
|
17
|
+
return 'desktop';
|
|
18
|
+
}
|
|
19
|
+
export function useShellViewport() {
|
|
20
|
+
// Initial state is always 'desktop' so server-rendered output matches the
|
|
21
|
+
// client's first paint — actual band resolves in the post-mount effect.
|
|
22
|
+
const [viewport, setViewport] = useState('desktop');
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setViewport(detectViewport());
|
|
25
|
+
const cleanups = Object.entries(BREAKPOINTS).map(([band, query]) => {
|
|
26
|
+
const mql = window.matchMedia(query);
|
|
27
|
+
const onChange = () => {
|
|
28
|
+
if (mql.matches)
|
|
29
|
+
setViewport(band);
|
|
30
|
+
};
|
|
31
|
+
mql.addEventListener('change', onChange);
|
|
32
|
+
return () => mql.removeEventListener('change', onChange);
|
|
33
|
+
});
|
|
34
|
+
return () => {
|
|
35
|
+
for (const cleanup of cleanups)
|
|
36
|
+
cleanup();
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
return viewport;
|
|
40
|
+
}
|
package/dist/shell/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ShellCommandDefinition, ShellModuleDefinition, ShellPanelDefinition } from './types.js';
|
|
1
|
+
import type { ShellCommandDefinition, ShellModuleDefinition, ShellPanelDefinition } from './extras/types.js';
|
|
2
2
|
export interface ShellPanelCollection {
|
|
3
3
|
globalViews: ShellPanelDefinition[];
|
|
4
4
|
moduleViews: ShellPanelDefinition[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--meda-shell-panel-dock-radius: var(--radius-4xl, 1.5rem);
|
|
3
|
+
--meda-shell-panel-dock-shadow: var(--shadow-panel-elevated, 0 32px 90px rgb(0 0 0 / 0.45));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* VoiceOrb (WebGL): minimal container styles — visual identity lives in the GLSL shader. */
|
|
7
|
+
.meda-voice-orb {
|
|
8
|
+
background: transparent;
|
|
9
|
+
border: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
isolation: isolate;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
border-radius: 9999px;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
position: relative;
|
|
16
|
+
}
|
|
17
|
+
.meda-voice-orb[disabled] {
|
|
18
|
+
cursor: not-allowed;
|
|
19
|
+
opacity: 0.6;
|
|
20
|
+
}
|
|
21
|
+
.meda-voice-orb canvas {
|
|
22
|
+
display: block;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
@import "./tokens.css";
|
|
2
|
+
|
|
3
|
+
@custom-variant dark (&:is(.dark *, [data-theme="dark"] *));
|
|
4
|
+
|
|
5
|
+
@theme inline {
|
|
6
|
+
/* Color primitives — exposed as Tailwind color scales */
|
|
7
|
+
|
|
8
|
+
/* Brand (purple) — 11 stops */
|
|
9
|
+
--color-brand-50: var(--color-brand-50);
|
|
10
|
+
--color-brand-100: var(--color-brand-100);
|
|
11
|
+
--color-brand-200: var(--color-brand-200);
|
|
12
|
+
--color-brand-300: var(--color-brand-300);
|
|
13
|
+
--color-brand-400: var(--color-brand-400);
|
|
14
|
+
--color-brand-500: var(--color-brand-500);
|
|
15
|
+
--color-brand-600: var(--color-brand-600);
|
|
16
|
+
--color-brand-700: var(--color-brand-700);
|
|
17
|
+
--color-brand-800: var(--color-brand-800);
|
|
18
|
+
--color-brand-900: var(--color-brand-900);
|
|
19
|
+
--color-brand-950: var(--color-brand-950);
|
|
20
|
+
|
|
21
|
+
/* Neutral (zinc-aligned) — 11 stops */
|
|
22
|
+
--color-neutral-50: var(--color-neutral-50);
|
|
23
|
+
--color-neutral-100: var(--color-neutral-100);
|
|
24
|
+
--color-neutral-200: var(--color-neutral-200);
|
|
25
|
+
--color-neutral-300: var(--color-neutral-300);
|
|
26
|
+
--color-neutral-400: var(--color-neutral-400);
|
|
27
|
+
--color-neutral-500: var(--color-neutral-500);
|
|
28
|
+
--color-neutral-600: var(--color-neutral-600);
|
|
29
|
+
--color-neutral-700: var(--color-neutral-700);
|
|
30
|
+
--color-neutral-800: var(--color-neutral-800);
|
|
31
|
+
--color-neutral-900: var(--color-neutral-900);
|
|
32
|
+
--color-neutral-950: var(--color-neutral-950);
|
|
33
|
+
|
|
34
|
+
/* Error (rose) — 10 stops */
|
|
35
|
+
--color-error-50: var(--color-error-50);
|
|
36
|
+
--color-error-100: var(--color-error-100);
|
|
37
|
+
--color-error-200: var(--color-error-200);
|
|
38
|
+
--color-error-300: var(--color-error-300);
|
|
39
|
+
--color-error-400: var(--color-error-400);
|
|
40
|
+
--color-error-500: var(--color-error-500);
|
|
41
|
+
--color-error-600: var(--color-error-600);
|
|
42
|
+
--color-error-700: var(--color-error-700);
|
|
43
|
+
--color-error-800: var(--color-error-800);
|
|
44
|
+
--color-error-900: var(--color-error-900);
|
|
45
|
+
|
|
46
|
+
/* Info (blue) — 10 stops */
|
|
47
|
+
--color-info-50: var(--color-info-50);
|
|
48
|
+
--color-info-100: var(--color-info-100);
|
|
49
|
+
--color-info-200: var(--color-info-200);
|
|
50
|
+
--color-info-300: var(--color-info-300);
|
|
51
|
+
--color-info-400: var(--color-info-400);
|
|
52
|
+
--color-info-500: var(--color-info-500);
|
|
53
|
+
--color-info-600: var(--color-info-600);
|
|
54
|
+
--color-info-700: var(--color-info-700);
|
|
55
|
+
--color-info-800: var(--color-info-800);
|
|
56
|
+
--color-info-900: var(--color-info-900);
|
|
57
|
+
|
|
58
|
+
/* Success (teal) — 10 stops */
|
|
59
|
+
--color-success-50: var(--color-success-50);
|
|
60
|
+
--color-success-100: var(--color-success-100);
|
|
61
|
+
--color-success-200: var(--color-success-200);
|
|
62
|
+
--color-success-300: var(--color-success-300);
|
|
63
|
+
--color-success-400: var(--color-success-400);
|
|
64
|
+
--color-success-500: var(--color-success-500);
|
|
65
|
+
--color-success-600: var(--color-success-600);
|
|
66
|
+
--color-success-700: var(--color-success-700);
|
|
67
|
+
--color-success-800: var(--color-success-800);
|
|
68
|
+
--color-success-900: var(--color-success-900);
|
|
69
|
+
|
|
70
|
+
/* Warning (amber) — 10 stops */
|
|
71
|
+
--color-warning-50: var(--color-warning-50);
|
|
72
|
+
--color-warning-100: var(--color-warning-100);
|
|
73
|
+
--color-warning-200: var(--color-warning-200);
|
|
74
|
+
--color-warning-300: var(--color-warning-300);
|
|
75
|
+
--color-warning-400: var(--color-warning-400);
|
|
76
|
+
--color-warning-500: var(--color-warning-500);
|
|
77
|
+
--color-warning-600: var(--color-warning-600);
|
|
78
|
+
--color-warning-700: var(--color-warning-700);
|
|
79
|
+
--color-warning-800: var(--color-warning-800);
|
|
80
|
+
--color-warning-900: var(--color-warning-900);
|
|
81
|
+
|
|
82
|
+
/* shadcn semantic colors (mode-flipped at runtime via :root / .dark) */
|
|
83
|
+
--color-background: var(--background);
|
|
84
|
+
--color-foreground: var(--foreground);
|
|
85
|
+
--color-card: var(--card);
|
|
86
|
+
--color-card-foreground: var(--card-foreground);
|
|
87
|
+
--color-popover: var(--popover);
|
|
88
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
89
|
+
--color-primary: var(--primary);
|
|
90
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
91
|
+
--color-secondary: var(--secondary);
|
|
92
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
93
|
+
--color-muted: var(--muted);
|
|
94
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
95
|
+
--color-accent: var(--accent);
|
|
96
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
97
|
+
--color-destructive: var(--destructive);
|
|
98
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
99
|
+
--color-border: var(--border);
|
|
100
|
+
--color-input: var(--input);
|
|
101
|
+
--color-ring: var(--ring);
|
|
102
|
+
|
|
103
|
+
/* Status semantic */
|
|
104
|
+
--color-success: var(--success);
|
|
105
|
+
--color-success-foreground: var(--success-foreground);
|
|
106
|
+
--color-warning: var(--warning);
|
|
107
|
+
--color-warning-foreground: var(--warning-foreground);
|
|
108
|
+
--color-info: var(--info);
|
|
109
|
+
--color-info-foreground: var(--info-foreground);
|
|
110
|
+
--color-danger: var(--danger);
|
|
111
|
+
|
|
112
|
+
/* Sidebar */
|
|
113
|
+
--color-sidebar: var(--sidebar);
|
|
114
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
115
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
116
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
117
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
118
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
119
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
120
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
121
|
+
|
|
122
|
+
/* Chart */
|
|
123
|
+
--color-chart-1: var(--chart-1);
|
|
124
|
+
--color-chart-2: var(--chart-2);
|
|
125
|
+
--color-chart-3: var(--chart-3);
|
|
126
|
+
--color-chart-4: var(--chart-4);
|
|
127
|
+
--color-chart-5: var(--chart-5);
|
|
128
|
+
|
|
129
|
+
/* Shell semantic colors */
|
|
130
|
+
--color-shell-header: var(--shell-header);
|
|
131
|
+
--color-shell-rail: var(--shell-rail);
|
|
132
|
+
--color-shell-context: var(--shell-context);
|
|
133
|
+
--color-shell-main: var(--shell-main);
|
|
134
|
+
--color-shell-panel: var(--shell-panel);
|
|
135
|
+
--color-shell-border: var(--shell-border);
|
|
136
|
+
|
|
137
|
+
/* Shell sizing → spacing scale */
|
|
138
|
+
--spacing-shell-header: var(--shell-header-height);
|
|
139
|
+
--spacing-shell-rail: var(--shell-rail-width);
|
|
140
|
+
--spacing-shell-context: var(--shell-context-default);
|
|
141
|
+
--spacing-shell-panel: var(--shell-panel-default);
|
|
142
|
+
--spacing-shell-bottom-nav: var(--shell-bottom-nav-height);
|
|
143
|
+
|
|
144
|
+
/* Font families — Geist + Geist Mono only (no Inter) */
|
|
145
|
+
--font-sans: var(--font-family-sans);
|
|
146
|
+
--font-mono: var(--font-family-mono);
|
|
147
|
+
}
|