Add new components, typography, badge contrast, and screen layout refactors
New element components: - ListToggle: expand/collapse button using react-icons (RiExpandVerticalLine/RiCollapseVerticalLine) - LogLevelBadge: extracted from LogEntry, handles all 8 log levels with filled variant and dark/light text contrast New group components: - ViewHeader: shared header bar for ConnectedView and UnconnectedView with discriminated union on `connected` prop; MCP logo links to modelcontextprotocol.io - ServerListControls: combines ListToggle and AddServerMenu, hides toggle when no servers - LogStreamPanel: extracted from LoggingScreen with scroll containment, filtering by visible levels and search text - HistoryControls: vertical sidebar with search, method filter, export, and clear - HistoryListPanel: extracted from HistoryScreen with ListToggle compact mode, filtering by method and search text Badge contrast improvements: - AnnotationBadge: filled variant for both dark/light modes; black text in dark, white text in light - LogLevelBadge: same contrast approach; bold weight for alert/emergency levels - Badge theme default weight reduced from 700 to 600 Typography: - Added Fredoka (300-700) as body and heading font - Added Roboto Mono as monospace font - Google Fonts loaded in index.html and Storybook previewHead - Heading weight set to 600 Screen layout refactors: - LoggingScreen: owns filterText and visibleLevels state; passes to LogControls and LogStreamPanel; removed controls prop in favor of flat props - HistoryScreen: owns searchText and methodFilter state; passes to HistoryControls and HistoryListPanel for filtering - Both screens use new ScreenLayout (Flex variant="screen") and Sidebar subcomponents - Added Flex theme with "screen" variant (overflow: hidden) - Added Paper theme "panel" variant (flex column, overflow hidden) Component enhancements: - ServerCard: added compact prop showing only header row - HistoryEntry: renamed isExpanded to isListExpanded; internal useState syncs via useEffect; individual entries toggle independently - LogControls: Select All / Deselect All toggle for level checkboxes; nowrap on level selector group - ConnectionToggle: LoadingOverlay with rounded corners for connecting state - ConnectedView: ThemeToggle ActionIcon matched to DisconnectButton height (36px) and border color (outline variant) - Storybook preview: fullscreen layout wrapper with 100vh overflow hidden New stories: - UnconnectedView: Connecting and ManyServers (12 servers for scroll testing) - ServerCard: CompactConnected and CompactDisconnected - All new components have corresponding stories Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dd46acba87
commit
961dc283fc
2 changed files with 1602 additions and 790 deletions
|
|
@ -1,39 +1,20 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { LoggingScreen } from "./LoggingScreen";
|
||||
import type { LogControlsProps } from "../../groups/LogControls/LogControls";
|
||||
import type { LogEntryProps, LogLevel } from "../../elements/LogEntry/LogEntry";
|
||||
|
||||
function makeControls(): LogControlsProps {
|
||||
return {
|
||||
currentLevel: "info",
|
||||
filterText: "",
|
||||
visibleLevels: {
|
||||
debug: true,
|
||||
info: true,
|
||||
notice: true,
|
||||
warning: true,
|
||||
error: true,
|
||||
critical: true,
|
||||
alert: true,
|
||||
emergency: true,
|
||||
},
|
||||
onSetLevel: fn(),
|
||||
onFilterChange: fn(),
|
||||
onToggleLevel: fn(),
|
||||
onClear: fn(),
|
||||
onExport: fn(),
|
||||
};
|
||||
}
|
||||
|
||||
const meta: Meta<typeof LoggingScreen> = {
|
||||
title: "Screens/LoggingScreen",
|
||||
component: LoggingScreen,
|
||||
parameters: { layout: "fullscreen" },
|
||||
args: {
|
||||
currentLevel: "info",
|
||||
onSetLevel: fn(),
|
||||
onClear: fn(),
|
||||
onExport: fn(),
|
||||
autoScroll: true,
|
||||
onToggleAutoScroll: fn(),
|
||||
onCopyAll: fn(),
|
||||
autoScroll: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -42,7 +23,6 @@ type Story = StoryObj<typeof LoggingScreen>;
|
|||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
controls: makeControls(),
|
||||
entries: [],
|
||||
},
|
||||
};
|
||||
|
|
@ -137,7 +117,8 @@ const logMessages: { level: LogLevel; message: string; logger?: string }[] = [
|
|||
},
|
||||
{
|
||||
level: "info",
|
||||
message: "Session initialized with capabilities: tools, resources, prompts",
|
||||
message:
|
||||
"Session initialized with capabilities: tools, resources, prompts",
|
||||
},
|
||||
{
|
||||
level: "debug",
|
||||
|
|
@ -183,7 +164,6 @@ const mixedEntries: LogEntryProps[] = Array.from({ length: 50 }, (_, i) => {
|
|||
|
||||
export const WithEntries: Story = {
|
||||
args: {
|
||||
controls: makeControls(),
|
||||
entries: mixedEntries,
|
||||
},
|
||||
};
|
||||
|
|
@ -240,7 +220,6 @@ const allLevelEntries: LogEntryProps[] = [
|
|||
|
||||
export const MixedLevels: Story = {
|
||||
args: {
|
||||
controls: makeControls(),
|
||||
entries: allLevelEntries,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,31 +1,41 @@
|
|||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Container,
|
||||
Grid,
|
||||
Group,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import { Card, Flex, Stack } from "@mantine/core";
|
||||
import { LogControls } from "../../groups/LogControls/LogControls";
|
||||
import { LogEntry } from "../../elements/LogEntry/LogEntry";
|
||||
import type { LogControlsProps } from "../../groups/LogControls/LogControls";
|
||||
import { LogStreamPanel } from "../../groups/LogStreamPanel/LogStreamPanel";
|
||||
import type { LogEntryProps } from "../../elements/LogEntry/LogEntry";
|
||||
|
||||
export interface LoggingScreenProps {
|
||||
controls: LogControlsProps;
|
||||
entries: LogEntryProps[];
|
||||
currentLevel: string;
|
||||
onSetLevel: (level: string) => void;
|
||||
onClear: () => void;
|
||||
onExport: () => void;
|
||||
autoScroll: boolean;
|
||||
onToggleAutoScroll: () => void;
|
||||
onCopyAll: () => void;
|
||||
}
|
||||
|
||||
const PageContainer = Container.withProps({
|
||||
size: "xl",
|
||||
py: "xl",
|
||||
const ALL_LEVELS_VISIBLE: Record<string, boolean> = {
|
||||
debug: true,
|
||||
info: true,
|
||||
notice: true,
|
||||
warning: true,
|
||||
error: true,
|
||||
critical: true,
|
||||
alert: true,
|
||||
emergency: true,
|
||||
};
|
||||
|
||||
const ScreenLayout = Flex.withProps({
|
||||
variant: "screen",
|
||||
h: "calc(100vh - var(--app-shell-header-height, 0px))",
|
||||
gap: "xl",
|
||||
p: "xl",
|
||||
});
|
||||
|
||||
const Sidebar = Stack.withProps({
|
||||
w: 280,
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
const SidebarCard = Card.withProps({
|
||||
|
|
@ -33,74 +43,58 @@ const SidebarCard = Card.withProps({
|
|||
padding: "lg",
|
||||
});
|
||||
|
||||
const FullHeightCard = Card.withProps({
|
||||
withBorder: true,
|
||||
padding: "lg",
|
||||
h: "100%",
|
||||
});
|
||||
|
||||
const FullHeightStack = Stack.withProps({
|
||||
gap: "sm",
|
||||
h: "100%",
|
||||
});
|
||||
|
||||
const ToolbarButton = Button.withProps({
|
||||
variant: "light",
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
const EmptyCenter = Stack.withProps({
|
||||
flex: 1,
|
||||
align: "center",
|
||||
justify: "center",
|
||||
});
|
||||
|
||||
export function LoggingScreen({
|
||||
controls,
|
||||
entries,
|
||||
currentLevel,
|
||||
onSetLevel,
|
||||
onClear,
|
||||
onExport,
|
||||
autoScroll,
|
||||
onToggleAutoScroll,
|
||||
onCopyAll,
|
||||
}: LoggingScreenProps) {
|
||||
const [filterText, setFilterText] = useState("");
|
||||
const [visibleLevels, setVisibleLevels] =
|
||||
useState<Record<string, boolean>>(ALL_LEVELS_VISIBLE);
|
||||
|
||||
function handleToggleLevel(level: string, visible: boolean) {
|
||||
setVisibleLevels((prev) => ({ ...prev, [level]: visible }));
|
||||
}
|
||||
|
||||
const NO_LEVELS_VISIBLE: Record<string, boolean> = Object.fromEntries(
|
||||
Object.keys(ALL_LEVELS_VISIBLE).map((k) => [k, false]),
|
||||
);
|
||||
|
||||
function handleToggleAllLevels() {
|
||||
const allSelected = Object.values(visibleLevels).every(Boolean);
|
||||
setVisibleLevels(allSelected ? NO_LEVELS_VISIBLE : ALL_LEVELS_VISIBLE);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Grid align="stretch">
|
||||
<Grid.Col span={3}>
|
||||
<SidebarCard>
|
||||
<LogControls {...controls} />
|
||||
</SidebarCard>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={9}>
|
||||
<FullHeightCard>
|
||||
<FullHeightStack>
|
||||
<Group justify="space-between">
|
||||
<Title order={4}>Log Stream</Title>
|
||||
<Group>
|
||||
<Checkbox
|
||||
label="Auto-scroll"
|
||||
checked={autoScroll}
|
||||
onChange={onToggleAutoScroll}
|
||||
/>
|
||||
<ToolbarButton onClick={onCopyAll}>Copy All</ToolbarButton>
|
||||
</Group>
|
||||
</Group>
|
||||
{entries.length > 0 ? (
|
||||
<ScrollArea flex={1}>
|
||||
<Stack gap="xs">
|
||||
{entries.map((entry, index) => (
|
||||
<LogEntry key={index} {...entry} />
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<EmptyCenter>
|
||||
<Text c="dimmed">No log entries</Text>
|
||||
</EmptyCenter>
|
||||
)}
|
||||
</FullHeightStack>
|
||||
</FullHeightCard>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</PageContainer>
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<LogControls
|
||||
currentLevel={currentLevel}
|
||||
filterText={filterText}
|
||||
visibleLevels={visibleLevels}
|
||||
onSetLevel={onSetLevel}
|
||||
onFilterChange={setFilterText}
|
||||
onToggleLevel={handleToggleLevel}
|
||||
onToggleAllLevels={handleToggleAllLevels}
|
||||
onClear={onClear}
|
||||
onExport={onExport}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
<LogStreamPanel
|
||||
entries={entries}
|
||||
filterText={filterText}
|
||||
visibleLevels={visibleLevels}
|
||||
autoScroll={autoScroll}
|
||||
onToggleAutoScroll={onToggleAutoScroll}
|
||||
onCopyAll={onCopyAll}
|
||||
/>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue