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
5 changed files with 1602 additions and 790 deletions
|
|
@ -8,10 +8,6 @@ const meta: Meta<typeof HistoryScreen> = {
|
|||
component: HistoryScreen,
|
||||
parameters: { layout: "fullscreen" },
|
||||
args: {
|
||||
searchText: "",
|
||||
onSearchChange: fn(),
|
||||
onMethodFilterChange: fn(),
|
||||
onLoadMore: fn(),
|
||||
onClearAll: fn(),
|
||||
onExport: fn(),
|
||||
},
|
||||
|
|
@ -29,8 +25,7 @@ const entry1: HistoryEntryProps = {
|
|||
parameters: { message: "Hello, world!" },
|
||||
response: { result: "Message sent successfully" },
|
||||
isPinned: false,
|
||||
isExpanded: false,
|
||||
onToggleExpand: fn(),
|
||||
isListExpanded: false,
|
||||
onReplay: fn(),
|
||||
onTogglePin: fn(),
|
||||
};
|
||||
|
|
@ -44,8 +39,7 @@ const entry2: HistoryEntryProps = {
|
|||
parameters: { uri: "file:///config.json" },
|
||||
response: { contents: [{ uri: "file:///config.json", text: "{}" }] },
|
||||
isPinned: false,
|
||||
isExpanded: false,
|
||||
onToggleExpand: fn(),
|
||||
isListExpanded: false,
|
||||
onReplay: fn(),
|
||||
onTogglePin: fn(),
|
||||
};
|
||||
|
|
@ -59,8 +53,7 @@ const entry3: HistoryEntryProps = {
|
|||
parameters: { ids: [1, 2, 3] },
|
||||
response: { error: "Permission denied" },
|
||||
isPinned: false,
|
||||
isExpanded: true,
|
||||
onToggleExpand: fn(),
|
||||
isListExpanded: true,
|
||||
onReplay: fn(),
|
||||
onTogglePin: fn(),
|
||||
};
|
||||
|
|
@ -72,9 +65,7 @@ const pinnedEntry1: HistoryEntryProps = {
|
|||
durationMs: 80,
|
||||
response: { tools: ["send_message", "list_users"] },
|
||||
isPinned: true,
|
||||
pinLabel: "Tool discovery",
|
||||
isExpanded: false,
|
||||
onToggleExpand: fn(),
|
||||
isListExpanded: false,
|
||||
onReplay: fn(),
|
||||
onTogglePin: fn(),
|
||||
};
|
||||
|
|
@ -90,9 +81,7 @@ const pinnedEntry2: HistoryEntryProps = {
|
|||
messages: [{ role: "user", content: { type: "text", text: "Hello!" } }],
|
||||
},
|
||||
isPinned: true,
|
||||
pinLabel: "Greeting prompt",
|
||||
isExpanded: false,
|
||||
onToggleExpand: fn(),
|
||||
isListExpanded: false,
|
||||
onReplay: fn(),
|
||||
onTogglePin: fn(),
|
||||
};
|
||||
|
|
@ -101,8 +90,6 @@ export const WithEntries: Story = {
|
|||
args: {
|
||||
entries: [entry1, entry2, entry3],
|
||||
pinnedEntries: [pinnedEntry1, pinnedEntry2],
|
||||
totalCount: 25,
|
||||
displayedCount: 3,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -110,17 +97,5 @@ export const Empty: Story = {
|
|||
args: {
|
||||
entries: [],
|
||||
pinnedEntries: [],
|
||||
totalCount: 0,
|
||||
displayedCount: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithFilter: Story = {
|
||||
args: {
|
||||
entries: [entry1, entry3],
|
||||
pinnedEntries: [],
|
||||
methodFilter: "tools/call",
|
||||
totalCount: 10,
|
||||
displayedCount: 2,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,155 +1,64 @@
|
|||
import {
|
||||
Button,
|
||||
Container,
|
||||
Divider,
|
||||
Group,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { HistoryEntry } from "../../groups/HistoryEntry/HistoryEntry";
|
||||
import { useState } from "react";
|
||||
import { Card, Flex, Stack } from "@mantine/core";
|
||||
import { HistoryControls } from "../../groups/HistoryControls/HistoryControls";
|
||||
import { HistoryListPanel } from "../../groups/HistoryListPanel/HistoryListPanel.js";
|
||||
import type { HistoryEntryProps } from "../../groups/HistoryEntry/HistoryEntry";
|
||||
|
||||
export interface HistoryScreenProps {
|
||||
entries: HistoryEntryProps[];
|
||||
pinnedEntries: HistoryEntryProps[];
|
||||
searchText: string;
|
||||
methodFilter?: string;
|
||||
totalCount: number;
|
||||
displayedCount: number;
|
||||
onSearchChange: (text: string) => void;
|
||||
onMethodFilterChange: (method: string) => void;
|
||||
onLoadMore: () => void;
|
||||
onClearAll: () => void;
|
||||
onExport: () => void;
|
||||
}
|
||||
|
||||
const METHOD_OPTIONS = [
|
||||
"tools/call",
|
||||
"tools/list",
|
||||
"resources/read",
|
||||
"resources/list",
|
||||
"prompts/get",
|
||||
"prompts/list",
|
||||
"sampling/createMessage",
|
||||
"elicitation/create",
|
||||
];
|
||||
|
||||
const PageContainer = Container.withProps({
|
||||
size: "xl",
|
||||
py: "xl",
|
||||
const ScreenLayout = Flex.withProps({
|
||||
variant: "screen",
|
||||
h: "calc(100vh - var(--app-shell-header-height, 0px))",
|
||||
gap: "xl",
|
||||
p: "xl",
|
||||
});
|
||||
|
||||
const ContentPanel = Paper.withProps({
|
||||
const Sidebar = Stack.withProps({
|
||||
w: 280,
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
const SidebarCard = Card.withProps({
|
||||
withBorder: true,
|
||||
p: "md",
|
||||
padding: "lg",
|
||||
});
|
||||
|
||||
const HeaderRow = Group.withProps({
|
||||
justify: "space-between",
|
||||
wrap: "wrap",
|
||||
});
|
||||
|
||||
const ToolbarButton = Button.withProps({
|
||||
variant: "light",
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
const EmptyState = Text.withProps({
|
||||
c: "dimmed",
|
||||
ta: "center",
|
||||
py: "xl",
|
||||
});
|
||||
|
||||
const CountText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
function entryKey(entry: HistoryEntryProps): string {
|
||||
return `${entry.timestamp}-${entry.method}`;
|
||||
}
|
||||
|
||||
function formatPinnedTitle(count: number): string {
|
||||
return `Pinned Requests (${count})`;
|
||||
}
|
||||
|
||||
function formatPagination(displayed: number, total: number): string {
|
||||
return `Showing ${displayed} of ${total} entries`;
|
||||
}
|
||||
|
||||
export function HistoryScreen({
|
||||
entries,
|
||||
pinnedEntries,
|
||||
searchText,
|
||||
methodFilter,
|
||||
totalCount,
|
||||
displayedCount,
|
||||
onSearchChange,
|
||||
onMethodFilterChange,
|
||||
onLoadMore,
|
||||
onClearAll,
|
||||
onExport,
|
||||
}: HistoryScreenProps) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [methodFilter, setMethodFilter] = useState<string | undefined>();
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ContentPanel>
|
||||
<Stack gap="md">
|
||||
<HeaderRow>
|
||||
<Title order={3}>Request History</Title>
|
||||
<Group>
|
||||
<TextInput
|
||||
placeholder="Search..."
|
||||
value={searchText}
|
||||
onChange={(event) => onSearchChange(event.currentTarget.value)}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Filter by method"
|
||||
data={METHOD_OPTIONS}
|
||||
value={methodFilter}
|
||||
onChange={(value) => onMethodFilterChange(value ?? "")}
|
||||
clearable
|
||||
/>
|
||||
<ToolbarButton onClick={onExport}>Export JSON</ToolbarButton>
|
||||
<ToolbarButton onClick={onClearAll}>Clear All</ToolbarButton>
|
||||
</Group>
|
||||
</HeaderRow>
|
||||
|
||||
{entries.length === 0 ? (
|
||||
<EmptyState>No request history</EmptyState>
|
||||
) : (
|
||||
<Stack gap="md">
|
||||
{entries.map((entry) => (
|
||||
<HistoryEntry key={entryKey(entry)} {...entry} />
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{pinnedEntries.length > 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<Title order={4}>{formatPinnedTitle(pinnedEntries.length)}</Title>
|
||||
<Stack gap="sm">
|
||||
{pinnedEntries.map((entry) => (
|
||||
<HistoryEntry key={entryKey(entry)} {...entry} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<CountText>
|
||||
{formatPagination(displayedCount, totalCount)}
|
||||
</CountText>
|
||||
{displayedCount < totalCount && (
|
||||
<ToolbarButton onClick={onLoadMore}>Load More</ToolbarButton>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</ContentPanel>
|
||||
</PageContainer>
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<HistoryControls
|
||||
searchText={searchText}
|
||||
methodFilter={methodFilter}
|
||||
onSearchChange={setSearchText}
|
||||
onMethodFilterChange={(value) =>
|
||||
setMethodFilter(value || undefined)
|
||||
}
|
||||
onClearAll={onClearAll}
|
||||
onExport={onExport}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
<HistoryListPanel
|
||||
entries={entries}
|
||||
pinnedEntries={pinnedEntries}
|
||||
searchText={searchText}
|
||||
methodFilter={methodFilter}
|
||||
/>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Container, Group, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import { Container, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import { ServerCard } from "../../groups/ServerCard/ServerCard";
|
||||
import { AddServerMenu } from "../../groups/AddServerMenu/AddServerMenu";
|
||||
import { ServerListControls } from "../../groups/ServerListControls/ServerListControls";
|
||||
import type { ServerCardProps } from "../../groups/ServerCard/ServerCard";
|
||||
|
||||
export interface ServerListScreenProps {
|
||||
|
|
@ -27,16 +28,23 @@ export function ServerListScreen({
|
|||
onImportConfig,
|
||||
onImportServerJson,
|
||||
}: ServerListScreenProps) {
|
||||
const [compact, setCompact] = useState<boolean>(false);
|
||||
|
||||
function handleToggleList() {
|
||||
setCompact((prev) => (prev === true ? false : true));
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Stack gap="lg">
|
||||
<Group justify="flex-end">
|
||||
<AddServerMenu
|
||||
onAddManually={onAddManually}
|
||||
onImportConfig={onImportConfig}
|
||||
onImportServerJson={onImportServerJson}
|
||||
/>
|
||||
</Group>
|
||||
<ServerListControls
|
||||
serverCount={servers.length}
|
||||
compact={compact}
|
||||
onToggleList={handleToggleList}
|
||||
onAddManually={onAddManually}
|
||||
onImportConfig={onImportConfig}
|
||||
onImportServerJson={onImportServerJson}
|
||||
/>
|
||||
|
||||
{servers.length === 0 ? (
|
||||
<EmptyState>
|
||||
|
|
@ -49,7 +57,7 @@ export function ServerListScreen({
|
|||
className="grid-align-start"
|
||||
>
|
||||
{servers.map((server) => (
|
||||
<ServerCard key={server.name} {...server} />
|
||||
<ServerCard key={server.name} compact={compact} {...server} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue