@medalsocial/meda 0.2.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/index.d.ts +11 -0
- package/dist/chat/index.js +5 -0
- package/dist/chat/latency-badge.d.ts +7 -0
- package/dist/chat/latency-badge.js +23 -0
- package/dist/chat/latency-breakdown.d.ts +8 -0
- package/dist/chat/latency-breakdown.js +9 -0
- package/dist/chat/public.d.ts +1 -0
- package/dist/chat/public.js +1 -0
- package/dist/chat/tool-call-block.d.ts +6 -0
- package/dist/chat/tool-call-block.js +19 -0
- package/dist/chat/transcript-stream.d.ts +10 -0
- package/dist/chat/transcript-stream.js +40 -0
- package/dist/chat/turn-card.d.ts +10 -0
- package/dist/chat/turn-card.js +25 -0
- package/dist/chat/types.d.ts +36 -0
- package/dist/chat/types.js +1 -0
- 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 +6 -1
- package/dist/index.js +6 -1
- package/dist/lib/format-time.d.ts +9 -0
- package/dist/lib/format-time.js +34 -0
- 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/index.d.ts +6 -0
- package/dist/panel/index.js +3 -0
- package/dist/panel/inspector-field.d.ts +9 -0
- package/dist/panel/inspector-field.js +4 -0
- package/dist/panel/inspector-json.d.ts +7 -0
- package/dist/panel/inspector-json.js +75 -0
- package/dist/panel/inspector.d.ts +7 -0
- package/dist/panel/inspector.js +12 -0
- package/dist/panel/public.d.ts +1 -0
- package/dist/panel/public.js +1 -0
- package/dist/panel/types.d.ts +6 -0
- package/dist/panel/types.js +1 -0
- 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.d.ts +11 -0
- package/dist/timeline/date-switcher.js +64 -0
- package/dist/timeline/event-card.d.ts +9 -0
- package/dist/timeline/event-card.js +18 -0
- package/dist/timeline/index.d.ts +13 -0
- package/dist/timeline/index.js +6 -0
- package/dist/timeline/live-indicator.d.ts +7 -0
- package/dist/timeline/live-indicator.js +7 -0
- package/dist/timeline/public.d.ts +1 -0
- package/dist/timeline/public.js +1 -0
- package/dist/timeline/scrub-bar.d.ts +14 -0
- package/dist/timeline/scrub-bar.js +43 -0
- package/dist/timeline/timeline-rail.d.ts +19 -0
- package/dist/timeline/timeline-rail.js +66 -0
- package/dist/timeline/timeline-tape.d.ts +19 -0
- package/dist/timeline/timeline-tape.js +65 -0
- package/dist/timeline/types.d.ts +40 -0
- package/dist/timeline/types.js +1 -0
- 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 +67 -7
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandShortcut, } from '../components/ui/command.js';
|
|
5
|
+
import { useMedaShell } from './shell-provider.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Context
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
export const CommandRegistryContext = createContext(null);
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// matchesHotkey — strict modifier matching
|
|
12
|
+
//
|
|
13
|
+
// 'mod+k' → matches Cmd+K on macOS, Ctrl+K on Windows/Linux
|
|
14
|
+
// 'mod+shift+k' → requires modifier + shift + key; no other modifiers
|
|
15
|
+
// 'mod+alt+k' → requires modifier + alt + key; no other modifiers
|
|
16
|
+
//
|
|
17
|
+
// Modifiers not listed in `spec` MUST NOT be pressed.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
export function matchesHotkey(e, spec) {
|
|
20
|
+
const parts = spec.toLowerCase().split('+');
|
|
21
|
+
const key = parts.at(-1) ?? '';
|
|
22
|
+
const mods = parts.slice(0, -1);
|
|
23
|
+
const wantMod = mods.includes('mod');
|
|
24
|
+
const wantShift = mods.includes('shift');
|
|
25
|
+
const wantAlt = mods.includes('alt');
|
|
26
|
+
const hasMod = e.metaKey || e.ctrlKey;
|
|
27
|
+
return (e.key.toLowerCase() === key &&
|
|
28
|
+
(wantMod ? hasMod : !hasMod) &&
|
|
29
|
+
(wantShift ? e.shiftKey : !e.shiftKey) &&
|
|
30
|
+
(wantAlt ? e.altKey : !e.altKey));
|
|
31
|
+
}
|
|
32
|
+
export function CommandPalette({ children }) {
|
|
33
|
+
const ctx = useMedaShell();
|
|
34
|
+
const { open, setOpen } = ctx.commandPalette;
|
|
35
|
+
const hotkey = ctx.commandPaletteHotkey;
|
|
36
|
+
const [commands, setCommands] = useState([]);
|
|
37
|
+
const [groups, setGroups] = useState([]);
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
// Registry methods
|
|
40
|
+
// -------------------------------------------------------------------------
|
|
41
|
+
const register = useCallback((cmds) => {
|
|
42
|
+
setCommands((prev) => {
|
|
43
|
+
const ids = new Set(cmds.map((c) => c.id));
|
|
44
|
+
return [...prev.filter((c) => !ids.has(c.id)), ...cmds];
|
|
45
|
+
});
|
|
46
|
+
}, []);
|
|
47
|
+
const unregister = useCallback((ids) => {
|
|
48
|
+
setCommands((prev) => prev.filter((c) => !ids.includes(c.id)));
|
|
49
|
+
}, []);
|
|
50
|
+
const registerGroup = useCallback((group) => {
|
|
51
|
+
setGroups((prev) => [...prev.filter((g) => g.id !== group.id), group]);
|
|
52
|
+
}, []);
|
|
53
|
+
const unregisterGroup = useCallback((id) => {
|
|
54
|
+
setGroups((prev) => prev.filter((g) => g.id !== id));
|
|
55
|
+
}, []);
|
|
56
|
+
const registry = useMemo(() => ({ register, unregister, registerGroup, unregisterGroup }), [register, unregister, registerGroup, unregisterGroup]);
|
|
57
|
+
// -------------------------------------------------------------------------
|
|
58
|
+
// Hotkey listener
|
|
59
|
+
// -------------------------------------------------------------------------
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const onKey = (e) => {
|
|
62
|
+
if (matchesHotkey(e, hotkey)) {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
setOpen(true);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
window.addEventListener('keydown', onKey);
|
|
68
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
69
|
+
}, [hotkey, setOpen]);
|
|
70
|
+
// -------------------------------------------------------------------------
|
|
71
|
+
// Group + sort commands
|
|
72
|
+
// -------------------------------------------------------------------------
|
|
73
|
+
const grouped = useMemo(() => {
|
|
74
|
+
const byGroup = new Map();
|
|
75
|
+
for (const cmd of commands) {
|
|
76
|
+
const list = byGroup.get(cmd.group) ?? [];
|
|
77
|
+
list.push(cmd);
|
|
78
|
+
byGroup.set(cmd.group, list);
|
|
79
|
+
}
|
|
80
|
+
const orderedGroupIds = [...byGroup.keys()].sort((a, b) => {
|
|
81
|
+
const ga = groups.find((g) => g.id === a);
|
|
82
|
+
const gb = groups.find((g) => g.id === b);
|
|
83
|
+
const pa = ga?.priority ?? 100;
|
|
84
|
+
const pb = gb?.priority ?? 100;
|
|
85
|
+
if (pa !== pb)
|
|
86
|
+
return pa - pb;
|
|
87
|
+
const la = ga?.label ?? a;
|
|
88
|
+
const lb = gb?.label ?? b;
|
|
89
|
+
return la.localeCompare(lb);
|
|
90
|
+
});
|
|
91
|
+
return orderedGroupIds.map((groupId) => {
|
|
92
|
+
const def = groups.find((g) => g.id === groupId);
|
|
93
|
+
return {
|
|
94
|
+
id: groupId,
|
|
95
|
+
label: def?.label ?? groupId,
|
|
96
|
+
commands: (byGroup.get(groupId) ?? []).sort((a, b) => a.label.localeCompare(b.label)),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}, [commands, groups]);
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
// Handlers
|
|
102
|
+
// -------------------------------------------------------------------------
|
|
103
|
+
const handleSelect = (cmd) => {
|
|
104
|
+
setOpen(false);
|
|
105
|
+
void cmd.run();
|
|
106
|
+
};
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// Render
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
return (_jsxs(CommandRegistryContext.Provider, { value: registry, children: [children, _jsx(CommandDialog, { open: open, onOpenChange: setOpen, children: _jsxs(Command, { children: [_jsx(CommandInput, { placeholder: "Search commands\u2026" }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: "No results." }), grouped.map((group) => (_jsx(CommandGroup, { heading: group.label, children: group.commands.map((cmd) => {
|
|
111
|
+
const Icon = cmd.icon;
|
|
112
|
+
return (_jsxs(CommandItem, { value: `${group.label} ${cmd.label}`, onSelect: () => handleSelect(cmd), children: [Icon && _jsx(Icon, { size: 16, "aria-hidden": "true" }), _jsx("span", { children: cmd.label }), cmd.shortcut && _jsx(CommandShortcut, { children: cmd.shortcut })] }, cmd.id));
|
|
113
|
+
}) }, group.id)))] })] }) })] }));
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// useCommands — registers on mount, unregisters on unmount
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
export function useCommands(commands) {
|
|
119
|
+
const registry = useContext(CommandRegistryContext);
|
|
120
|
+
if (!registry)
|
|
121
|
+
throw new Error('useCommands must be used inside <CommandPalette>');
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
registry.register(commands);
|
|
124
|
+
return () => registry.unregister(commands.map((c) => c.id));
|
|
125
|
+
}, [commands, registry.register, registry.unregister]);
|
|
126
|
+
}
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// useCommandGroup — registers group metadata for ordering
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
export function useCommandGroup(group) {
|
|
131
|
+
const registry = useContext(CommandRegistryContext);
|
|
132
|
+
if (!registry)
|
|
133
|
+
throw new Error('useCommandGroup must be used inside <CommandPalette>');
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
registry.registerGroup(group);
|
|
136
|
+
return () => registry.unregisterGroup(group.id);
|
|
137
|
+
}, [group, registry.registerGroup, registry.unregisterGroup]);
|
|
138
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextRail — spec §10
|
|
3
|
+
*
|
|
4
|
+
* Renders a resizable, collapsible, persisted context navigation rail.
|
|
5
|
+
* Width is stored per-(workspaceId, appId) via useShellLayoutState through
|
|
6
|
+
* the MedaShellProvider context.
|
|
7
|
+
*
|
|
8
|
+
* Resize integration note (Phase 9 / Pattern B):
|
|
9
|
+
* The rail uses its own pointer-events resize handle on the right edge instead
|
|
10
|
+
* of wrapping in <ResizableShell> (Pattern A). Pattern A requires migrating
|
|
11
|
+
* <AppShellBody> to a PanelGroup layout — that is Phase 11 territory.
|
|
12
|
+
* TODO(phase-11): replace pointer-events handle with <ResizableShellPanel>
|
|
13
|
+
* once <AppShellBody> ships as a ResizableShell Group.
|
|
14
|
+
*/
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
16
|
+
import type { ContextModule, ShellLinkRenderArgs } from './types.js';
|
|
17
|
+
export interface ContextRailProps {
|
|
18
|
+
/** Drives the persistence key for layout state. */
|
|
19
|
+
appId: string;
|
|
20
|
+
module?: ContextModule;
|
|
21
|
+
/** Starting width in px. Falls back to provider's stored width (default 300). */
|
|
22
|
+
defaultWidth?: number;
|
|
23
|
+
collapsible?: boolean;
|
|
24
|
+
/** When true, the rail evaporates — renders an aria-hidden placeholder. */
|
|
25
|
+
hidden?: boolean;
|
|
26
|
+
/** Optional active item id for nav active state (preferred over useMedaShell().selection). */
|
|
27
|
+
activeItemId?: string;
|
|
28
|
+
renderLink?: (args: ShellLinkRenderArgs) => ReactNode;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function ContextRail({ appId: _appId, module, hidden, collapsible: _collapsible, activeItemId, renderLink, className, }: ContextRailProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState } 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
|
+
// Constants — spec §10 dimensions
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const MIN_WIDTH = 240;
|
|
11
|
+
const MAX_WIDTH = 420;
|
|
12
|
+
function ResizeHandle({ currentWidth, onResize, onCommit }) {
|
|
13
|
+
const startWidthRef = useRef(currentWidth);
|
|
14
|
+
const startXRef = useRef(0);
|
|
15
|
+
const draggingRef = useRef(false);
|
|
16
|
+
const handlePointerDown = (e) => {
|
|
17
|
+
// setPointerCapture may not be available in all test environments (jsdom)
|
|
18
|
+
try {
|
|
19
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// ignore — drag still functions via draggingRef
|
|
23
|
+
}
|
|
24
|
+
startWidthRef.current = currentWidth;
|
|
25
|
+
startXRef.current = e.clientX;
|
|
26
|
+
draggingRef.current = true;
|
|
27
|
+
};
|
|
28
|
+
const handlePointerMove = (e) => {
|
|
29
|
+
if (!draggingRef.current)
|
|
30
|
+
return;
|
|
31
|
+
const next = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidthRef.current + (e.clientX - startXRef.current)));
|
|
32
|
+
onResize(next);
|
|
33
|
+
};
|
|
34
|
+
const handlePointerUp = (e) => {
|
|
35
|
+
if (!draggingRef.current)
|
|
36
|
+
return;
|
|
37
|
+
draggingRef.current = false;
|
|
38
|
+
const next = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidthRef.current + (e.clientX - startXRef.current)));
|
|
39
|
+
onCommit(next);
|
|
40
|
+
};
|
|
41
|
+
return (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": "Resize context rail", "aria-valuenow": currentWidth, "aria-valuemin": MIN_WIDTH, "aria-valuemax": MAX_WIDTH, tabIndex: 0, className: cn('absolute top-0 right-0 h-full w-1 cursor-col-resize', 'opacity-0 transition-opacity hover:opacity-100 hover:bg-ring'), onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp }));
|
|
42
|
+
}
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// ContextRail
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
export function ContextRail({ appId: _appId, module, hidden = false, collapsible: _collapsible = true, activeItemId, renderLink, className, }) {
|
|
47
|
+
const band = useShellViewport();
|
|
48
|
+
const ctx = useMedaShell();
|
|
49
|
+
const collapsed = ctx.contextRail.collapsed;
|
|
50
|
+
// Local display width tracks pointer-move updates; ctx.contextRail.setWidth
|
|
51
|
+
// is called on pointerUp to persist via useShellLayoutState.
|
|
52
|
+
const [displayWidth, setDisplayWidth] = useState(null);
|
|
53
|
+
const width = displayWidth ?? ctx.contextRail.width;
|
|
54
|
+
if (band === 'mobile')
|
|
55
|
+
return null;
|
|
56
|
+
if (hidden) {
|
|
57
|
+
return _jsx("div", { "aria-hidden": "true", className: "hidden", "data-testid": "context-rail-hidden" });
|
|
58
|
+
}
|
|
59
|
+
if (!module || module.items.length === 0) {
|
|
60
|
+
return _jsx("div", { "aria-hidden": "true", className: "hidden", "data-testid": "context-rail-empty" });
|
|
61
|
+
}
|
|
62
|
+
const handleResize = (w) => {
|
|
63
|
+
setDisplayWidth(w);
|
|
64
|
+
};
|
|
65
|
+
const handleCommit = (w) => {
|
|
66
|
+
setDisplayWidth(null);
|
|
67
|
+
ctx.contextRail.setWidth(w);
|
|
68
|
+
};
|
|
69
|
+
return (_jsxs("aside", { "aria-label": module.label, className: cn('relative h-full shrink-0 overflow-hidden border-r border-shell-border bg-shell-context', collapsed && 'w-0', className), style: { width: collapsed ? 0 : width }, children: [_jsxs("div", { className: "border-b border-shell-border px-4 py-3", children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: module.label }), module.description && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: module.description }))] }), _jsx("nav", { "aria-label": `${module.label} navigation`, className: "flex flex-col gap-0.5 p-2", children: module.items.map((item) => {
|
|
70
|
+
const isActive = item.id === activeItemId;
|
|
71
|
+
const klass = cn('flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors', isActive
|
|
72
|
+
? 'bg-primary/10 text-primary'
|
|
73
|
+
: 'text-muted-foreground hover:bg-accent hover:text-foreground');
|
|
74
|
+
const IconComp = item.icon;
|
|
75
|
+
const inner = (_jsxs(_Fragment, { children: [_jsx(IconComp, { size: 16, "aria-hidden": "true", className: "shrink-0" }), _jsx("span", { className: "truncate", children: item.label }), item.shortcut && (_jsx("kbd", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: item.shortcut }))] }));
|
|
76
|
+
if (renderLink) {
|
|
77
|
+
return renderLink({ item, isActive, className: klass, children: inner });
|
|
78
|
+
}
|
|
79
|
+
return (_jsx("a", { href: item.to, "aria-current": isActive ? 'page' : undefined, className: klass, children: inner }, item.id));
|
|
80
|
+
}) }), _jsx(ResizeHandle, { currentWidth: width, onResize: handleResize, onCommit: handleCommit })] }));
|
|
81
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export declare function ShellDesktopPanelDock({ defaultView, panelOpen, viewIds, width, onWidthChange, renderPanel, className, }: {
|
|
3
|
+
defaultView: string;
|
|
4
|
+
panelOpen: boolean;
|
|
5
|
+
viewIds?: string[];
|
|
6
|
+
width: number;
|
|
7
|
+
onWidthChange: (width: number) => void;
|
|
8
|
+
renderPanel: (options: {
|
|
9
|
+
defaultView: string;
|
|
10
|
+
viewIds?: string[];
|
|
11
|
+
className?: string;
|
|
12
|
+
}) => React.ReactNode;
|
|
13
|
+
className?: string;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
function ShellPanelResizeHandle({ width, onWidthChange, }) {
|
|
4
|
+
const startXRef = React.useRef(0);
|
|
5
|
+
const startWidthRef = React.useRef(width);
|
|
6
|
+
const cleanupRef = React.useRef(null);
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
return () => {
|
|
9
|
+
cleanupRef.current?.();
|
|
10
|
+
};
|
|
11
|
+
}, []);
|
|
12
|
+
function handlePointerDown(event) {
|
|
13
|
+
if (event.button !== 0)
|
|
14
|
+
return;
|
|
15
|
+
startXRef.current = event.clientX;
|
|
16
|
+
startWidthRef.current = width;
|
|
17
|
+
const handlePointerMove = (moveEvent) => {
|
|
18
|
+
onWidthChange(startWidthRef.current + (startXRef.current - moveEvent.clientX));
|
|
19
|
+
};
|
|
20
|
+
const handlePointerEnd = () => {
|
|
21
|
+
window.removeEventListener('pointermove', handlePointerMove);
|
|
22
|
+
window.removeEventListener('pointerup', handlePointerEnd);
|
|
23
|
+
window.removeEventListener('pointercancel', handlePointerEnd);
|
|
24
|
+
cleanupRef.current = null;
|
|
25
|
+
};
|
|
26
|
+
cleanupRef.current?.();
|
|
27
|
+
window.addEventListener('pointermove', handlePointerMove);
|
|
28
|
+
window.addEventListener('pointerup', handlePointerEnd);
|
|
29
|
+
window.addEventListener('pointercancel', handlePointerEnd);
|
|
30
|
+
cleanupRef.current = handlePointerEnd;
|
|
31
|
+
}
|
|
32
|
+
return (_jsx("button", { type: "button", "aria-label": "Resize panel", onPointerDown: handlePointerDown, className: "absolute inset-y-0 left-0 z-10 flex w-2 shrink-0 cursor-col-resize items-center justify-center bg-transparent transition-colors hover:bg-[var(--primary)]/10", children: _jsx("span", { className: "h-12 w-px rounded-full bg-[var(--border-subtle)]" }) }));
|
|
33
|
+
}
|
|
34
|
+
export function ShellDesktopPanelDock({ defaultView, panelOpen, viewIds, width, onWidthChange, renderPanel, className, }) {
|
|
35
|
+
return (_jsx("div", { "data-testid": "desktop-panel-dock", "aria-hidden": !panelOpen, "data-state": panelOpen ? 'open' : 'closed', className: panelOpen
|
|
36
|
+
? 'pointer-events-auto absolute inset-y-4 right-4 z-20 translate-x-0 opacity-100 transition-[transform,opacity] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]'
|
|
37
|
+
: 'pointer-events-none absolute inset-y-4 right-4 z-20 translate-x-[calc(100%+1.25rem)] opacity-0 transition-[transform,opacity] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]', style: { width: `${width}px` }, children: _jsxs("div", { className: "relative h-full w-full", children: [_jsx(ShellPanelResizeHandle, { width: width, onWidthChange: onWidthChange }), renderPanel({
|
|
38
|
+
defaultView,
|
|
39
|
+
viewIds,
|
|
40
|
+
className: className ??
|
|
41
|
+
'ml-2 rounded-[var(--radius-4xl)] border border-[var(--border)] bg-[color-mix(in_srgb,var(--shell-panel)_90%,transparent)] shadow-[var(--shell-shadow)] backdrop-blur-xl',
|
|
42
|
+
})] }) }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ShellContentLayout } from './types.js';
|
|
3
|
+
export interface ShellScrollableContentProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
desktopDockOffset?: number;
|
|
6
|
+
layout: ShellContentLayout;
|
|
7
|
+
maxWidth?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function ShellScrollableContent({ children, desktopDockOffset, layout, maxWidth, }: ShellScrollableContentProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
function joinClasses(...values) {
|
|
3
|
+
return values.filter(Boolean).join(' ');
|
|
4
|
+
}
|
|
5
|
+
export function ShellScrollableContent({ children, desktopDockOffset = 0, layout, maxWidth, }) {
|
|
6
|
+
return (_jsx("main", { "data-testid": "shell-workspace-viewport", className: "shell-scrollbar-hidden absolute inset-0 min-w-0 overflow-y-auto overflow-x-hidden pb-[var(--bottom-nav-height)] transition-[padding] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] md:pb-0", style: desktopDockOffset > 0 ? { paddingRight: `${desktopDockOffset}px` } : undefined, children: layout === 'centered' ? (_jsx("div", { "data-testid": "shell-content-centered", className: "flex min-h-full items-center justify-center px-5 py-5 sm:px-6 sm:py-6 md:px-8 md:py-8 lg:px-10", children: _jsx("div", { className: "flex w-full max-w-3xl items-center justify-center", children: children }) })) : layout === 'fullbleed' ? (_jsx("div", { "data-testid": "shell-content-fullbleed", className: "mx-auto w-full px-5 py-5 sm:px-6 sm:py-6 md:px-8 md:py-8 lg:px-10", style: maxWidth ? { maxWidth: `${maxWidth}px` } : undefined, children: children })) : (_jsx("div", { "data-testid": "shell-content-workspace", className: joinClasses('mx-auto w-full px-5 py-5 sm:px-6 sm:py-6 md:px-8 md:py-8 lg:px-10'), style: maxWidth ? { maxWidth: `${maxWidth}px` } : undefined, children: children })) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ShellTab } from './types.js';
|
|
3
|
+
interface ShellTabBarRenderArgs {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
className: string;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
tab: ShellTab;
|
|
8
|
+
}
|
|
9
|
+
export interface ShellTabBarProps {
|
|
10
|
+
tabs: ShellTab[];
|
|
11
|
+
activeTab?: string;
|
|
12
|
+
onTabChange?: (tabId: string) => void;
|
|
13
|
+
className?: string;
|
|
14
|
+
ariaLabel?: string;
|
|
15
|
+
renderLink?: (args: ShellTabBarRenderArgs) => ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export declare function ShellTabBar({ tabs, activeTab, onTabChange, className, ariaLabel, renderLink, }: ShellTabBarProps): import("react/jsx-runtime").JSX.Element | null;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
function joinClasses(...values) {
|
|
3
|
+
return values.filter(Boolean).join(' ');
|
|
4
|
+
}
|
|
5
|
+
const tabBarClassName = 'flex h-[var(--tab-bar-height)] shrink-0 items-center gap-3 overflow-x-auto bg-[var(--background)] px-3';
|
|
6
|
+
const tabClassName = 'border-b-[1.5px] pb-2.5 pt-2.5 text-xs whitespace-nowrap transition-colors';
|
|
7
|
+
const activeTabClassName = 'border-[var(--primary)] text-[var(--primary)]';
|
|
8
|
+
const inactiveTabClassName = 'border-transparent text-[var(--muted-foreground)] hover:text-[var(--foreground)]';
|
|
9
|
+
export function ShellTabBar({ tabs, activeTab, onTabChange, className, ariaLabel = 'Tabs', renderLink, }) {
|
|
10
|
+
if (tabs.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
if (renderLink && tabs.every((tab) => Boolean(tab.to))) {
|
|
13
|
+
return (_jsx("nav", { "aria-label": ariaLabel, className: joinClasses(tabBarClassName, className), children: tabs.map((tab) => {
|
|
14
|
+
const isActive = tab.id === activeTab;
|
|
15
|
+
return renderLink({
|
|
16
|
+
tab,
|
|
17
|
+
isActive,
|
|
18
|
+
className: joinClasses(tabClassName, isActive ? activeTabClassName : inactiveTabClassName),
|
|
19
|
+
children: tab.label,
|
|
20
|
+
});
|
|
21
|
+
}) }));
|
|
22
|
+
}
|
|
23
|
+
return (_jsx("nav", { "aria-label": ariaLabel, className: joinClasses(tabBarClassName, className), children: tabs.map((tab) => {
|
|
24
|
+
const isActive = tab.id === activeTab;
|
|
25
|
+
return (_jsx("button", { type: "button", "data-active": isActive, onClick: () => onTabChange?.(tab.id), className: joinClasses(tabClassName, isActive ? activeTabClassName : inactiveTabClassName), children: tab.label }, tab.id));
|
|
26
|
+
}) }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
export type ShellContentLayout = 'workspace' | 'centered' | 'fullbleed';
|
|
4
|
+
export type ShellViewportBand = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'ultrawide';
|
|
5
|
+
export interface ShellTab {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
to?: string;
|
|
9
|
+
icon?: LucideIcon;
|
|
10
|
+
}
|
|
11
|
+
export interface ShellNavItem {
|
|
12
|
+
to: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
icon: LucideIcon;
|
|
16
|
+
shortcut?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ShellRailItem {
|
|
19
|
+
to: string;
|
|
20
|
+
label: string;
|
|
21
|
+
icon: LucideIcon;
|
|
22
|
+
}
|
|
23
|
+
export interface ShellModuleDefinition {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
description: string;
|
|
27
|
+
items: ShellNavItem[];
|
|
28
|
+
headerTabs?: ShellTab[];
|
|
29
|
+
}
|
|
30
|
+
export interface ShellPanelDefinition {
|
|
31
|
+
id: string;
|
|
32
|
+
label: string;
|
|
33
|
+
icon: LucideIcon;
|
|
34
|
+
global?: boolean;
|
|
35
|
+
hidden?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ShellCommandDefinition {
|
|
38
|
+
id: string;
|
|
39
|
+
label: string;
|
|
40
|
+
to: string;
|
|
41
|
+
icon: LucideIcon;
|
|
42
|
+
group: string;
|
|
43
|
+
shortcut?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface ShellPanelRenderContext {
|
|
46
|
+
sectionKey: string;
|
|
47
|
+
viewId: string;
|
|
48
|
+
selectedProductId: string | null;
|
|
49
|
+
}
|
|
50
|
+
export type ShellPanelRenderer = (context: ShellPanelRenderContext) => ReactNode;
|
|
51
|
+
export interface ShellRouteContext {
|
|
52
|
+
pathname: string;
|
|
53
|
+
params: Record<string, string | undefined>;
|
|
54
|
+
}
|
|
55
|
+
export interface ShellHostAdapter {
|
|
56
|
+
route: ShellRouteContext;
|
|
57
|
+
navigate: (to: string) => void;
|
|
58
|
+
}
|
|
59
|
+
export interface ShellViewDefinition {
|
|
60
|
+
shellContentLayout?: ShellContentLayout;
|
|
61
|
+
shellTabs?: ShellTab[] | ((context: ShellRouteContext) => ShellTab[]);
|
|
62
|
+
shellActions?: ReactNode | ((context: ShellRouteContext) => ReactNode);
|
|
63
|
+
shellPanelViews?: string[];
|
|
64
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ShellViewportBand } from './types.js';
|
|
3
|
+
export interface WorkbenchLayoutProps {
|
|
4
|
+
viewportBand: ShellViewportBand;
|
|
5
|
+
main: ReactNode;
|
|
6
|
+
aside?: ReactNode;
|
|
7
|
+
toolbar?: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function WorkbenchLayout({ viewportBand, main, aside, toolbar, className, }: WorkbenchLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
function joinClasses(...values) {
|
|
3
|
+
return values.filter(Boolean).join(' ');
|
|
4
|
+
}
|
|
5
|
+
function getWorkbenchMaxWidth(viewportBand) {
|
|
6
|
+
if (viewportBand === 'desktop')
|
|
7
|
+
return 1440;
|
|
8
|
+
if (viewportBand === 'wide')
|
|
9
|
+
return 1760;
|
|
10
|
+
if (viewportBand === 'ultrawide')
|
|
11
|
+
return 1920;
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function WorkbenchLayout({ viewportBand, main, aside, toolbar, className, }) {
|
|
15
|
+
const maxWidth = getWorkbenchMaxWidth(viewportBand);
|
|
16
|
+
const layout = !aside
|
|
17
|
+
? 'single'
|
|
18
|
+
: viewportBand === 'mobile' || viewportBand === 'tablet'
|
|
19
|
+
? 'stacked'
|
|
20
|
+
: viewportBand === 'desktop'
|
|
21
|
+
? 'split'
|
|
22
|
+
: 'multi-zone';
|
|
23
|
+
const columnStyle = layout === 'split'
|
|
24
|
+
? { gridTemplateColumns: 'minmax(0, 1fr) minmax(280px, 320px)' }
|
|
25
|
+
: layout === 'multi-zone'
|
|
26
|
+
? { gridTemplateColumns: 'minmax(0, 1fr) minmax(300px, 360px)' }
|
|
27
|
+
: undefined;
|
|
28
|
+
return (_jsxs("section", { "data-testid": "workbench-layout", "data-band": viewportBand, className: joinClasses('mx-auto flex w-full flex-col gap-6', className), style: maxWidth ? { maxWidth: `${maxWidth}px` } : undefined, children: [toolbar ? _jsx("div", { "data-testid": "workbench-toolbar", children: toolbar }) : null, _jsxs("div", { "data-testid": "workbench-columns", "data-layout": layout, className: joinClasses('w-full gap-5', layout === 'stacked' ? 'flex flex-col' : layout === 'single' ? 'flex flex-col' : 'grid'), style: columnStyle, children: [_jsx("div", { "data-testid": "workbench-main", className: "min-w-0", children: main }), aside ? (_jsx("aside", { "data-testid": "workbench-aside", className: "min-w-0", children: aside })) : null] })] }));
|
|
29
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/shell/index.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export * from './
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export
|
|
15
|
-
export
|
|
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';
|