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

View file

@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { AppShell } from "@mantine/core";
import { useMemo, useState, type ReactNode } from "react";
import { AppShell, Box, Stack, Transition } from "@mantine/core";
import type {
InitializeResult,
LoggingLevel,
@ -35,11 +35,45 @@ const ALL_TABS: string[] = [
"Tools",
"Prompts",
"Resources",
"Logs",
"Tasks",
"Logs",
"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
// wiring will replace each with its real `useManaged*` / `useConnection`
// hook call. Anything still pointing here in Phase 3 is unfinished.
@ -204,84 +238,86 @@ export function InspectorView({
)}
</AppShell.Header>
<AppShell.Main>
{activeTab === SERVERS_TAB && (
<ServerListScreen
servers={servers}
activeServer={activeServer}
onAddManually={noop}
onImportConfig={noop}
onImportServerJson={noop}
onToggleConnection={handleToggleConnection}
onServerInfo={noop}
onSettings={noop}
onEdit={noop}
onClone={noop}
onRemove={noop}
/>
)}
{activeTab === "Tools" && (
<ToolsScreen
tools={tools}
listChanged={false}
onRefreshList={noop}
onSelectTool={noop}
onCallTool={noop}
/>
)}
{activeTab === "Prompts" && (
<PromptsScreen
prompts={prompts}
listChanged={false}
onRefreshList={noop}
onSelectPrompt={noop}
onGetPrompt={noop}
/>
)}
{activeTab === "Resources" && (
<ResourcesScreen
resources={resources}
templates={resourceTemplates}
subscriptions={subscriptions}
listChanged={false}
onRefreshList={noop}
onSelectUri={noop}
onSelectTemplate={noop}
onReadResource={noop}
onSubscribeResource={noop}
onUnsubscribeResource={noop}
/>
)}
{activeTab === "Logs" && (
<LoggingScreen
entries={logs}
currentLevel={logLevel}
onSetLevel={setLogLevel}
onClear={noop}
onExport={noop}
autoScroll={autoScroll}
onToggleAutoScroll={() => setAutoScroll((prev) => !prev)}
onCopyAll={noop}
/>
)}
{activeTab === "Tasks" && (
<TasksScreen
tasks={tasks}
progressByTaskId={progressByTaskId}
onRefresh={noop}
onClearCompleted={noop}
onCancel={noop}
/>
)}
{activeTab === "History" && (
<HistoryScreen
entries={history}
pinnedIds={new Set()}
onClearAll={noop}
onExport={noop}
onReplay={noop}
onTogglePin={noop}
/>
)}
<ScreenStageContainer>
<ScreenStage active={activeTab === SERVERS_TAB}>
<ServerListScreen
servers={servers}
activeServer={activeServer}
onAddManually={noop}
onImportConfig={noop}
onImportServerJson={noop}
onToggleConnection={handleToggleConnection}
onServerInfo={noop}
onSettings={noop}
onEdit={noop}
onClone={noop}
onRemove={noop}
/>
</ScreenStage>
<ScreenStage active={activeTab === "Tools"}>
<ToolsScreen
tools={tools}
listChanged={false}
onRefreshList={noop}
onSelectTool={noop}
onCallTool={noop}
/>
</ScreenStage>
<ScreenStage active={activeTab === "Prompts"}>
<PromptsScreen
prompts={prompts}
listChanged={false}
onRefreshList={noop}
onSelectPrompt={noop}
onGetPrompt={noop}
/>
</ScreenStage>
<ScreenStage active={activeTab === "Resources"}>
<ResourcesScreen
resources={resources}
templates={resourceTemplates}
subscriptions={subscriptions}
listChanged={false}
onRefreshList={noop}
onSelectUri={noop}
onSelectTemplate={noop}
onReadResource={noop}
onSubscribeResource={noop}
onUnsubscribeResource={noop}
/>
</ScreenStage>
<ScreenStage active={activeTab === "Tasks"}>
<TasksScreen
tasks={tasks}
progressByTaskId={progressByTaskId}
onRefresh={noop}
onClearCompleted={noop}
onCancel={noop}
/>
</ScreenStage>
<ScreenStage active={activeTab === "Logs"}>
<LoggingScreen
entries={logs}
currentLevel={logLevel}
onSetLevel={setLogLevel}
onClear={noop}
onExport={noop}
autoScroll={autoScroll}
onToggleAutoScroll={() => setAutoScroll((prev) => !prev)}
onCopyAll={noop}
/>
</ScreenStage>
<ScreenStage active={activeTab === "History"}>
<HistoryScreen
entries={history}
pinnedIds={new Set()}
onClearAll={noop}
onExport={noop}
onReplay={noop}
onTogglePin={noop}
/>
</ScreenStage>
</ScreenStageContainer>
</AppShell.Main>
</AppShell>
);