Make ViewHeader and ServerStatusIndicator responsive
Header now adapts cleanly across widths instead of wrapping or clipping: - LeftSection/RightSection are symmetric (`flex: 1, miw: 0, wrap: nowrap`), so the natural-width CenterSection sits visually centered between them. Server name truncates with ellipsis when the left slice is too narrow. - Below `md` (992px) the SegmentedControl swaps for a Select dropdown so the tab labels never have to shrink or wrap. - Below `sm` (768px) the "Disconnect" button collapses to an MdLinkOff ActionIcon (with `aria-label` + `title` for the tooltip). - Below `lg` (1200px) ServerStatusIndicator hides its text label and shows only the colored dot (full label still available as a tooltip on hover), freeing space so the Disconnect button stops getting truncated at typical desktop widths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9553fb9c8a
commit
4e99e6393f
2 changed files with 52 additions and 17 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { Group, Paper, Text } from "@mantine/core";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import type { ConnectionStatus } from "@inspector/core/mcp/types.js";
|
||||
|
||||
export interface ServerStatusIndicatorProps {
|
||||
|
|
@ -42,13 +43,16 @@ export function ServerStatusIndicator({
|
|||
latencyMs,
|
||||
retryCount,
|
||||
}: ServerStatusIndicatorProps) {
|
||||
const showLabel = useMediaQuery("(min-width: 1200px)");
|
||||
const label = getLabel(status, latencyMs, retryCount);
|
||||
return (
|
||||
<Group gap="xs">
|
||||
<Dot
|
||||
bg={statusColorVar[status]}
|
||||
className={status === "connecting" ? "inspector-pulse" : undefined}
|
||||
title={showLabel ? undefined : label}
|
||||
/>
|
||||
<Text size="sm">{getLabel(status, latencyMs, retryCount)}</Text>
|
||||
{showLabel && <Text size="sm">{label}</Text>}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ import {
|
|||
Group,
|
||||
Image,
|
||||
SegmentedControl,
|
||||
Select,
|
||||
Text,
|
||||
Title,
|
||||
useComputedColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import type { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { MdLightMode, MdDarkMode } from "react-icons/md";
|
||||
import { MdLightMode, MdDarkMode, MdLinkOff } from "react-icons/md";
|
||||
import type { ConnectionStatus } from "@inspector/core/mcp/types.js";
|
||||
import { ServerStatusIndicator } from "../../elements/ServerStatusIndicator/ServerStatusIndicator";
|
||||
import mcpLogo from "../../../theme/assets/MCP.svg";
|
||||
|
|
@ -39,13 +41,13 @@ const HeaderBar = Group.withProps({
|
|||
h: "100%",
|
||||
px: "md",
|
||||
wrap: "nowrap",
|
||||
gap: 0,
|
||||
gap: "md",
|
||||
});
|
||||
|
||||
const LeftSection = Group.withProps({
|
||||
gap: "md",
|
||||
wrap: "nowrap",
|
||||
w: "33.33%",
|
||||
flex: 1,
|
||||
miw: 0,
|
||||
});
|
||||
|
||||
|
|
@ -66,17 +68,20 @@ const ServerName = Text.withProps({
|
|||
fw: 600,
|
||||
size: "lg",
|
||||
truncate: "end",
|
||||
maw: "calc(100% - 40px)",
|
||||
miw: 0,
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const CenterSection = Group.withProps({
|
||||
w: "33.33%",
|
||||
justify: "center",
|
||||
wrap: "nowrap",
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
const RightSection = Group.withProps({
|
||||
gap: "sm",
|
||||
w: "33.33%",
|
||||
wrap: "nowrap",
|
||||
flex: 1,
|
||||
miw: 0,
|
||||
justify: "flex-end",
|
||||
});
|
||||
|
||||
|
|
@ -86,6 +91,13 @@ const DisconnectButton = Button.withProps({
|
|||
size: "sm",
|
||||
});
|
||||
|
||||
const DisconnectIcon = ActionIcon.withProps({
|
||||
variant: "subtle",
|
||||
c: "red",
|
||||
size: 36,
|
||||
"aria-label": "Disconnect",
|
||||
});
|
||||
|
||||
const ThemeToggle = ActionIcon.withProps({
|
||||
variant: "subtle",
|
||||
size: 36,
|
||||
|
|
@ -101,6 +113,8 @@ const UnconnectedBar = Group.withProps({
|
|||
export function ViewHeader(props: ViewHeaderProps) {
|
||||
const colorScheme = useComputedColorScheme();
|
||||
const ThemeIcon = colorScheme === "dark" ? MdLightMode : MdDarkMode;
|
||||
const showSegmented = useMediaQuery("(min-width: 992px)");
|
||||
const showDisconnectLabel = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
if (!props.connected) {
|
||||
return (
|
||||
|
|
@ -126,12 +140,23 @@ export function ViewHeader(props: ViewHeaderProps) {
|
|||
</LeftSection>
|
||||
|
||||
<CenterSection>
|
||||
<SegmentedControl
|
||||
value={props.activeTab}
|
||||
onChange={props.onTabChange}
|
||||
data={props.availableTabs}
|
||||
size="sm"
|
||||
/>
|
||||
{showSegmented ? (
|
||||
<SegmentedControl
|
||||
value={props.activeTab}
|
||||
onChange={props.onTabChange}
|
||||
data={props.availableTabs}
|
||||
size="sm"
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
value={props.activeTab}
|
||||
onChange={(value) => value && props.onTabChange(value)}
|
||||
data={props.availableTabs}
|
||||
size="sm"
|
||||
allowDeselect={false}
|
||||
w={140}
|
||||
/>
|
||||
)}
|
||||
</CenterSection>
|
||||
|
||||
<RightSection>
|
||||
|
|
@ -139,9 +164,15 @@ export function ViewHeader(props: ViewHeaderProps) {
|
|||
status={props.status}
|
||||
latencyMs={props.latencyMs}
|
||||
/>
|
||||
<DisconnectButton onClick={props.onDisconnect}>
|
||||
Disconnect
|
||||
</DisconnectButton>
|
||||
{showDisconnectLabel ? (
|
||||
<DisconnectButton onClick={props.onDisconnect}>
|
||||
Disconnect
|
||||
</DisconnectButton>
|
||||
) : (
|
||||
<DisconnectIcon onClick={props.onDisconnect} title="Disconnect">
|
||||
<MdLinkOff size={20} />
|
||||
</DisconnectIcon>
|
||||
)}
|
||||
<ThemeToggle onClick={props.onToggleTheme}>
|
||||
<ThemeIcon size={20} />
|
||||
</ThemeToggle>
|
||||
|
|
|
|||
Loading…
Reference in a new issue