@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,29 @@
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import type { ReactNode } from 'react';
3
+ export interface IconRailItem {
4
+ id: string;
5
+ label: string;
6
+ icon: LucideIcon;
7
+ to: string;
8
+ badge?: ReactNode;
9
+ }
10
+ export interface IconRailRenderLinkArgs {
11
+ item: IconRailItem;
12
+ isActive: boolean;
13
+ className: string;
14
+ children: ReactNode;
15
+ }
16
+ export interface IconRailProps {
17
+ mainItems: IconRailItem[];
18
+ utilityItems?: IconRailItem[];
19
+ footer?: ReactNode;
20
+ activeId?: string;
21
+ renderLink?: (args: IconRailRenderLinkArgs) => ReactNode;
22
+ className?: string;
23
+ }
24
+ export interface RailDividerProps {
25
+ pinnedBottom: boolean;
26
+ onToggle: () => void;
27
+ }
28
+ export declare function RailDivider({ pinnedBottom, onToggle }: RailDividerProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function IconRail({ mainItems, utilityItems, footer, activeId, renderLink, className, }: IconRailProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronDown, ChevronUp } from 'lucide-react';
4
+ import { useState } from 'react';
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../components/ui/tooltip.js';
6
+ import { cn } from '../lib/utils.js';
7
+ import { useShellViewport } from './use-shell-viewport.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Item styling
10
+ // ---------------------------------------------------------------------------
11
+ function itemClass(isActive) {
12
+ return cn('group relative flex h-11 w-11 items-center justify-center rounded-xl transition-colors', isActive
13
+ ? 'bg-primary/12 text-primary'
14
+ : 'text-muted-foreground hover:bg-accent hover:text-foreground');
15
+ }
16
+ export function RailDivider({ pinnedBottom, onToggle }) {
17
+ return (_jsx("button", { type: "button", "data-testid": "rail-divider", onClick: onToggle, "aria-label": pinnedBottom ? 'Pull utility items up' : 'Push utility items down', className: cn('my-3 flex h-6 w-8 items-center justify-center rounded-md', 'text-muted-foreground hover:bg-accent hover:text-foreground transition-colors'), children: pinnedBottom ? (_jsx(ChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(ChevronDown, { size: 14, "aria-hidden": "true" })) }));
18
+ }
19
+ // ---------------------------------------------------------------------------
20
+ // IconRail
21
+ // ---------------------------------------------------------------------------
22
+ export function IconRail({ mainItems, utilityItems = [], footer, activeId, renderLink, className, }) {
23
+ const band = useShellViewport();
24
+ const [pinnedBottom, setPinnedBottom] = useState(true);
25
+ if (band === 'mobile')
26
+ return null;
27
+ const renderItem = (item) => {
28
+ const isActive = item.id === activeId;
29
+ const klass = itemClass(isActive);
30
+ const IconComp = item.icon;
31
+ const inner = (_jsxs(_Fragment, { children: [_jsx(IconComp, { size: 22, "aria-hidden": "true" }), item.badge ? _jsx("span", { className: "absolute right-1 top-1", children: item.badge }) : null] }));
32
+ const linkContent = renderLink ? (
33
+ // renderLink consumers receive the className so they can apply it themselves
34
+ renderLink({ item, isActive, className: klass, children: inner })) : (_jsx("a", { href: item.to, "aria-label": item.label, "aria-current": isActive ? 'page' : undefined, className: "contents", children: inner }));
35
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("span", { "data-testid": `icon-rail-trigger-${item.id}`, className: klass, children: linkContent }) }), _jsx(TooltipContent, { side: "right", children: item.label })] }, item.id));
36
+ };
37
+ return (_jsx(TooltipProvider, { children: _jsxs("nav", { "aria-label": "Primary", className: cn('flex h-full w-[var(--shell-rail-width)] shrink-0 flex-col items-center bg-shell-rail py-3.5', className), children: [_jsx("div", { className: "flex flex-col items-center gap-1", children: mainItems.map(renderItem) }), utilityItems.length > 0 && (_jsxs(_Fragment, { children: [_jsx(RailDivider, { pinnedBottom: pinnedBottom, onToggle: () => setPinnedBottom((prev) => !prev) }), _jsx("div", { "data-testid": "utility-items-wrapper", className: cn('flex flex-col items-center gap-1', pinnedBottom && 'mt-auto'), children: utilityItems.map(renderItem) })] })), footer && (_jsx("div", { className: cn('pt-3', pinnedBottom || utilityItems.length === 0 ? 'mt-auto' : ''), children: footer }))] }) }));
38
+ }
@@ -1,15 +1,24 @@
1
- export * from './navigation-area.js';
2
- export * from './shell-app-rail.js';
3
- export * from './shell-desktop-panel-dock.js';
4
- export * from './shell-frame.js';
5
- export * from './shell-header.js';
6
- export * from './shell-layout-utils.js';
7
- export * from './shell-module-nav.js';
8
- export * from './shell-panel-rail.js';
9
- export * from './shell-route-utils.js';
10
- export * from './shell-scrollable-content.js';
11
- export * from './shell-state.js';
12
- export * from './shell-tab-bar.js';
13
- export * from './types.js';
14
- export * from './utils.js';
15
- export * from './workbench-layout.js';
1
+ export { AppShell, AppShellBody } from './app-shell.js';
2
+ export type { CommandGroupDefinition } from './command-palette.js';
3
+ export { CommandPalette, useCommandGroup, useCommands } from './command-palette.js';
4
+ export { ContextRail } from './context-rail.js';
5
+ export * as Extras from './extras/index.js';
6
+ export { IconRail, RailDivider } from './icon-rail.js';
7
+ export type { ShellStorageAdapter } from './layout-state.js';
8
+ export { createLocalStorageAdapter } from './layout-state.js';
9
+ export { MobileBottomNav } from './mobile/mobile-bottom-nav.js';
10
+ export { MobileDrawers } from './mobile/mobile-drawers.js';
11
+ export { MobileHeader } from './mobile/mobile-header.js';
12
+ export { motion } from './motion.js';
13
+ export { ResizableHandle, ResizableShell, ResizableShellPanel } from './resizable-shell.js';
14
+ export { RightPanel } from './right-panel.js';
15
+ export type { ShellAuthFrameProps, ShellAuthTheme, ShellAuthThemeToggleProps, } from './shell-auth-frame.js';
16
+ export { ShellAuthFrame, ShellAuthThemeToggle } from './shell-auth-frame.js';
17
+ export { AppTabs, PanelToggle, ShellHeader, WorkspaceSwitcher } from './shell-header.js';
18
+ export { ShellMain } from './shell-main.js';
19
+ export type { MedaShellProviderProps } from './shell-provider.js';
20
+ export { MedaShellProvider, useMedaShell, useShellSelection } from './shell-provider.js';
21
+ export { DefaultThemeProvider, ThemeToggle, useTheme } from './theme.js';
22
+ export { NextThemesAdapter } from './theme-next-themes.js';
23
+ export type { AppDefinition, CommandDefinition, ContextItem, ContextModule, MobileBottomNavItem, PanelMode, PanelView, ShellLinkRenderArgs, ShellMainLayout, ShellRenderContext, ShellViewport, ThemeAdapter, WorkspaceDefinition, } from './types.js';
24
+ export { useShellViewport } from './use-shell-viewport.js';
@@ -1,15 +1,35 @@
1
- export * from './navigation-area.js';
2
- export * from './shell-app-rail.js';
3
- export * from './shell-desktop-panel-dock.js';
4
- export * from './shell-frame.js';
5
- export * from './shell-header.js';
6
- export * from './shell-layout-utils.js';
7
- export * from './shell-module-nav.js';
8
- export * from './shell-panel-rail.js';
9
- export * from './shell-route-utils.js';
10
- export * from './shell-scrollable-content.js';
11
- export * from './shell-state.js';
12
- export * from './shell-tab-bar.js';
13
- export * from './types.js';
14
- export * from './utils.js';
15
- export * from './workbench-layout.js';
1
+ // Note: NO 'use client' at the top of this barrel.
2
+ // Next.js 14+'s flight loader rejects `export * as Foo` inside a file with
3
+ // the directive (see test/nextjs-consumer.test.ts). Each underlying component
4
+ // file carries its own 'use client' — Next traces through the barrel and
5
+ // applies them per-component. The barrel itself is just a re-exporter.
6
+ // Layout
7
+ export { AppShell, AppShellBody } from './app-shell.js';
8
+ // Command palette
9
+ export { CommandPalette, useCommandGroup, useCommands } from './command-palette.js';
10
+ export { ContextRail } from './context-rail.js';
11
+ // Extras (legacy components ported during Phase 15 — opt-in for apps that need them)
12
+ export * as Extras from './extras/index.js';
13
+ // Rails + main + panel
14
+ export { IconRail, RailDivider } from './icon-rail.js';
15
+ // Storage adapter (consumers may want to provide their own)
16
+ export { createLocalStorageAdapter } from './layout-state.js';
17
+ export { MobileBottomNav } from './mobile/mobile-bottom-nav.js';
18
+ export { MobileDrawers } from './mobile/mobile-drawers.js';
19
+ // Mobile (individual exports + namespaced fallback)
20
+ export { MobileHeader } from './mobile/mobile-header.js';
21
+ // Hooks + tokens
22
+ export { motion } from './motion.js';
23
+ // Resize primitives
24
+ export { ResizableHandle, ResizableShell, ResizableShellPanel } from './resizable-shell.js';
25
+ export { RightPanel } from './right-panel.js';
26
+ export { ShellAuthFrame, ShellAuthThemeToggle } from './shell-auth-frame.js';
27
+ // Header (and its individual children for advanced composition)
28
+ export { AppTabs, PanelToggle, ShellHeader, WorkspaceSwitcher } from './shell-header.js';
29
+ export { ShellMain } from './shell-main.js';
30
+ // Provider + hooks
31
+ export { MedaShellProvider, useMedaShell, useShellSelection } from './shell-provider.js';
32
+ // Theme
33
+ export { DefaultThemeProvider, ThemeToggle, useTheme } from './theme.js';
34
+ export { NextThemesAdapter } from './theme-next-themes.js';
35
+ export { useShellViewport } from './use-shell-viewport.js';
@@ -0,0 +1,32 @@
1
+ import type { PanelMode } from './types.js';
2
+ export interface ShellStorageAdapter {
3
+ load: (key: string) => unknown;
4
+ save: (key: string, value: unknown) => void;
5
+ }
6
+ export declare function createLocalStorageAdapter(): ShellStorageAdapter;
7
+ export interface ShellLayoutState {
8
+ contextRail: {
9
+ width: number;
10
+ collapsed: boolean;
11
+ };
12
+ rightPanel: {
13
+ mode: PanelMode;
14
+ activeView: string | null;
15
+ width: number;
16
+ };
17
+ }
18
+ export type ShellLayoutStateUpdater = ShellLayoutState | ((prev: ShellLayoutState) => ShellLayoutState);
19
+ interface UseShellLayoutStateArgs {
20
+ workspaceId: string;
21
+ appId: string;
22
+ storage: ShellStorageAdapter;
23
+ }
24
+ /**
25
+ * Per-(workspaceId, appId) layout state with localStorage persistence.
26
+ *
27
+ * The `storage` adapter must be referentially stable across renders (typically
28
+ * memoized once at the provider level). An inline-constructed adapter would
29
+ * loop the hydration effect and clobber local mutations.
30
+ */
31
+ export declare function useShellLayoutState({ workspaceId, appId, storage, }: UseShellLayoutStateArgs): readonly [ShellLayoutState, (next: ShellLayoutStateUpdater) => void];
32
+ export {};
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ export function createLocalStorageAdapter() {
4
+ let timer = null;
5
+ let pending = {};
6
+ // Single shared timer across keys: the debounce window resets on every
7
+ // save() and flushes all pending keys together. Intentional — layout
8
+ // updates tend to arrive in bursts (rail width + panel mode at once).
9
+ return {
10
+ load: (key) => {
11
+ if (typeof window === 'undefined')
12
+ return null;
13
+ try {
14
+ const raw = window.localStorage.getItem(key);
15
+ return raw ? JSON.parse(raw) : null;
16
+ }
17
+ catch {
18
+ /* swallow quota / parse errors */
19
+ return null;
20
+ }
21
+ },
22
+ save: (key, value) => {
23
+ if (typeof window === 'undefined')
24
+ return;
25
+ pending[key] = value;
26
+ if (timer)
27
+ clearTimeout(timer);
28
+ timer = setTimeout(() => {
29
+ for (const [k, v] of Object.entries(pending)) {
30
+ try {
31
+ window.localStorage.setItem(k, JSON.stringify(v));
32
+ }
33
+ catch {
34
+ /* swallow quota errors */
35
+ }
36
+ }
37
+ pending = {};
38
+ }, 200);
39
+ },
40
+ };
41
+ }
42
+ const DEFAULTS = {
43
+ contextRail: { width: 300, collapsed: false },
44
+ rightPanel: { mode: 'closed', activeView: null, width: 340 },
45
+ };
46
+ function isShellLayoutState(value) {
47
+ if (!value || typeof value !== 'object')
48
+ return false;
49
+ const v = value;
50
+ if (!v.contextRail ||
51
+ typeof v.contextRail.width !== 'number' ||
52
+ typeof v.contextRail.collapsed !== 'boolean') {
53
+ return false;
54
+ }
55
+ if (!v.rightPanel || typeof v.rightPanel.width !== 'number')
56
+ return false;
57
+ const validModes = ['closed', 'panel', 'expanded', 'fullscreen'];
58
+ if (!validModes.includes(v.rightPanel.mode))
59
+ return false;
60
+ if (v.rightPanel.activeView !== null && typeof v.rightPanel.activeView !== 'string')
61
+ return false;
62
+ return true;
63
+ }
64
+ /**
65
+ * Per-(workspaceId, appId) layout state with localStorage persistence.
66
+ *
67
+ * The `storage` adapter must be referentially stable across renders (typically
68
+ * memoized once at the provider level). An inline-constructed adapter would
69
+ * loop the hydration effect and clobber local mutations.
70
+ */
71
+ export function useShellLayoutState({ workspaceId, appId, storage, }) {
72
+ const key = `meda:shell:${workspaceId}:${appId}`;
73
+ const [state, setStateInternal] = useState(DEFAULTS);
74
+ const [persistRevision, setPersistRevision] = useState(0);
75
+ const persistedRevisionRef = useRef(0);
76
+ const pendingSaveKeyRef = useRef(key);
77
+ // Hydrate after mount. Effect re-runs when key changes (workspace/app switch).
78
+ useEffect(() => {
79
+ const stored = storage.load(key);
80
+ if (isShellLayoutState(stored)) {
81
+ setStateInternal(stored);
82
+ }
83
+ }, [key, storage]);
84
+ const setState = useCallback((next) => {
85
+ pendingSaveKeyRef.current = key;
86
+ setStateInternal((prev) => {
87
+ return typeof next === 'function' ? next(prev) : next;
88
+ });
89
+ setPersistRevision((revision) => revision + 1);
90
+ }, [key]);
91
+ useEffect(() => {
92
+ if (persistedRevisionRef.current === persistRevision)
93
+ return;
94
+ persistedRevisionRef.current = persistRevision;
95
+ storage.save(pendingSaveKeyRef.current, state);
96
+ }, [persistRevision, state, storage]);
97
+ return [state, setState];
98
+ }
@@ -0,0 +1,15 @@
1
+ export interface MobileBottomNavProps {
2
+ className?: string;
3
+ }
4
+ /**
5
+ * Mobile-only bottom navigation bar.
6
+ *
7
+ * Renders 4 buttons sourced from `ctx.mobileBottomNav` (provider-controlled,
8
+ * overrideable per-app via `mobileBottomNav` prop). Each button click opens
9
+ * the corresponding drawer via `ctx.mobileDrawer.setOpen`.
10
+ *
11
+ * The Menu button supports long-press (500ms) to open the command palette.
12
+ *
13
+ * Hidden on non-mobile viewports and when the right panel is in fullscreen mode.
14
+ */
15
+ export declare function MobileBottomNav({ className }: MobileBottomNavProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef } from 'react';
4
+ import { cn } from '../../lib/utils.js';
5
+ import { useMedaShell } from '../shell-provider.js';
6
+ import { useShellViewport } from '../use-shell-viewport.js';
7
+ /**
8
+ * Mobile-only bottom navigation bar.
9
+ *
10
+ * Renders 4 buttons sourced from `ctx.mobileBottomNav` (provider-controlled,
11
+ * overrideable per-app via `mobileBottomNav` prop). Each button click opens
12
+ * the corresponding drawer via `ctx.mobileDrawer.setOpen`.
13
+ *
14
+ * The Menu button supports long-press (500ms) to open the command palette.
15
+ *
16
+ * Hidden on non-mobile viewports and when the right panel is in fullscreen mode.
17
+ */
18
+ export function MobileBottomNav({ className }) {
19
+ const ctx = useMedaShell();
20
+ const band = useShellViewport();
21
+ if (band !== 'mobile')
22
+ return null;
23
+ if (ctx.panel.mode === 'fullscreen')
24
+ return null;
25
+ return (_jsx("nav", { "aria-label": "Mobile navigation", className: cn('flex h-[var(--shell-bottom-nav-height)] items-center justify-around border-t border-border bg-card', className), children: ctx.mobileBottomNav.map((item) => (_jsx(MobileBottomNavButton, { item: item }, item.id))) }));
26
+ }
27
+ /** Long-press threshold in milliseconds */
28
+ const LONG_PRESS_DURATION = 500;
29
+ function MobileBottomNavButton({ item }) {
30
+ const ctx = useMedaShell();
31
+ const Icon = item.icon;
32
+ const label = typeof item.label === 'function' ? item.label() : item.label;
33
+ const isMenu = item.id === 'menu';
34
+ const longPressTimerRef = useRef(null);
35
+ const longPressFiredRef = useRef(false);
36
+ const cancelTimer = () => {
37
+ if (longPressTimerRef.current !== null) {
38
+ clearTimeout(longPressTimerRef.current);
39
+ longPressTimerRef.current = null;
40
+ }
41
+ };
42
+ const handlePointerDown = () => {
43
+ if (!isMenu)
44
+ return;
45
+ longPressFiredRef.current = false;
46
+ longPressTimerRef.current = setTimeout(() => {
47
+ longPressFiredRef.current = true;
48
+ longPressTimerRef.current = null;
49
+ ctx.commandPalette.setOpen(true);
50
+ }, LONG_PRESS_DURATION);
51
+ };
52
+ const handlePointerUp = () => {
53
+ cancelTimer();
54
+ };
55
+ const handlePointerLeave = () => {
56
+ cancelTimer();
57
+ };
58
+ const handlePointerCancel = () => {
59
+ cancelTimer();
60
+ };
61
+ const handleClick = () => {
62
+ if (isMenu && longPressFiredRef.current) {
63
+ // Long-press already fired — suppress the regular click action
64
+ longPressFiredRef.current = false;
65
+ return;
66
+ }
67
+ if (typeof item.opens === 'string') {
68
+ ctx.mobileDrawer.setOpen(item.opens);
69
+ }
70
+ else {
71
+ // Custom render fn — open a custom drawer keyed by the item's id
72
+ ctx.mobileDrawer.setOpen(item.id);
73
+ }
74
+ };
75
+ return (_jsxs("button", { type: "button", onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerCancel, onClick: handleClick, "aria-label": label, className: "flex h-full flex-1 flex-col items-center justify-center gap-0.5 text-muted-foreground hover:text-foreground", children: [_jsx(Icon, { size: 20, "aria-hidden": "true" }), _jsx("span", { className: "text-[10px]", children: label })] }));
76
+ }
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { IconRailItem } from '../icon-rail.js';
3
+ import type { ContextModule, PanelView } from '../types.js';
4
+ export interface MobileDrawersProps {
5
+ /** Menu drawer source (icon-rail items). */
6
+ menuItems?: IconRailItem[];
7
+ /** Module drawer source (current app's context-rail module). */
8
+ module?: ContextModule;
9
+ /** Panels drawer source. */
10
+ panelViews?: PanelView[];
11
+ /** Custom content drawers keyed by id from MobileBottomNavItem.opens render fn. */
12
+ customContent?: Record<string, (close: () => void) => ReactNode>;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Renders all four mobile drawer slots (Menu / Module / Panels / AI) plus
17
+ * any custom-content drawers. Mount once near the AppShell root; drawers
18
+ * open/close via `ctx.mobileDrawer.open` provider state.
19
+ */
20
+ export declare function MobileDrawers({ menuItems, module, panelViews, customContent, }: MobileDrawersProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect } from 'react';
4
+ import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, } from '../../components/ui/drawer.js';
5
+ import { cn } from '../../lib/utils.js';
6
+ import { useMedaShell } from '../shell-provider.js';
7
+ /**
8
+ * Renders all four mobile drawer slots (Menu / Module / Panels / AI) plus
9
+ * any custom-content drawers. Mount once near the AppShell root; drawers
10
+ * open/close via `ctx.mobileDrawer.open` provider state.
11
+ */
12
+ export function MobileDrawers({ menuItems = [], module, panelViews = [], customContent = {}, }) {
13
+ const ctx = useMedaShell();
14
+ const open = ctx.mobileDrawer.open;
15
+ const setOpen = ctx.mobileDrawer.setOpen;
16
+ const close = () => setOpen(null);
17
+ const renderCtx = {
18
+ workspaceId: ctx.workspace.id,
19
+ appId: ctx.activeAppId,
20
+ };
21
+ return (_jsxs(_Fragment, { children: [_jsx(MenuDrawer, { open: open === 'menu-drawer', onClose: close, items: menuItems }), _jsx(ModuleDrawer, { open: open === 'module-drawer', onClose: close, module: module }), _jsx(PanelsDrawer, { open: open === 'panels-drawer', onClose: close, panelViews: panelViews, renderCtx: renderCtx }), _jsx(AiDrawer, { open: open === 'ai-drawer', onClose: close, panelViews: panelViews, renderCtx: renderCtx }), Object.entries(customContent).map(([id, renderFn]) => (_jsx(Drawer, { open: open === id, onOpenChange: (o) => !o && close(), direction: "bottom", children: _jsx(DrawerContent, { children: renderFn(close) }) }, id)))] }));
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // Internal sub-drawers
25
+ // ---------------------------------------------------------------------------
26
+ function MenuDrawer({ open, onClose, items, }) {
27
+ return (_jsx(Drawer, { open: open, onOpenChange: (o) => !o && onClose(), direction: "left", children: _jsxs(DrawerContent, { children: [_jsxs(DrawerHeader, { children: [_jsx(DrawerTitle, { children: "Menu" }), _jsx(DrawerDescription, { className: "sr-only", children: "Switch between primary app areas." })] }), _jsx("nav", { className: "flex flex-col gap-0.5 p-2", children: items.map((item) => {
28
+ const Icon = item.icon;
29
+ return (_jsxs("a", { href: item.to, onClick: onClose, className: "flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground", children: [_jsx(Icon, { size: 18, "aria-hidden": "true" }), _jsx("span", { children: item.label })] }, item.id));
30
+ }) })] }) }));
31
+ }
32
+ function ModuleDrawer({ open, onClose, module, }) {
33
+ if (!module)
34
+ return null;
35
+ return (_jsx(Drawer, { open: open, onOpenChange: (o) => !o && onClose(), direction: "left", children: _jsxs(DrawerContent, { children: [_jsxs(DrawerHeader, { children: [_jsx(DrawerTitle, { children: module.label }), module.description && (_jsx(DrawerDescription, { className: "text-muted-foreground text-xs", children: module.description }))] }), _jsx("nav", { className: "flex flex-col gap-0.5 p-2", children: module.items.map((item) => {
36
+ const Icon = item.icon;
37
+ return (_jsxs("a", { href: item.to, onClick: onClose, className: "flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground", children: [_jsx(Icon, { size: 16, "aria-hidden": "true" }), _jsx("span", { children: item.label })] }, item.id));
38
+ }) })] }) }));
39
+ }
40
+ function PanelsDrawer({ open, onClose, panelViews, renderCtx, }) {
41
+ const ctx = useMedaShell();
42
+ const activeView = ctx.panel.activeView;
43
+ const active = panelViews.find((v) => v.id === activeView) ?? panelViews[0];
44
+ return (_jsx(Drawer, { open: open, onOpenChange: (o) => !o && onClose(), direction: "bottom", children: _jsxs(DrawerContent, { children: [_jsxs(DrawerHeader, { children: [_jsx(DrawerTitle, { children: active?.label ?? 'Panels' }), _jsx(DrawerDescription, { className: "sr-only", children: "Contextual panels for the current module." })] }), panelViews.length > 1 && (_jsx("div", { className: "flex items-center gap-1 border-b border-border px-3 py-2", children: panelViews.map((view) => (_jsx("button", { type: "button", onClick: () => ctx.panel.setActiveView(view.id), "aria-current": view.id === activeView ? 'true' : undefined, className: cn('rounded-md px-2 py-1 text-xs', view.id === activeView
45
+ ? 'bg-accent text-accent-foreground'
46
+ : 'text-muted-foreground hover:bg-accent'), children: view.label }, view.id))) })), _jsx("div", { className: "flex-1 overflow-y-auto p-3", children: active?.render(renderCtx) })] }) }));
47
+ }
48
+ function AiDrawer({ open, onClose, panelViews, renderCtx, }) {
49
+ const ctx = useMedaShell();
50
+ const aiView = panelViews.find((v) => v.id === 'ai');
51
+ const setActiveView = ctx.panel.setActiveView;
52
+ // Pin to ai view when drawer opens
53
+ // biome-ignore lint/correctness/useExhaustiveDependencies: setActiveView is stable (from useMemo in provider)
54
+ useEffect(() => {
55
+ if (open && aiView) {
56
+ setActiveView('ai');
57
+ }
58
+ }, [open, aiView]);
59
+ if (!aiView)
60
+ return null;
61
+ return (_jsx(Drawer, { open: open, onOpenChange: (o) => !o && onClose(), direction: "bottom", children: _jsxs(DrawerContent, { children: [_jsxs(DrawerHeader, { children: [_jsx(DrawerTitle, { children: aiView.label }), _jsx(DrawerDescription, { className: "sr-only", children: "Open the AI assistant panel." })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-3", children: aiView.render(renderCtx) })] }) }));
62
+ }
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface MobileHeaderProps {
3
+ /** When set, renders back button + parentLabel · title (nested mode). */
4
+ parentLabel?: string;
5
+ /** Optional — for routing-system link rendering. */
6
+ parentTo?: string;
7
+ /** Page title shown in nested mode. */
8
+ title?: string;
9
+ /** Back button handler (nested mode). */
10
+ onBack?: () => void;
11
+ /** Root mode only — renders to the right of workspace name. */
12
+ globalActions?: ReactNode;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Mobile-only header.
17
+ *
18
+ * Root mode: workspace name + optional globalActions.
19
+ * Nested mode (when parentLabel is set): ← parentLabel · pageTitle back button.
20
+ *
21
+ * Renders nothing on non-mobile viewports.
22
+ */
23
+ export declare function MobileHeader({ parentLabel, parentTo: _parentTo, title, onBack, globalActions, className, }: MobileHeaderProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronLeft } from 'lucide-react';
4
+ import { cn } from '../../lib/utils.js';
5
+ import { useMedaShell } from '../shell-provider.js';
6
+ import { useShellViewport } from '../use-shell-viewport.js';
7
+ /**
8
+ * Mobile-only header.
9
+ *
10
+ * Root mode: workspace name + optional globalActions.
11
+ * Nested mode (when parentLabel is set): ← parentLabel · pageTitle back button.
12
+ *
13
+ * Renders nothing on non-mobile viewports.
14
+ */
15
+ export function MobileHeader({ parentLabel, parentTo: _parentTo, title, onBack, globalActions, className, }) {
16
+ const ctx = useMedaShell();
17
+ const band = useShellViewport();
18
+ const isNested = !!parentLabel;
19
+ if (band !== 'mobile')
20
+ return null;
21
+ return (_jsx("header", { "data-meda-mobile-header": isNested ? 'nested' : 'root', className: cn('flex h-[var(--shell-header-height)] items-center justify-between border-b border-border bg-card px-3', className), children: isNested ? (_jsxs("button", { type: "button", onClick: onBack, "aria-label": "Go back", className: "flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm hover:bg-accent", children: [_jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "text-muted-foreground", children: parentLabel }), title && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-muted-foreground/40", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "text-foreground", children: title })] }))] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [_jsx("span", { "aria-hidden": "true", children: ctx.workspace.icon }), _jsx("span", { children: ctx.workspace.name })] }), globalActions && _jsx("div", { className: "flex items-center gap-1", children: globalActions })] })) }));
22
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Motion tokens (spec §14).
3
+ *
4
+ * Numeric values are unitless milliseconds — consumers append `ms`:
5
+ * `transition-duration: ${motion.default}ms`
6
+ *
7
+ * Mirrored as CSS custom properties in `src/styles/tokens.css` for use
8
+ * via Tailwind arbitrary values (e.g. `duration-[var(--motion-default)]`).
9
+ *
10
+ * `spring` is JS-only (no CSS analogue); for Framer Motion / react-spring.
11
+ */
12
+ export declare const motion: {
13
+ readonly fast: 140;
14
+ readonly default: 200;
15
+ readonly panel: 240;
16
+ readonly fullscreen: 280;
17
+ readonly ease: "cubic-bezier(0.2, 0.8, 0.2, 1)";
18
+ readonly spring: {
19
+ readonly stiffness: 420;
20
+ readonly damping: 34;
21
+ };
22
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Motion tokens (spec §14).
3
+ *
4
+ * Numeric values are unitless milliseconds — consumers append `ms`:
5
+ * `transition-duration: ${motion.default}ms`
6
+ *
7
+ * Mirrored as CSS custom properties in `src/styles/tokens.css` for use
8
+ * via Tailwind arbitrary values (e.g. `duration-[var(--motion-default)]`).
9
+ *
10
+ * `spring` is JS-only (no CSS analogue); for Framer Motion / react-spring.
11
+ */
12
+ export const motion = {
13
+ fast: 140,
14
+ default: 200,
15
+ panel: 240,
16
+ fullscreen: 280,
17
+ ease: 'cubic-bezier(0.2, 0.8, 0.2, 1)',
18
+ spring: { stiffness: 420, damping: 34 },
19
+ };
@@ -1,2 +1,16 @@
1
- export type { ShellCommandDefinition, ShellContentLayout, ShellHostAdapter, ShellModuleDefinition, ShellNavItem, ShellPanelDefinition, ShellRailItem, ShellRenderContext, ShellRouteContext, ShellSearchParamsAdapter, ShellStateContextValue, ShellTab, ShellViewDefinition, ShellViewportBand, } from './index.js';
2
- export { buildShellSectionCommands, buildShellShortcutMap, getDefaultShellPanelView, getResolvedShellPanelWidth, getShellActionsFromMatches, getShellContentLayoutFromMatches, getShellContentMaxWidth, getShellPanelCollection, getShellPanelView, getShellPanelViewsFromMatches, getShellTabsFromMatches, isShellPanelViewAvailable, NavigationArea, resolveShellPanelView, ShellAppRail, ShellDesktopPanelDock, ShellFrame, ShellHeaderFrame, ShellModuleNav, ShellPanelRail, ShellPanelToggle, ShellScrollableContent, ShellStateProvider, ShellTabBar, useShellState, WorkbenchLayout, } from './index.js';
1
+ export { ShellDesktopPanelDock } from './extras/shell-desktop-panel-dock.js';
2
+ export { ShellScrollableContent } from './extras/shell-scrollable-content.js';
3
+ export { ShellTabBar } from './extras/shell-tab-bar.js';
4
+ export { WorkbenchLayout } from './extras/workbench-layout.js';
5
+ export { NavigationArea } from './navigation-area.js';
6
+ export { ShellAppRail } from './shell-app-rail.js';
7
+ export { ShellFrame } from './shell-frame.js';
8
+ export { ShellHeaderFrame, ShellPanelToggle } from './shell-header.js';
9
+ export { getResolvedShellPanelWidth, getShellContentMaxWidth, } from './shell-layout-utils.js';
10
+ export { ShellModuleNav } from './shell-module-nav.js';
11
+ export { ShellPanelRail } from './shell-panel-rail.js';
12
+ export { getShellActionsFromMatches, getShellContentLayoutFromMatches, getShellPanelViewsFromMatches, getShellTabsFromMatches, } from './shell-route-utils.js';
13
+ export type { ShellSearchParamsAdapter, ShellStateContextValue, } from './shell-state.js';
14
+ export { ShellStateProvider, useShellState } from './shell-state.js';
15
+ export type { ShellCommandDefinition, ShellContentLayout, ShellHostAdapter, ShellModuleDefinition, ShellNavItem, ShellPanelDefinition, ShellPanelRenderContext, ShellRailItem, ShellRouteContext, ShellTab, ShellViewDefinition, ShellViewportBand, } from './types.js';
16
+ export { buildShellSectionCommands, buildShellShortcutMap, getDefaultShellPanelView, getShellPanelCollection, getShellPanelView, isShellPanelViewAvailable, resolveShellPanelView, } from './utils.js';
@@ -1 +1,20 @@
1
- export { buildShellSectionCommands, buildShellShortcutMap, getDefaultShellPanelView, getResolvedShellPanelWidth, getShellActionsFromMatches, getShellContentLayoutFromMatches, getShellContentMaxWidth, getShellPanelCollection, getShellPanelView, getShellPanelViewsFromMatches, getShellTabsFromMatches, isShellPanelViewAvailable, NavigationArea, resolveShellPanelView, ShellAppRail, ShellDesktopPanelDock, ShellFrame, ShellHeaderFrame, ShellModuleNav, ShellPanelRail, ShellPanelToggle, ShellScrollableContent, ShellStateProvider, ShellTabBar, useShellState, WorkbenchLayout, } from './index.js';
1
+ // Legacy public surface imports from source files directly since index.ts now re-exports v2 only.
2
+ // Phase 17 deletes this file along with the legacy component files.
3
+ // Extras (moved to extras/ during Phase 15)
4
+ export { ShellDesktopPanelDock } from './extras/shell-desktop-panel-dock.js';
5
+ export { ShellScrollableContent } from './extras/shell-scrollable-content.js';
6
+ export { ShellTabBar } from './extras/shell-tab-bar.js';
7
+ export { WorkbenchLayout } from './extras/workbench-layout.js';
8
+ // Legacy components
9
+ export { NavigationArea } from './navigation-area.js';
10
+ export { ShellAppRail } from './shell-app-rail.js';
11
+ export { ShellFrame } from './shell-frame.js';
12
+ // Legacy compat shims inside shell-header.tsx
13
+ export { ShellHeaderFrame, ShellPanelToggle } from './shell-header.js';
14
+ export { getResolvedShellPanelWidth, getShellContentMaxWidth, } from './shell-layout-utils.js';
15
+ export { ShellModuleNav } from './shell-module-nav.js';
16
+ export { ShellPanelRail } from './shell-panel-rail.js';
17
+ export { getShellActionsFromMatches, getShellContentLayoutFromMatches, getShellPanelViewsFromMatches, getShellTabsFromMatches, } from './shell-route-utils.js';
18
+ export { ShellStateProvider, useShellState } from './shell-state.js';
19
+ // Utility functions from their actual source locations
20
+ export { buildShellSectionCommands, buildShellShortcutMap, getDefaultShellPanelView, getShellPanelCollection, getShellPanelView, isShellPanelViewAvailable, resolveShellPanelView, } from './utils.js';