Animate InspectorView screen transitions and refactor App.tsx

- InspectorView: wrap each screen in a Mantine `Transition` (`fade-up`,
  350ms). Outgoing screens fade out and slide down 30px; incoming screens
  fade in and slide up to meet the ViewHeader. A `pos: "relative"` Stack
  container holds the screens so the absolute-positioned `Transition`
  wrappers stack in the same area without pushing layout.
- InspectorView: reorder ALL_TABS so Logs sits beside History at the end:
  Servers · Tools · Prompts · Resources · Tasks · Logs · History.
- App.tsx: refactor to AGENTS.md guidelines — extract `PageContainer`,
  `PageStack`, `ThemeToggle`, `SubtitleText` as `.withProps()`
  subcomponents; replace inline unicode glyphs with `MdLightMode` /
  `MdDarkMode` icons (matches the ViewHeader pattern); convert the click
  handler to a `function` declaration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-04-25 19:33:57 -04:00
commit d3c2645e38
2 changed files with 157 additions and 103 deletions

View file

@ -1,41 +1,59 @@
import { import {
Title,
Text,
Container,
Stack,
useMantineColorScheme,
useComputedColorScheme,
ActionIcon, ActionIcon,
Container,
Group, Group,
Stack,
Text,
Title,
useComputedColorScheme,
useMantineColorScheme,
} from "@mantine/core"; } from "@mantine/core";
import { MdDarkMode, MdLightMode } from "react-icons/md";
const PageContainer = Container.withProps({
size: "sm",
py: "xl",
});
const PageStack = Stack.withProps({
align: "center",
gap: "md",
});
const ThemeToggle = ActionIcon.withProps({
variant: "default",
size: "lg",
"aria-label": "Toggle color scheme",
});
const SubtitleText = Text.withProps({
c: "var(--inspector-text-secondary)",
});
function App() { function App() {
const { setColorScheme } = useMantineColorScheme(); const { setColorScheme } = useMantineColorScheme();
const computedColorScheme = useComputedColorScheme("light"); const computedColorScheme = useComputedColorScheme("light");
const isDark = computedColorScheme === "dark";
const ThemeIcon = isDark ? MdLightMode : MdDarkMode;
const toggleColorScheme = () => { function toggleColorScheme() {
setColorScheme(computedColorScheme === "dark" ? "light" : "dark"); setColorScheme(isDark ? "light" : "dark");
}; }
return ( return (
<Container size="sm" py="xl"> <PageContainer>
<Stack align="center" gap="md"> <PageStack>
<Group> <Group>
<Title order={1}>MCP Inspector</Title> <Title order={1}>MCP Inspector</Title>
<ActionIcon <ThemeToggle onClick={toggleColorScheme}>
onClick={toggleColorScheme} <ThemeIcon size={20} />
variant="default" </ThemeToggle>
size="lg"
aria-label="Toggle color scheme"
>
{computedColorScheme === "dark" ? "\u2600" : "\u263E"}
</ActionIcon>
</Group> </Group>
<Text c="var(--inspector-text-secondary)"> <SubtitleText>
Web client for the Model Context Protocol Inspector Web client for the Model Context Protocol Inspector
</Text> </SubtitleText>
</Stack> </PageStack>
</Container> </PageContainer>
); );
} }

View file

@ -1,5 +1,5 @@
import { useMemo, useState } from "react"; import { useMemo, useState, type ReactNode } from "react";
import { AppShell } from "@mantine/core"; import { AppShell, Box, Stack, Transition } from "@mantine/core";
import type { import type {
InitializeResult, InitializeResult,
LoggingLevel, LoggingLevel,
@ -35,11 +35,45 @@ const ALL_TABS: string[] = [
"Tools", "Tools",
"Prompts", "Prompts",
"Resources", "Resources",
"Logs",
"Tasks", "Tasks",
"Logs",
"History", "History",
]; ];
const SCREEN_TRANSITION_MS = 350;
const ScreenStageContainer = Stack.withProps({
pos: "relative",
gap: 0,
flex: 1,
mih: "100%",
});
function ScreenStage({
active,
children,
}: {
active: boolean;
children: ReactNode;
}) {
return (
<Transition
mounted={active}
transition="fade-up"
duration={SCREEN_TRANSITION_MS}
timingFunction="ease"
>
{(styles) => (
// `style={styles}` is the runtime transition state from Mantine's
// Transition API — these are interpolated values, not static styling.
<Box style={styles} pos="absolute" top={0} left={0} right={0}>
{children}
</Box>
)}
</Transition>
);
}
// Demo stub: every screen-action callback below resolves to noop. Phase 3 // Demo stub: every screen-action callback below resolves to noop. Phase 3
// wiring will replace each with its real `useManaged*` / `useConnection` // wiring will replace each with its real `useManaged*` / `useConnection`
// hook call. Anything still pointing here in Phase 3 is unfinished. // hook call. Anything still pointing here in Phase 3 is unfinished.
@ -204,84 +238,86 @@ export function InspectorView({
)} )}
</AppShell.Header> </AppShell.Header>
<AppShell.Main> <AppShell.Main>
{activeTab === SERVERS_TAB && ( <ScreenStageContainer>
<ServerListScreen <ScreenStage active={activeTab === SERVERS_TAB}>
servers={servers} <ServerListScreen
activeServer={activeServer} servers={servers}
onAddManually={noop} activeServer={activeServer}
onImportConfig={noop} onAddManually={noop}
onImportServerJson={noop} onImportConfig={noop}
onToggleConnection={handleToggleConnection} onImportServerJson={noop}
onServerInfo={noop} onToggleConnection={handleToggleConnection}
onSettings={noop} onServerInfo={noop}
onEdit={noop} onSettings={noop}
onClone={noop} onEdit={noop}
onRemove={noop} onClone={noop}
/> onRemove={noop}
)} />
{activeTab === "Tools" && ( </ScreenStage>
<ToolsScreen <ScreenStage active={activeTab === "Tools"}>
tools={tools} <ToolsScreen
listChanged={false} tools={tools}
onRefreshList={noop} listChanged={false}
onSelectTool={noop} onRefreshList={noop}
onCallTool={noop} onSelectTool={noop}
/> onCallTool={noop}
)} />
{activeTab === "Prompts" && ( </ScreenStage>
<PromptsScreen <ScreenStage active={activeTab === "Prompts"}>
prompts={prompts} <PromptsScreen
listChanged={false} prompts={prompts}
onRefreshList={noop} listChanged={false}
onSelectPrompt={noop} onRefreshList={noop}
onGetPrompt={noop} onSelectPrompt={noop}
/> onGetPrompt={noop}
)} />
{activeTab === "Resources" && ( </ScreenStage>
<ResourcesScreen <ScreenStage active={activeTab === "Resources"}>
resources={resources} <ResourcesScreen
templates={resourceTemplates} resources={resources}
subscriptions={subscriptions} templates={resourceTemplates}
listChanged={false} subscriptions={subscriptions}
onRefreshList={noop} listChanged={false}
onSelectUri={noop} onRefreshList={noop}
onSelectTemplate={noop} onSelectUri={noop}
onReadResource={noop} onSelectTemplate={noop}
onSubscribeResource={noop} onReadResource={noop}
onUnsubscribeResource={noop} onSubscribeResource={noop}
/> onUnsubscribeResource={noop}
)} />
{activeTab === "Logs" && ( </ScreenStage>
<LoggingScreen <ScreenStage active={activeTab === "Tasks"}>
entries={logs} <TasksScreen
currentLevel={logLevel} tasks={tasks}
onSetLevel={setLogLevel} progressByTaskId={progressByTaskId}
onClear={noop} onRefresh={noop}
onExport={noop} onClearCompleted={noop}
autoScroll={autoScroll} onCancel={noop}
onToggleAutoScroll={() => setAutoScroll((prev) => !prev)} />
onCopyAll={noop} </ScreenStage>
/> <ScreenStage active={activeTab === "Logs"}>
)} <LoggingScreen
{activeTab === "Tasks" && ( entries={logs}
<TasksScreen currentLevel={logLevel}
tasks={tasks} onSetLevel={setLogLevel}
progressByTaskId={progressByTaskId} onClear={noop}
onRefresh={noop} onExport={noop}
onClearCompleted={noop} autoScroll={autoScroll}
onCancel={noop} onToggleAutoScroll={() => setAutoScroll((prev) => !prev)}
/> onCopyAll={noop}
)} />
{activeTab === "History" && ( </ScreenStage>
<HistoryScreen <ScreenStage active={activeTab === "History"}>
entries={history} <HistoryScreen
pinnedIds={new Set()} entries={history}
onClearAll={noop} pinnedIds={new Set()}
onExport={noop} onClearAll={noop}
onReplay={noop} onExport={noop}
onTogglePin={noop} onReplay={noop}
/> onTogglePin={noop}
)} />
</ScreenStage>
</ScreenStageContainer>
</AppShell.Main> </AppShell.Main>
</AppShell> </AppShell>
); );