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:
cliffhall 2026-03-28 20:38:04 -04:00
commit 961dc283fc

View file

@ -36,6 +36,11 @@ const ToolbarButton = Button.withProps({
size: "sm",
});
const SubtleButton = Button.withProps({
variant: "subtle",
size: "xs",
});
export interface LogControlsProps {
currentLevel: string;
filterText: string;
@ -43,6 +48,7 @@ export interface LogControlsProps {
onSetLevel: (level: string) => void;
onFilterChange: (text: string) => void;
onToggleLevel: (level: string, visible: boolean) => void;
onToggleAllLevels: () => void;
onClear: () => void;
onExport: () => void;
}
@ -54,13 +60,16 @@ export function LogControls({
onSetLevel,
onFilterChange,
onToggleLevel,
onToggleAllLevels,
onClear,
onExport,
}: LogControlsProps) {
return (
<Stack gap="md">
<Title order={5}>Log Level</Title>
<Group>
<Title order={4}>Logging</Title>
<Title order={5}>Active Level</Title>
<Group wrap="nowrap">
<Select
data={LOG_LEVELS.map((level) => ({ value: level, label: level }))}
value={currentLevel}
@ -69,18 +78,25 @@ export function LogControls({
}}
/>
<Button size="sm" onClick={() => onSetLevel(currentLevel)}>
Set Level
Set
</Button>
</Group>
<Title order={5}>Filter</Title>
<Title order={5}>Search</Title>
<TextInput
placeholder="Search logs..."
value={filterText}
onChange={(e) => onFilterChange(e.currentTarget.value)}
/>
<Title order={5}>Show Levels</Title>
<Group justify="space-between">
<Title order={5}>Filter by Level</Title>
<SubtleButton onClick={onToggleAllLevels}>
{Object.values(visibleLevels).every(Boolean)
? "Deselect All"
: "Select All"}
</SubtleButton>
</Group>
{LOG_LEVELS.map((level) => {
const style = LEVEL_COLORS[level];
return (