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
6 changed files with 1602 additions and 790 deletions
|
|
@ -13,16 +13,13 @@ export interface AnnotationBadgeProps {
|
|||
variant?: AnnotationVariant;
|
||||
}
|
||||
|
||||
const variantMap: Record<
|
||||
AnnotationVariant,
|
||||
{ color: string; variant: string }
|
||||
> = {
|
||||
audience: { color: "blue", variant: "outline" },
|
||||
readOnly: { color: "green", variant: "filled" },
|
||||
destructive: { color: "red", variant: "filled" },
|
||||
longRun: { color: "yellow", variant: "filled" },
|
||||
priority: { color: "orange", variant: "filled" },
|
||||
default: { color: "gray", variant: "light" },
|
||||
const colorMap: Record<AnnotationVariant, string> = {
|
||||
audience: "blue",
|
||||
readOnly: "green",
|
||||
destructive: "red",
|
||||
longRun: "yellow",
|
||||
priority: "orange",
|
||||
default: "gray",
|
||||
};
|
||||
|
||||
export function AnnotationBadge({
|
||||
|
|
@ -30,15 +27,10 @@ export function AnnotationBadge({
|
|||
variant = "default",
|
||||
}: AnnotationBadgeProps) {
|
||||
const colorScheme = useComputedColorScheme();
|
||||
const { color, variant: badgeVariant } = variantMap[variant];
|
||||
const textColor =
|
||||
(variant === "priority" || variant === "destructive") &&
|
||||
colorScheme === "dark"
|
||||
? "black"
|
||||
: undefined;
|
||||
|
||||
const color = colorMap[variant];
|
||||
const textColor = colorScheme === "dark" ? "black" : "white";
|
||||
return (
|
||||
<Badge color={color} variant={badgeVariant} c={textColor}>
|
||||
<Badge color={color} variant="filled" fw={500} c={textColor}>
|
||||
{label}
|
||||
</Badge>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { ListToggle } from "./ListToggle";
|
||||
|
||||
const meta: Meta<typeof ListToggle> = {
|
||||
title: "Elements/ListToggle",
|
||||
component: ListToggle,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ListToggle>;
|
||||
|
||||
export const Expanded: Story = {
|
||||
args: {
|
||||
compact: false,
|
||||
onToggle: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export const Collapsed: Story = {
|
||||
args: {
|
||||
compact: true,
|
||||
onToggle: fn(),
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { Button } from "@mantine/core";
|
||||
import { RiExpandVerticalLine, RiCollapseVerticalLine } from "react-icons/ri";
|
||||
|
||||
export interface ListToggleProps {
|
||||
compact: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
export function ListToggle({ compact, onToggle }: ListToggleProps) {
|
||||
const Icon = compact ? RiExpandVerticalLine : RiCollapseVerticalLine;
|
||||
|
||||
return (
|
||||
<Button size="sm" onClick={onToggle}>
|
||||
<Icon size={20} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { Badge, Group, Text } from "@mantine/core";
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { LogLevelBadge } from "../LogLevelBadge/LogLevelBadge";
|
||||
|
||||
export type LogLevel =
|
||||
| "debug"
|
||||
|
|
@ -17,28 +18,6 @@ export interface LogEntryProps {
|
|||
logger?: string;
|
||||
}
|
||||
|
||||
const levelBadgeColor: Record<LogLevel, string> = {
|
||||
debug: "gray",
|
||||
info: "blue",
|
||||
notice: "teal",
|
||||
warning: "yellow",
|
||||
error: "red",
|
||||
critical: "red",
|
||||
alert: "red",
|
||||
emergency: "red",
|
||||
};
|
||||
|
||||
const levelBadgeVariant: Record<LogLevel, string> = {
|
||||
debug: "light",
|
||||
info: "light",
|
||||
notice: "light",
|
||||
warning: "light",
|
||||
error: "light",
|
||||
critical: "filled",
|
||||
alert: "filled",
|
||||
emergency: "filled",
|
||||
};
|
||||
|
||||
const levelMessageColor: Record<LogLevel, string | undefined> = {
|
||||
debug: "dimmed",
|
||||
info: "blue",
|
||||
|
|
@ -50,14 +29,6 @@ const levelMessageColor: Record<LogLevel, string | undefined> = {
|
|||
emergency: "red",
|
||||
};
|
||||
|
||||
function isBoldLevel(level: LogLevel): boolean {
|
||||
return level === "alert" || level === "emergency";
|
||||
}
|
||||
|
||||
function getBoldWeight(level: LogLevel): number | undefined {
|
||||
return isBoldLevel(level) ? 700 : undefined;
|
||||
}
|
||||
|
||||
function formatLogger(logger: string): string {
|
||||
return `[${logger}]`;
|
||||
}
|
||||
|
|
@ -70,6 +41,7 @@ const TimestampText = Text.withProps({
|
|||
|
||||
const LoggerText = Text.withProps({
|
||||
size: "xs",
|
||||
ff: "monospace",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
|
|
@ -77,15 +49,9 @@ export function LogEntry({ timestamp, level, message, logger }: LogEntryProps) {
|
|||
return (
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<TimestampText>{timestamp}</TimestampText>
|
||||
<Badge
|
||||
color={levelBadgeColor[level]}
|
||||
variant={levelBadgeVariant[level]}
|
||||
fw={getBoldWeight(level)}
|
||||
>
|
||||
{level}
|
||||
</Badge>
|
||||
<LogLevelBadge level={level} />
|
||||
{logger && <LoggerText>{formatLogger(logger)}</LoggerText>}
|
||||
<Text size="sm" c={levelMessageColor[level]}>
|
||||
<Text size="sm" ff="monospace" c={levelMessageColor[level]}>
|
||||
{message}
|
||||
</Text>
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { LogLevelBadge } from "./LogLevelBadge";
|
||||
|
||||
const meta: Meta<typeof LogLevelBadge> = {
|
||||
title: "Elements/LogLevelBadge",
|
||||
component: LogLevelBadge,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof LogLevelBadge>;
|
||||
|
||||
export const Debug: Story = { args: { level: "debug" } };
|
||||
export const Info: Story = { args: { level: "info" } };
|
||||
export const Notice: Story = { args: { level: "notice" } };
|
||||
export const Warning: Story = { args: { level: "warning" } };
|
||||
export const Error: Story = { args: { level: "error" } };
|
||||
export const Critical: Story = { args: { level: "critical" } };
|
||||
export const Alert: Story = { args: { level: "alert" } };
|
||||
export const Emergency: Story = { args: { level: "emergency" } };
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { Badge, useComputedColorScheme } from "@mantine/core";
|
||||
import type { LogLevel } from "../LogEntry/LogEntry";
|
||||
|
||||
export interface LogLevelBadgeProps {
|
||||
level: LogLevel;
|
||||
}
|
||||
|
||||
const levelColor: Record<LogLevel, string> = {
|
||||
debug: "gray",
|
||||
info: "blue",
|
||||
notice: "teal",
|
||||
warning: "yellow",
|
||||
error: "red",
|
||||
critical: "red",
|
||||
alert: "red",
|
||||
emergency: "red",
|
||||
};
|
||||
|
||||
const boldLevels: Set<LogLevel> = new Set(["alert", "emergency"]);
|
||||
|
||||
export function LogLevelBadge({ level }: LogLevelBadgeProps) {
|
||||
const colorScheme = useComputedColorScheme();
|
||||
const textColor = colorScheme === "dark" ? "black" : "white";
|
||||
const fw = boldLevels.has(level) ? 500 : undefined;
|
||||
|
||||
return (
|
||||
<Badge color={levelColor[level]} variant="filled" fw={fw} c={textColor}>
|
||||
{level}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue