@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.
Files changed (112) hide show
  1. package/README.md +26 -0
  2. package/dist/brand/index.d.ts +2 -0
  3. package/dist/brand/index.js +1 -0
  4. package/dist/brand/medal-social-mark.d.ts +7 -0
  5. package/dist/brand/medal-social-mark.js +6 -0
  6. package/dist/brand/public.d.ts +2 -0
  7. package/dist/brand/public.js +1 -0
  8. package/dist/chat/latency-badge.js +6 -2
  9. package/dist/chat/tool-call-block.js +1 -1
  10. package/dist/chat/turn-card.js +3 -1
  11. package/dist/components/ui/command.d.ts +19 -0
  12. package/dist/components/ui/command.js +37 -0
  13. package/dist/components/ui/dialog.d.ts +26 -0
  14. package/dist/components/ui/dialog.js +39 -0
  15. package/dist/components/ui/drawer.d.ts +16 -0
  16. package/dist/components/ui/drawer.js +35 -0
  17. package/dist/components/ui/dropdown-menu.d.ts +32 -0
  18. package/dist/components/ui/dropdown-menu.js +68 -0
  19. package/dist/components/ui/tooltip.d.ts +6 -0
  20. package/dist/components/ui/tooltip.js +17 -0
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.js +3 -1
  23. package/dist/lib/utils.d.ts +2 -0
  24. package/dist/lib/utils.js +5 -0
  25. package/dist/marketing/index.d.ts +5 -0
  26. package/dist/marketing/index.js +4 -0
  27. package/dist/marketing/marketing-callout.d.ts +2 -0
  28. package/dist/marketing/marketing-callout.js +8 -0
  29. package/dist/marketing/marketing-contact.d.ts +2 -0
  30. package/dist/marketing/marketing-contact.js +19 -0
  31. package/dist/marketing/marketing-cta-list.d.ts +2 -0
  32. package/dist/marketing/marketing-cta-list.js +21 -0
  33. package/dist/marketing/marketing-lead-magnet.d.ts +2 -0
  34. package/dist/marketing/marketing-lead-magnet.js +9 -0
  35. package/dist/marketing/public.d.ts +1 -0
  36. package/dist/marketing/public.js +1 -0
  37. package/dist/marketing/types.d.ts +67 -0
  38. package/dist/marketing/types.js +1 -0
  39. package/dist/marketing/utils.d.ts +2 -0
  40. package/dist/marketing/utils.js +6 -0
  41. package/dist/panel/inspector-json.js +5 -3
  42. package/dist/shell/app-shell.d.ts +9 -0
  43. package/dist/shell/app-shell.js +11 -0
  44. package/dist/shell/command-palette.d.ts +23 -0
  45. package/dist/shell/command-palette.js +138 -0
  46. package/dist/shell/context-rail.d.ts +31 -0
  47. package/dist/shell/context-rail.js +81 -0
  48. package/dist/shell/extras/index.d.ts +4 -0
  49. package/dist/shell/extras/index.js +4 -0
  50. package/dist/shell/extras/shell-desktop-panel-dock.d.ts +14 -0
  51. package/dist/shell/extras/shell-desktop-panel-dock.js +43 -0
  52. package/dist/shell/extras/shell-scrollable-content.d.ts +9 -0
  53. package/dist/shell/extras/shell-scrollable-content.js +7 -0
  54. package/dist/shell/extras/shell-tab-bar.d.ts +18 -0
  55. package/dist/shell/extras/shell-tab-bar.js +27 -0
  56. package/dist/shell/extras/types.d.ts +64 -0
  57. package/dist/shell/extras/types.js +2 -0
  58. package/dist/shell/extras/workbench-layout.d.ts +10 -0
  59. package/dist/shell/extras/workbench-layout.js +29 -0
  60. package/dist/shell/icon-rail.d.ts +29 -0
  61. package/dist/shell/icon-rail.js +38 -0
  62. package/dist/shell/index.d.ts +24 -15
  63. package/dist/shell/index.js +35 -15
  64. package/dist/shell/layout-state.d.ts +32 -0
  65. package/dist/shell/layout-state.js +98 -0
  66. package/dist/shell/mobile/mobile-bottom-nav.d.ts +15 -0
  67. package/dist/shell/mobile/mobile-bottom-nav.js +76 -0
  68. package/dist/shell/mobile/mobile-drawers.d.ts +20 -0
  69. package/dist/shell/mobile/mobile-drawers.js +62 -0
  70. package/dist/shell/mobile/mobile-header.d.ts +23 -0
  71. package/dist/shell/mobile/mobile-header.js +22 -0
  72. package/dist/shell/motion.d.ts +22 -0
  73. package/dist/shell/motion.js +19 -0
  74. package/dist/shell/public.d.ts +16 -2
  75. package/dist/shell/public.js +20 -1
  76. package/dist/shell/resizable-shell.d.ts +45 -0
  77. package/dist/shell/resizable-shell.js +15 -0
  78. package/dist/shell/right-panel.d.ts +15 -0
  79. package/dist/shell/right-panel.js +126 -0
  80. package/dist/shell/shell-auth-frame.d.ts +22 -0
  81. package/dist/shell/shell-auth-frame.js +60 -0
  82. package/dist/shell/shell-header.d.ts +10 -9
  83. package/dist/shell/shell-header.js +57 -14
  84. package/dist/shell/shell-layout-utils.d.ts +2 -2
  85. package/dist/shell/shell-main.d.ts +8 -0
  86. package/dist/shell/shell-main.js +11 -0
  87. package/dist/shell/shell-provider.d.ts +65 -0
  88. package/dist/shell/shell-provider.js +152 -0
  89. package/dist/shell/shell-route-utils.d.ts +1 -1
  90. package/dist/shell/theme-next-themes.d.ts +4 -0
  91. package/dist/shell/theme-next-themes.js +24 -0
  92. package/dist/shell/theme.d.ts +9 -0
  93. package/dist/shell/theme.js +58 -0
  94. package/dist/shell/types.d.ts +41 -41
  95. package/dist/shell/types.js +1 -0
  96. package/dist/shell/use-shell-viewport.d.ts +2 -0
  97. package/dist/shell/use-shell-viewport.js +40 -0
  98. package/dist/shell/utils.d.ts +1 -1
  99. package/dist/styles/globals.css +26 -0
  100. package/dist/styles/theme.css +147 -0
  101. package/dist/styles/tokens.css +338 -0
  102. package/dist/timeline/date-switcher.js +3 -1
  103. package/dist/timeline/event-card.js +5 -13
  104. package/dist/timeline/live-indicator.js +3 -1
  105. package/dist/timeline/scrub-bar.js +8 -9
  106. package/dist/timeline/timeline-rail.js +6 -4
  107. package/dist/timeline/timeline-tape.js +7 -8
  108. package/dist/voice/voice-level.js +3 -1
  109. package/dist/voice/voice-orb-scene.js +1 -0
  110. package/dist/voice/voice-orb.js +9 -11
  111. package/dist/voice/voice-status-pill.js +4 -9
  112. package/package.json +53 -6
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ResizableShell — shadcn-style wrapper around react-resizable-panels v4.
3
+ *
4
+ * API surface: Group → ResizableShell, Panel → ResizableShellPanel,
5
+ * Separator → ResizableHandle.
6
+ *
7
+ * Note: react-resizable-panels v4 renamed the exports:
8
+ * PanelGroup → Group, PanelResizeHandle → Separator
9
+ * The v4 prop for layout direction is `orientation` (not `direction`).
10
+ *
11
+ * Persistence: left intentionally to consumers (ContextRail, RightPanel) because
12
+ * they own the (workspaceId, appId) key needed to key useShellLayoutState.
13
+ * ResizableShell is a layout primitive; don't couple it to the shell context hook.
14
+ */
15
+ import type { ReactNode } from 'react';
16
+ import type { OnPanelResize } from 'react-resizable-panels';
17
+ export interface ResizableShellProps {
18
+ /** Default: 'horizontal'. */
19
+ orientation?: 'horizontal' | 'vertical';
20
+ className?: string;
21
+ children: ReactNode;
22
+ }
23
+ export declare function ResizableShell({ orientation, className, children, }: ResizableShellProps): import("react/jsx-runtime").JSX.Element;
24
+ export interface ResizableShellPanelProps {
25
+ /** Percent (0–100). */
26
+ defaultSize?: number;
27
+ /** Minimum size in percent. Token-aligned default: none (set by consumer). */
28
+ minSize?: number;
29
+ /** Maximum size in percent. Token-aligned default: none (set by consumer). */
30
+ maxSize?: number;
31
+ id?: string;
32
+ collapsible?: boolean;
33
+ collapsedSize?: number;
34
+ /** Called on each resize. Uses the react-resizable-panels v4 signature. */
35
+ onResize?: OnPanelResize;
36
+ className?: string;
37
+ children: ReactNode;
38
+ }
39
+ export declare function ResizableShellPanel({ className, children, onResize, ...props }: ResizableShellPanelProps): import("react/jsx-runtime").JSX.Element;
40
+ export interface ResizableHandleProps {
41
+ className?: string;
42
+ /** When true, renders a visible drag-indicator bar inside the handle. */
43
+ withHandle?: boolean;
44
+ }
45
+ export declare function ResizableHandle({ className, withHandle }: ResizableHandleProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Group, Panel, Separator } from 'react-resizable-panels';
4
+ import { cn } from '../lib/utils.js';
5
+ export function ResizableShell({ orientation = 'horizontal', className, children, }) {
6
+ return (_jsx(Group, { orientation: orientation, className: cn('flex h-full w-full', className), children: children }));
7
+ }
8
+ export function ResizableShellPanel({ className, children, onResize, ...props }) {
9
+ return (_jsx(Panel, { className: cn('h-full', className), onResize: onResize, ...props, children: children }));
10
+ }
11
+ export function ResizableHandle({ className, withHandle = false }) {
12
+ return (_jsx(Separator, { className: cn(
13
+ // 4px wide, transparent until hover/active — then ring-color line
14
+ 'group relative w-1 shrink-0 cursor-col-resize transition-colors', 'bg-transparent hover:bg-ring data-[separator]:hover:bg-ring', 'focus-visible:bg-ring focus-visible:outline-none', className), children: withHandle && (_jsx("div", { "data-testid": "resize-handle-indicator", className: cn('absolute top-1/2 left-1/2 h-6 w-1 -translate-x-1/2 -translate-y-1/2', 'rounded-sm bg-border opacity-0 transition-opacity group-hover:opacity-100') })) }));
15
+ }
@@ -0,0 +1,15 @@
1
+ import type { PanelMode, PanelView } from './types.js';
2
+ export interface RightPanelProps {
3
+ /** Views to render as tabs in the panel header. */
4
+ panelViews?: PanelView[];
5
+ /** Default active view id — set on mount when no activeView is set in context. */
6
+ defaultView?: string;
7
+ /**
8
+ * Which open-modes this consumer allows.
9
+ * Default is all three: ['panel', 'expanded', 'fullscreen'].
10
+ * If only ['panel'], the cycle button is hidden.
11
+ */
12
+ modes?: PanelMode[];
13
+ className?: string;
14
+ }
15
+ export declare function RightPanel({ panelViews, defaultView, modes, className, }: RightPanelProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * RightPanel — spec §12
5
+ *
6
+ * Four-mode right panel: closed (0px), panel (resizable 300–520px),
7
+ * expanded (~60vw), fullscreen (100vw/100vh takeover).
8
+ *
9
+ * Mode state lives in MedaShellProvider (panel.mode / panel.setMode).
10
+ * Width is persisted per-(workspaceId, appId) via panel.width / panel.setWidth.
11
+ *
12
+ * Resize note (Pattern B):
13
+ * Uses its own pointer-event resize handle on the LEFT edge instead of
14
+ * wrapping in <ResizableShell>. Pattern A requires migrating <AppShellBody>
15
+ * to a PanelGroup layout.
16
+ * TODO(phase-15-refactor): swap to ResizableShell once AppShellBody is a PanelGroup
17
+ */
18
+ import { Maximize2, Minimize2, X } from 'lucide-react';
19
+ import { useEffect, useRef, useState } from 'react';
20
+ import { cn } from '../lib/utils.js';
21
+ import { useMedaShell } from './shell-provider.js';
22
+ import { useShellViewport } from './use-shell-viewport.js';
23
+ // ---------------------------------------------------------------------------
24
+ // Constants — spec §12 dimensions
25
+ // ---------------------------------------------------------------------------
26
+ const MIN_WIDTH = 300;
27
+ const MAX_WIDTH = 520;
28
+ function ResizeHandle({ currentWidth, onResize, onCommit }) {
29
+ const startWidthRef = useRef(currentWidth);
30
+ const startXRef = useRef(0);
31
+ const draggingRef = useRef(false);
32
+ const handlePointerDown = (e) => {
33
+ try {
34
+ e.currentTarget.setPointerCapture(e.pointerId);
35
+ }
36
+ catch {
37
+ // ignore — drag still functions via draggingRef
38
+ }
39
+ startWidthRef.current = currentWidth;
40
+ startXRef.current = e.clientX;
41
+ draggingRef.current = true;
42
+ };
43
+ const handlePointerMove = (e) => {
44
+ if (!draggingRef.current)
45
+ return;
46
+ // Left edge: drag left (clientX decreasing) widens panel
47
+ const delta = startXRef.current - e.clientX;
48
+ const next = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidthRef.current + delta));
49
+ onResize(next);
50
+ };
51
+ const handlePointerUp = (e) => {
52
+ if (!draggingRef.current)
53
+ return;
54
+ draggingRef.current = false;
55
+ const delta = startXRef.current - e.clientX;
56
+ const next = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidthRef.current + delta));
57
+ onCommit(next);
58
+ };
59
+ return (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": "Resize panel", "aria-valuenow": currentWidth, "aria-valuemin": MIN_WIDTH, "aria-valuemax": MAX_WIDTH, tabIndex: 0, className: cn('absolute top-0 left-0 h-full w-1 cursor-col-resize', 'opacity-0 transition-opacity hover:opacity-100 hover:bg-ring'), onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp }));
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // RightPanel
63
+ // ---------------------------------------------------------------------------
64
+ export function RightPanel({ panelViews = [], defaultView, modes = ['panel', 'expanded', 'fullscreen'], className, }) {
65
+ const band = useShellViewport();
66
+ const ctx = useMedaShell();
67
+ const { mode, activeView, width, setMode, setActiveView, setWidth } = ctx.panel;
68
+ // Hydrate defaultView on mount if no active view set
69
+ // biome-ignore lint/correctness/useExhaustiveDependencies: intentional one-time mount effect
70
+ useEffect(() => {
71
+ if (!activeView && defaultView) {
72
+ setActiveView(defaultView);
73
+ }
74
+ }, []);
75
+ // Local display width tracks pointer-move updates live;
76
+ // setWidth (persist) is called on pointerUp.
77
+ const [displayWidth, setDisplayWidth] = useState(null);
78
+ const resolvedWidth = displayWidth ?? width;
79
+ // On mobile, the right panel renders as a drawer via <MobileDrawers > PanelsDrawer />.
80
+ // The desktop-shaped <RightPanel> hides; consumers must mount <MobileDrawers> to expose
81
+ // panel views on mobile.
82
+ if (band === 'mobile')
83
+ return null;
84
+ const renderCtx = {
85
+ workspaceId: ctx.workspace.id,
86
+ appId: ctx.activeAppId,
87
+ };
88
+ // Cycle through allowed open modes only (skip 'closed')
89
+ const cycleOpenMode = () => {
90
+ if (mode === 'closed')
91
+ return;
92
+ const openModes = modes.filter((m) => m !== 'closed');
93
+ const idx = openModes.indexOf(mode);
94
+ const next = openModes[(idx + 1) % openModes.length] ?? mode;
95
+ setMode(next);
96
+ };
97
+ // Mode → inline width style
98
+ const widthStyle = (() => {
99
+ switch (mode) {
100
+ case 'closed':
101
+ return { width: 0, pointerEvents: 'none' };
102
+ case 'panel':
103
+ return { width: `${resolvedWidth}px` };
104
+ case 'expanded':
105
+ return { width: '60vw' };
106
+ case 'fullscreen':
107
+ return { width: '100vw' };
108
+ }
109
+ })();
110
+ // Z-index escalation: fullscreen covers header + rails
111
+ const zIndexClass = mode === 'fullscreen' ? 'z-[var(--z-shell-fullscreen)]' : 'z-[var(--z-shell-panel)]';
112
+ const activePanelView = panelViews.find((v) => v.id === activeView);
113
+ const cycleAriaLabel = mode === 'panel' ? 'Expand panel' : mode === 'expanded' ? 'Maximize panel' : 'Restore panel';
114
+ const handleResize = (w) => setDisplayWidth(w);
115
+ const handleCommit = (w) => {
116
+ setDisplayWidth(null);
117
+ setWidth(w);
118
+ };
119
+ return (_jsx("aside", { "data-meda-panel-mode": mode, "aria-hidden": mode === 'closed' ? 'true' : undefined, className: cn('relative h-full shrink-0 overflow-hidden border-l border-shell-border bg-shell-panel', 'transition-[width] ease-[var(--motion-ease)] duration-[var(--motion-panel)]', mode === 'fullscreen' && 'fixed inset-0 h-screen w-screen border-none', zIndexClass, className), style: widthStyle, children: mode !== 'closed' && (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-shell-border px-3 py-2", children: [_jsx("div", { className: "flex items-center gap-1", children: panelViews.map((view) => {
120
+ const isActive = view.id === activeView;
121
+ const Icon = view.icon;
122
+ return (_jsxs("button", { type: "button", "aria-current": isActive ? 'true' : undefined, onClick: () => setActiveView(view.id), className: cn('inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-colors', isActive
123
+ ? 'bg-accent text-accent-foreground'
124
+ : 'text-muted-foreground hover:bg-accent hover:text-foreground'), children: [_jsx(Icon, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: view.label })] }, view.id));
125
+ }) }), _jsxs("div", { className: "flex items-center gap-1", children: [modes.length > 1 && (_jsx("button", { type: "button", "aria-label": cycleAriaLabel, onClick: cycleOpenMode, className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: mode === 'fullscreen' ? (_jsx(Minimize2, { size: 14, "aria-hidden": "true" })) : (_jsx(Maximize2, { size: 14, "aria-hidden": "true" })) })), _jsx("button", { type: "button", "aria-label": "Close panel", onClick: () => setMode('closed'), className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(X, { size: 14, "aria-hidden": "true" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: activePanelView != null ? (activePanelView.render(renderCtx)) : (_jsx("div", { className: "p-4 text-muted-foreground text-sm", children: "No panel view selected" })) }), mode === 'panel' && (_jsx(ResizeHandle, { currentWidth: resolvedWidth, onResize: handleResize, onCommit: handleCommit }))] })) }));
126
+ }
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react';
2
+ export type ShellAuthTheme = 'light' | 'dark';
3
+ export interface ShellAuthFrameProps {
4
+ children: ReactNode;
5
+ title: ReactNode;
6
+ description?: ReactNode;
7
+ brandName?: ReactNode;
8
+ brandMark?: ReactNode;
9
+ eyebrow?: ReactNode;
10
+ preview?: ReactNode;
11
+ actions?: ReactNode;
12
+ className?: string;
13
+ }
14
+ export interface ShellAuthThemeToggleProps {
15
+ value: ShellAuthTheme;
16
+ onValueChange: (value: ShellAuthTheme) => void;
17
+ className?: string;
18
+ lightLabel?: string;
19
+ darkLabel?: string;
20
+ }
21
+ export declare function ShellAuthFrame({ children, title, description, brandName, brandMark, eyebrow, preview, actions, className, }: ShellAuthFrameProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function ShellAuthThemeToggle({ value, onValueChange, className, lightLabel, darkLabel, }: ShellAuthThemeToggleProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { BarChart3, CheckCircle2, Mail, Moon, PanelRightOpen, Search, Send, Sparkles, Sun, UsersRound, } from 'lucide-react';
4
+ function cx(...values) {
5
+ return values.filter(Boolean).join(' ');
6
+ }
7
+ function DefaultBrandMark() {
8
+ return (_jsx("span", { className: "inline-flex size-10 items-center justify-center rounded-lg bg-white/14 text-white ring-1 ring-white/20 shadow-lg shadow-black/20", children: _jsx(Sparkles, { className: "size-5", "aria-hidden": true }) }));
9
+ }
10
+ export function ShellAuthFrame({ children, title, description, brandName = 'Meda', brandMark, eyebrow, preview, actions, className, }) {
11
+ const resolvedBrandMark = brandMark ?? _jsx(DefaultBrandMark, {});
12
+ return (_jsxs("section", { "data-testid": "shell-auth-frame", className: cx('grid min-h-screen overflow-hidden bg-background text-foreground lg:grid-cols-[minmax(0,1.08fr)_minmax(420px,0.92fr)]', className), children: [_jsxs("div", { className: "relative flex min-h-[24rem] flex-col overflow-hidden bg-[radial-gradient(circle_at_24%_18%,var(--color-info-500)_0,transparent_25%),radial-gradient(circle_at_84%_24%,var(--color-brand-400)_0,transparent_28%),linear-gradient(135deg,var(--color-brand-800),var(--color-brand-700)_42%,var(--color-brand-500))] px-6 py-6 text-white sm:min-h-[30rem] sm:px-8 lg:min-h-screen lg:px-10 lg:py-8", children: [_jsx("div", { className: "pointer-events-none absolute inset-0 opacity-[0.07]", style: {
13
+ backgroundImage: "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")",
14
+ } }), _jsxs("div", { className: "relative z-10 flex items-center gap-3", children: [resolvedBrandMark, _jsx("span", { className: "text-lg font-semibold tracking-normal", children: brandName })] }), _jsxs("div", { className: "relative z-10 flex flex-1 flex-col justify-center gap-8 py-10 lg:py-12", children: [_jsxs("div", { className: "max-w-2xl", children: [eyebrow && (_jsx("p", { className: "mb-4 text-sm font-semibold uppercase tracking-[0.12em] text-white/70", children: eyebrow })), _jsx("h1", { className: "text-4xl font-bold leading-tight tracking-normal text-white sm:text-5xl", children: title }), description && (_jsx("p", { className: "mt-4 max-w-xl text-base leading-7 text-white/72 sm:text-lg", children: description }))] }), _jsx("div", { className: "w-full max-w-3xl", children: preview ?? _jsx(DefaultAuthPreview, {}) })] })] }), _jsxs("div", { className: "relative flex min-h-screen flex-col bg-background px-6 py-6 sm:px-8 lg:px-12", children: [actions && _jsx("div", { className: "flex justify-end", children: actions }), _jsxs("div", { className: "mx-auto flex w-full max-w-md flex-1 flex-col justify-center py-10", children: [_jsx("div", { className: "mb-8 flex justify-center lg:hidden", children: resolvedBrandMark }), children] })] })] }));
15
+ }
16
+ export function ShellAuthThemeToggle({ value, onValueChange, className, lightLabel = 'Light theme', darkLabel = 'Dark theme', }) {
17
+ return (_jsxs("fieldset", { className: cx('inline-flex items-center gap-1 rounded-md border border-border bg-card p-1 text-card-foreground shadow-sm', className), children: [_jsx("legend", { className: "sr-only", children: "Theme" }), _jsx("button", { type: "button", "aria-label": lightLabel, "aria-pressed": value === 'light', title: lightLabel, onClick: () => onValueChange('light'), className: themeButtonClass(value === 'light'), children: _jsx(Sun, { className: "size-4", "aria-hidden": true }) }), _jsx("button", { type: "button", "aria-label": darkLabel, "aria-pressed": value === 'dark', title: darkLabel, onClick: () => onValueChange('dark'), className: themeButtonClass(value === 'dark'), children: _jsx(Moon, { className: "size-4", "aria-hidden": true }) })] }));
18
+ }
19
+ function themeButtonClass(active) {
20
+ return cx('inline-flex size-8 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', active
21
+ ? 'bg-primary text-primary-foreground shadow-sm'
22
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground');
23
+ }
24
+ function DefaultAuthPreview() {
25
+ return (_jsx("div", { "data-testid": "shell-auth-default-preview", className: "relative overflow-hidden rounded-[1.5rem] border border-white/16 bg-[color-mix(in_srgb,var(--background)_88%,transparent)] p-2 text-foreground shadow-2xl shadow-black/30 backdrop-blur", "aria-hidden": true, children: _jsxs("div", { className: "overflow-hidden rounded-[1.125rem] border border-border/70 bg-background shadow-sm", children: [_jsxs("div", { className: "flex h-9 items-center justify-between bg-card/90 px-3", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "size-2 rounded-full bg-rose-400" }), _jsx("span", { className: "size-2 rounded-full bg-amber-400" }), _jsx("span", { className: "size-2 rounded-full bg-emerald-400" })] }), _jsx("span", { className: "hidden rounded-full bg-muted px-2 py-0.5 text-[9px] font-medium text-muted-foreground sm:inline-flex", children: "app.medal.social" })] }), _jsxs("div", { "data-testid": "shell-auth-preview-shell-header", className: "flex h-12 items-center justify-between border-shell-border border-t border-b bg-shell-header px-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "inline-flex size-7 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground", children: _jsx(Sparkles, { className: "size-3.5", "aria-hidden": "true" }) }), _jsx("span", { className: "truncate text-[11px] font-semibold", children: "Medal Social" }), _jsx("div", { className: "ml-2 hidden items-center gap-0.5 rounded-full bg-muted/70 p-0.5 sm:flex", children: PREVIEW_TABS.map((tab) => (_jsx("span", { className: cx('rounded-full px-2 py-1 text-[10px] font-medium', tab === 'Inbox'
26
+ ? 'bg-background text-foreground shadow-sm'
27
+ : 'text-muted-foreground'), children: tab }, tab))) })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "hidden rounded-lg bg-primary px-2.5 py-1.5 text-[9px] font-semibold text-primary-foreground sm:inline-flex", children: "+ New" }), _jsx("span", { "data-testid": "shell-auth-preview-panel-toggle", className: "inline-flex size-8 items-center justify-center rounded-lg border border-border bg-card text-muted-foreground shadow-sm", children: _jsx(PanelRightOpen, { className: "size-3.5", "aria-hidden": "true" }) })] })] }), _jsxs("div", { className: "grid min-h-[19.75rem] grid-cols-[44px_minmax(0,1fr)] gap-2 bg-shell-main p-2 sm:grid-cols-[48px_138px_minmax(0,1fr)] lg:grid-cols-[52px_156px_minmax(0,1fr)]", children: [_jsxs("aside", { "data-testid": "shell-auth-preview-icon-rail", className: "flex flex-col items-center gap-1 rounded-2xl bg-shell-rail p-1.5 shadow-sm ring-1 ring-border/70", children: [PREVIEW_RAIL_ITEMS.map((item) => (_jsx(PreviewRailItem, { icon: item.icon, label: item.label, active: item.active, badge: item.badge }, item.label))), _jsx("span", { className: "mt-auto inline-flex size-8 items-center justify-center rounded-lg bg-muted text-[10px] font-semibold text-muted-foreground", children: "AS" })] }), _jsxs("aside", { "data-testid": "shell-auth-preview-context-rail", className: "hidden overflow-hidden rounded-2xl bg-shell-context shadow-sm ring-1 ring-border/70 sm:flex sm:flex-col", children: [_jsxs("div", { className: "border-shell-border border-b px-3 py-3", children: [_jsx("p", { className: "text-[11px] font-semibold text-foreground", children: "Customer OS" }), _jsx("p", { className: "mt-0.5 truncate text-[9px] text-muted-foreground", children: "Auto-CS workspace" })] }), _jsx("nav", { className: "flex flex-col gap-0.5 p-2", children: PREVIEW_CONTEXT_ITEMS.map((item) => (_jsx(PreviewContextItem, { ...item }, item.label))) }), _jsxs("div", { className: "mt-auto border-shell-border border-t p-2", children: [_jsx("p", { className: "text-[9px] font-medium text-muted-foreground", children: "Next sync" }), _jsx("p", { className: "mt-1 truncate text-[10px] font-semibold text-foreground", children: "12 campaigns" })] })] }), _jsxs("main", { className: "min-w-0 overflow-hidden rounded-2xl bg-background/80 p-3 shadow-sm ring-1 ring-border/70", children: [_jsxs("div", { className: "mb-3 flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-[9px] font-medium uppercase text-muted-foreground", children: "Inbox command center" }), _jsx("p", { className: "mt-1 truncate text-sm font-semibold", children: "Launch queue and support" })] }), _jsx("span", { className: "rounded-full bg-success/12 px-2 py-1 text-[9px] font-semibold text-success", children: "Live" })] }), _jsxs("div", { className: "mb-3 flex h-9 items-center gap-2 rounded-xl border border-border bg-card px-2.5 text-muted-foreground shadow-sm", children: [_jsx(Search, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "truncate text-[10px]", children: "Ask Meda to draft a customer reply..." }), _jsx("kbd", { className: "ml-auto hidden rounded-md bg-muted px-1.5 py-0.5 font-mono text-[8px] text-muted-foreground sm:inline", children: "CMD K" })] }), _jsx("div", { className: "grid gap-2 sm:grid-cols-3", children: PREVIEW_METRICS.map((metric) => (_jsxs("div", { className: "rounded-xl border border-border bg-card p-2.5", children: [_jsx("p", { className: "text-[9px] font-medium text-muted-foreground", children: metric.label }), _jsx("p", { className: "mt-1 text-base font-semibold", children: metric.value })] }, metric.label))) }), _jsxs("div", { className: "mt-3 grid gap-2 lg:grid-cols-[minmax(0,1fr)_112px]", children: [_jsxs("div", { className: "rounded-xl border border-border bg-card p-2.5", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("p", { className: "text-[11px] font-semibold", children: "Priority work" }), _jsx("p", { className: "text-[9px] text-muted-foreground", children: "Updated now" })] }), _jsx("div", { className: "space-y-2", children: PREVIEW_ACTIVITY.map((item) => (_jsx(PreviewActivityRow, { label: item }, item))) })] }), _jsxs("div", { className: "hidden rounded-xl border border-border bg-card p-2.5 lg:block", children: [_jsx("p", { className: "text-[10px] font-semibold", children: "Publish health" }), _jsx("div", { className: "mt-3 h-1.5 overflow-hidden rounded-full bg-muted", children: _jsx("div", { className: "h-full w-[72%] rounded-full bg-primary" }) }), _jsx("p", { className: "mt-2 text-[9px] leading-4 text-muted-foreground", children: "Response SLA and scheduled posts are on track." })] })] })] })] })] }) }));
28
+ }
29
+ function PreviewRailItem({ icon: Icon, label, active, badge, }) {
30
+ return (_jsxs("span", { className: cx('relative inline-flex size-8 items-center justify-center rounded-lg transition-colors', active ? 'bg-primary/12 text-primary' : 'text-muted-foreground'), title: label, children: [_jsx(Icon, { className: "size-4", "aria-hidden": "true" }), badge && _jsx("span", { className: "absolute top-0.5 right-0.5 size-1.5 rounded-full bg-primary" })] }));
31
+ }
32
+ function PreviewContextItem({ icon: Icon, label, active, shortcut, }) {
33
+ return (_jsxs("span", { className: cx('flex items-center gap-2 rounded-md px-2 py-1.5 text-[10px] transition-colors', active ? 'bg-primary/10 font-semibold text-primary' : 'text-muted-foreground'), children: [_jsx(Icon, { className: "size-3.5 shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: label }), shortcut && (_jsx("kbd", { className: "ml-auto font-mono text-[8px] text-muted-foreground", children: shortcut }))] }));
34
+ }
35
+ function PreviewActivityRow({ label }) {
36
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary", children: _jsx(CheckCircle2, { className: "size-3", "aria-hidden": "true" }) }), _jsx("p", { className: "truncate text-[10px] text-muted-foreground", children: label })] }));
37
+ }
38
+ const PREVIEW_TABS = ['Home', 'Inbox', 'Campaigns'];
39
+ const PREVIEW_RAIL_ITEMS = [
40
+ { icon: BarChart3, label: 'Overview' },
41
+ { icon: Mail, label: 'Inbox', active: true, badge: '4' },
42
+ { icon: Send, label: 'Posts' },
43
+ { icon: UsersRound, label: 'Contacts' },
44
+ ];
45
+ const PREVIEW_CONTEXT_ITEMS = [
46
+ { icon: BarChart3, label: 'Overview', shortcut: '1' },
47
+ { icon: Mail, label: 'Inbox', active: true, shortcut: '2' },
48
+ { icon: Send, label: 'Posts', shortcut: '3' },
49
+ { icon: UsersRound, label: 'Contacts', shortcut: '4' },
50
+ ];
51
+ const PREVIEW_METRICS = [
52
+ { label: 'Queued posts', value: '18' },
53
+ { label: 'Open tickets', value: '42' },
54
+ { label: 'Reach', value: '9.8k' },
55
+ ];
56
+ const PREVIEW_ACTIVITY = [
57
+ 'Campaign approved for publishing',
58
+ 'Lead segment synced with CRM',
59
+ 'Automated reply drafted for review',
60
+ ];
@@ -1,11 +1,12 @@
1
1
  import type { ReactNode } from 'react';
2
- export declare function ShellHeaderFrame({ left, center, right, className, }: {
3
- left: ReactNode;
4
- center?: ReactNode;
5
- right?: ReactNode;
2
+ export interface WorkspaceSwitcherProps {
3
+ workspaceMenuFooter?: ReactNode;
4
+ }
5
+ export declare function WorkspaceSwitcher({ workspaceMenuFooter }?: WorkspaceSwitcherProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function AppTabs(): import("react/jsx-runtime").JSX.Element;
7
+ export declare function PanelToggle(): import("react/jsx-runtime").JSX.Element;
8
+ export interface ShellHeaderProps {
9
+ globalActions?: ReactNode;
6
10
  className?: string;
7
- }): import("react/jsx-runtime").JSX.Element;
8
- export declare function ShellPanelToggle({ panelOpen, onToggle, }: {
9
- panelOpen: boolean;
10
- onToggle: () => void;
11
- }): import("react/jsx-runtime").JSX.Element;
11
+ }
12
+ export declare function ShellHeader({ globalActions, className }?: ShellHeaderProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,15 +1,58 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { PanelRightClose, PanelRightOpen } from 'lucide-react';
3
- export function ShellHeaderFrame({ left, center, right, className, }) {
4
- return (_jsxs("header", { className: [
5
- 'relative z-[var(--z-sticky)] grid h-[var(--header-height)] w-full grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 bg-[var(--header)] px-3 shadow-[var(--shadow-header)] sm:px-4',
6
- className,
7
- ]
8
- .filter(Boolean)
9
- .join(' '), children: [left, _jsx("div", { className: "min-w-0", children: center }), _jsx("div", { "data-testid": "shell-header-actions", className: "hidden items-center justify-end gap-2 md:flex", children: right })] }));
10
- }
11
- export function ShellPanelToggle({ panelOpen, onToggle, }) {
12
- return (_jsx("button", { type: "button", "aria-label": panelOpen ? 'Close panel' : 'Open panel', onClick: onToggle, className: panelOpen
13
- ? 'inline-flex h-9 items-center justify-center rounded-xl border border-[var(--primary)]/30 bg-[var(--primary)]/12 px-2.5 text-[var(--primary)] transition-[background-color,color,border-color] duration-200 hover:bg-[var(--primary)]/18'
14
- : 'inline-flex h-9 items-center justify-center rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-1)] px-2.5 text-[var(--muted-foreground)] transition-[background-color,color,border-color] duration-200 hover:bg-[var(--sidebar-accent)] hover:text-[var(--foreground)]', children: panelOpen ? _jsx(PanelRightClose, { size: 18 }) : _jsx(PanelRightOpen, { size: 18 }) }));
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { ChevronDown, Monitor, Moon, PanelRightClose, PanelRightOpen, Sun } from 'lucide-react';
4
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../components/ui/dropdown-menu.js';
5
+ import { cn } from '../lib/utils.js';
6
+ import { useMedaShell } from './shell-provider.js';
7
+ import { useTheme } from './theme.js';
8
+ import { useShellViewport } from './use-shell-viewport.js';
9
+ const NEXT_THEME = { light: 'dark', dark: 'system', system: 'light' };
10
+ const THEME_ICON = { light: Sun, dark: Moon, system: Monitor };
11
+ const THEME_LABEL = {
12
+ light: 'Switch to dark theme',
13
+ dark: 'Switch to system theme',
14
+ system: 'Switch to light theme',
15
+ };
16
+ function ThemeToggleMenuItem() {
17
+ const { theme, setTheme } = useTheme();
18
+ const Icon = THEME_ICON[theme];
19
+ return (_jsxs(DropdownMenuItem, { onClick: () => setTheme(NEXT_THEME[theme]), children: [_jsx(Icon, { size: 16, "aria-hidden": "true" }), THEME_LABEL[theme]] }));
20
+ }
21
+ export function WorkspaceSwitcher({ workspaceMenuFooter } = {}) {
22
+ const { workspace, workspaces } = useMedaShell();
23
+ return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx("button", { type: "button", className: "flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm font-medium hover:bg-accent" }), children: [workspace.icon != null && (_jsx("span", { className: "shrink-0", "aria-hidden": "true", children: workspace.icon })), _jsx("span", { children: workspace.name }), _jsx(ChevronDown, { size: 14, "aria-hidden": "true" })] }), _jsxs(DropdownMenuContent, { className: "min-w-[200px]", children: [workspaces.length > 0 && (_jsxs(_Fragment, { children: [workspaces.map((ws) => (_jsxs(DropdownMenuItem, { children: [ws.icon != null && _jsx("span", { "aria-hidden": "true", children: ws.icon }), ws.name] }, ws.id))), _jsx(DropdownMenuSeparator, {})] })), _jsx(DropdownMenuItem, { children: "Manage workspaces" }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { children: "Settings" }), _jsx(DropdownMenuItem, { children: "Profile" }), _jsx(DropdownMenuSeparator, {}), _jsx(ThemeToggleMenuItem, {}), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { children: "Sign out" }), workspaceMenuFooter] })] }));
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // AppTabs
27
+ // ---------------------------------------------------------------------------
28
+ export function AppTabs() {
29
+ const { apps, activeAppId, setActiveApp } = useMedaShell();
30
+ return (_jsx("nav", { "aria-label": "Applications", className: "flex items-center", children: apps.map((app) => {
31
+ const isActive = app.id === activeAppId;
32
+ const Icon = app.icon;
33
+ return (_jsxs("button", { type: "button", "aria-current": isActive ? 'page' : undefined, onClick: () => {
34
+ setActiveApp(app.id);
35
+ // TODO(Phase 18.x): renderLink integration so clicking a tab also navigates
36
+ // the consumer's router; setActiveApp alone updates context.
37
+ }, onMouseEnter: () => { }, className: cn('flex items-center gap-1.5 px-3 py-2 text-sm font-medium transition-colors', isActive
38
+ ? 'border-b-2 border-primary text-foreground'
39
+ : 'text-muted-foreground hover:text-foreground'), children: [_jsx(Icon, { size: 16, "aria-hidden": "true" }), app.label] }, app.id));
40
+ }) }));
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // PanelToggle
44
+ // ---------------------------------------------------------------------------
45
+ export function PanelToggle() {
46
+ const { panel } = useMedaShell();
47
+ const isOpen = panel.mode !== 'closed';
48
+ const handleClick = () => {
49
+ panel.setMode(isOpen ? 'closed' : 'panel');
50
+ };
51
+ return (_jsx("button", { type: "button", "aria-label": isOpen ? 'Close right panel' : 'Open right panel', onClick: handleClick, className: cn('inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors', isOpen ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-accent'), children: isOpen ? (_jsx(PanelRightClose, { size: 18, "aria-hidden": "true" })) : (_jsx(PanelRightOpen, { size: 18, "aria-hidden": "true" })) }));
52
+ }
53
+ export function ShellHeader({ globalActions, className } = {}) {
54
+ const band = useShellViewport();
55
+ if (band === 'mobile')
56
+ return null;
57
+ return (_jsxs("header", { className: cn('flex h-[var(--shell-header-height)] w-full items-center justify-between', 'border-b border-border bg-background px-3', className), children: [_jsxs("div", { className: "flex items-center", children: [_jsx(WorkspaceSwitcher, {}), _jsx(AppTabs, {})] }), _jsxs("div", { className: "flex items-center gap-2", children: [globalActions, _jsx(PanelToggle, {})] })] }));
15
58
  }
@@ -1,5 +1,5 @@
1
- import type { ShellContentLayout, ShellViewportBand } from './types.js';
2
- export declare function getShellContentMaxWidth(layout: ShellContentLayout, viewportBand: ShellViewportBand): 1120 | 1280 | 1400 | 1440 | 1760 | 1920 | undefined;
1
+ import type { ShellContentLayout, ShellViewportBand } from './extras/types.js';
2
+ export declare function getShellContentMaxWidth(layout: ShellContentLayout, viewportBand: ShellViewportBand): 1440 | 1760 | 1920 | 1120 | 1280 | 1400 | undefined;
3
3
  export declare function getResolvedShellPanelWidth({ preferredWidth, viewportWidth, sidebarOpen, sidebarWidth, }: {
4
4
  preferredWidth: number;
5
5
  viewportWidth: number;
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ShellMainLayout } from './types.js';
3
+ export interface ShellMainProps {
4
+ layout?: ShellMainLayout;
5
+ className?: string;
6
+ children: ReactNode;
7
+ }
8
+ export declare function ShellMain({ layout, className, children }: ShellMainProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cn } from '../lib/utils.js';
4
+ const layoutClass = {
5
+ workspace: 'mx-auto w-full max-w-[1280px] px-4 py-6 sm:px-6 lg:px-8',
6
+ centered: 'mx-auto w-full max-w-2xl px-4 py-6 sm:px-6',
7
+ fullbleed: 'w-full',
8
+ };
9
+ export function ShellMain({ layout = 'workspace', className, children }) {
10
+ return (_jsx("main", { "data-meda-shell-main-layout": layout, className: cn('flex-1 min-w-0 overflow-y-auto bg-shell-main', '[content-visibility:auto]', layoutClass[layout], className), children: children }));
11
+ }
@@ -0,0 +1,65 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type ShellStorageAdapter } from './layout-state.js';
3
+ import type { AppDefinition, MobileBottomNavItem, PanelMode, ThemeAdapter, WorkspaceDefinition } from './types.js';
4
+ export type MobileDrawerKind = 'menu-drawer' | 'module-drawer' | 'panels-drawer' | 'ai-drawer' | (string & {}) | null;
5
+ interface MedaShellContextValue {
6
+ workspace: WorkspaceDefinition;
7
+ workspaces: WorkspaceDefinition[];
8
+ apps: AppDefinition[];
9
+ activeAppId: string;
10
+ setActiveApp: (id: string) => void;
11
+ panel: {
12
+ mode: PanelMode;
13
+ activeView: string | null;
14
+ width: number;
15
+ setMode: (m: PanelMode) => void;
16
+ setActiveView: (v: string | null) => void;
17
+ setWidth: (w: number) => void;
18
+ /** Opens panel + switches to viewId in one call.
19
+ * Sugar for app keyboard shortcuts (e.g. Cmd+J → panel.focus('ai')).
20
+ * If already open in 'panel', 'expanded', or 'fullscreen', the existing
21
+ * mode is preserved — only flips closed → panel. */
22
+ focus: (viewId: string) => void;
23
+ };
24
+ contextRail: {
25
+ width: number;
26
+ collapsed: boolean;
27
+ setWidth: (w: number) => void;
28
+ setCollapsed: (c: boolean) => void;
29
+ };
30
+ mobileBottomNav: MobileBottomNavItem[];
31
+ mobileDrawer: {
32
+ open: MobileDrawerKind;
33
+ setOpen: (kind: MobileDrawerKind) => void;
34
+ };
35
+ commandPalette: {
36
+ open: boolean;
37
+ setOpen: (open: boolean) => void;
38
+ };
39
+ commandPaletteHotkey: string;
40
+ /** Selection bridge between main workspace and right panel views (spec §17). */
41
+ selection: unknown | null;
42
+ setSelection: (value: unknown | null) => void;
43
+ }
44
+ export declare function useMedaShell(): MedaShellContextValue;
45
+ /**
46
+ * Selection bridge between main workspace and right panel views (spec §17).
47
+ *
48
+ * `T` is consumer-typed: setSelection writes whatever shape you pass; the
49
+ * matching getter narrows to `T | null`. The shell does not validate the
50
+ * shape — coordinate types between writer and readers in your app.
51
+ */
52
+ export declare function useShellSelection<T>(): readonly [T | null, (value: T | null) => void];
53
+ export interface MedaShellProviderProps {
54
+ workspace: WorkspaceDefinition;
55
+ workspaces?: WorkspaceDefinition[];
56
+ apps: AppDefinition[];
57
+ defaultActiveApp?: string;
58
+ storage?: ShellStorageAdapter;
59
+ mobileBottomNav?: MobileBottomNavItem[];
60
+ commandPaletteHotkey?: string;
61
+ themeAdapter?: 'default' | 'next-themes' | ThemeAdapter;
62
+ children: ReactNode;
63
+ }
64
+ export declare function MedaShellProvider(props: MedaShellProviderProps): import("react/jsx-runtime").JSX.Element;
65
+ export {};