* fix(web): filter toggle hover as border, not background (#1460) The Logging/History/Network filter toggles used the same --mantine-primary-color-light background for both their hover and their active (toggled-on) state, so clicking a button to turn it off while the cursor was still over it looked like a no-op — the hover background masked the removal of the active background. - Add a shared FilterToggleButton element used by LogControls, NetworkControls, and MessageDirectionFilter, replacing the duplicated UnstyledButton + inline `bg={active ? … : undefined}` in each. - Add a `filterToggle` UnstyledButton theme variant + `.filter-toggle` rules: hover is now a thin border (reserved via a transparent 1px border so it doesn't shift layout); the active state is the filled background, keyed off `aria-pressed="true"`. - This leaves the `.list-item` selectable rows (Prompts/Apps/Tools/ Resources lists, section headers) untouched — they keep their hover-fill since they aren't toggles. - Add `aria-pressed` to the Logging level toggles (previously missing). Tests + stories cover the active/hover distinction via aria-pressed for FilterToggleButton and all three controls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): move filter-toggle border to CSS so :hover actually shows (#1460) The previous attempt set the reserved `1px solid transparent` border via the theme's `styles` callback, which Mantine emits as an *inline* style. Inline styles outrank stylesheet `:hover` rules, so `.filter-toggle:hover { border-color }` never applied — hovering showed no border at all (verified in a real browser via Playwright). Move the border (and border-radius) out of the inline theme styles into the `.filter-toggle` class in App.css, so `:hover` can override the transparent border-color. The active fill (`[aria-pressed="true"]`) was already correct. Verified with real Playwright page.hover: inactive+hover now shows a 1px --inspector-brand-primary border, active shows the fill, active+hover shows both, all distinct. Story play functions assert the active fill and the reserved transparent border (the hover border can't be asserted in the synthetic-event test runner, so it's documented as Playwright-verified). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
92da6c1675
commit
d90cc4c34b
5 changed files with 256 additions and 68 deletions
|
|
@ -132,6 +132,20 @@ describe("LogControls", () => {
|
|||
expect(onToggleAllLevels).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("reflects level visibility via aria-pressed", () => {
|
||||
renderWithMantine(
|
||||
<LogControls {...baseProps} visibleLevels={someVisible} />,
|
||||
);
|
||||
expect(screen.getByRole("button", { name: "debug" })).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"false",
|
||||
);
|
||||
expect(screen.getByRole("button", { name: "warning" })).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"true",
|
||||
);
|
||||
});
|
||||
|
||||
it("invokes onToggleLevel when a level button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onToggleLevel = vi.fn();
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ import {
|
|||
Group,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import type { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { FilterToggleButton } from "../../elements/FilterToggleButton/FilterToggleButton";
|
||||
|
||||
const LOG_LEVELS: LoggingLevel[] = [
|
||||
"debug",
|
||||
|
|
@ -102,24 +101,15 @@ export function LogControls({
|
|||
</SubtleButton>
|
||||
</Group>
|
||||
<Stack gap="xs">
|
||||
{LOG_LEVELS.map((level) => {
|
||||
const style = LEVEL_COLORS[level];
|
||||
const active = visibleLevels[level];
|
||||
return (
|
||||
<UnstyledButton
|
||||
key={level}
|
||||
w="100%"
|
||||
p="sm"
|
||||
variant="listItem"
|
||||
bg={active ? "var(--mantine-primary-color-light)" : undefined}
|
||||
onClick={() => onToggleLevel(level, !active)}
|
||||
>
|
||||
<Text c={style.c} ta="center" fw={500}>
|
||||
{level}
|
||||
</Text>
|
||||
</UnstyledButton>
|
||||
);
|
||||
})}
|
||||
{LOG_LEVELS.map((level) => (
|
||||
<FilterToggleButton
|
||||
key={level}
|
||||
label={level}
|
||||
color={LEVEL_COLORS[level].c}
|
||||
active={visibleLevels[level]}
|
||||
onToggle={(visible) => onToggleLevel(level, visible)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
import {
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import { Button, Group, Stack, Title } from "@mantine/core";
|
||||
import type { MessageOrigin } from "@inspector/core/mcp/types.js";
|
||||
import { FilterToggleButton } from "../../elements/FilterToggleButton/FilterToggleButton";
|
||||
|
||||
const SubtleButton = Button.withProps({
|
||||
variant: "subtle",
|
||||
|
|
@ -33,8 +27,8 @@ export interface MessageDirectionFilterProps {
|
|||
|
||||
/**
|
||||
* "Filter by Message Direction" section — a Select/Deselect All control plus a
|
||||
* listItem toggle per direction (client → server / client ← server). Used by the
|
||||
* History controls. (Kept as its own component so the section is testable in
|
||||
* FilterToggleButton per direction (client → server / client ← server). Used by
|
||||
* the History controls. (Kept as its own component so the section is testable in
|
||||
* isolation and reusable if another screen ever needs a direction filter.)
|
||||
*/
|
||||
export function MessageDirectionFilter({
|
||||
|
|
@ -53,24 +47,15 @@ export function MessageDirectionFilter({
|
|||
</SubtleButton>
|
||||
</Group>
|
||||
<Stack gap="xs">
|
||||
{MESSAGE_DIRECTIONS.map(({ origin, label, color }) => {
|
||||
const active = visibleDirections[origin];
|
||||
return (
|
||||
<UnstyledButton
|
||||
key={origin}
|
||||
w="100%"
|
||||
p="sm"
|
||||
variant="listItem"
|
||||
aria-pressed={active}
|
||||
bg={active ? "var(--mantine-primary-color-light)" : undefined}
|
||||
onClick={() => onToggleDirection(origin, !active)}
|
||||
>
|
||||
<Text c={color} ta="center" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</UnstyledButton>
|
||||
);
|
||||
})}
|
||||
{MESSAGE_DIRECTIONS.map(({ origin, label, color }) => (
|
||||
<FilterToggleButton
|
||||
key={origin}
|
||||
label={label}
|
||||
color={color}
|
||||
active={visibleDirections[origin]}
|
||||
onToggle={(visible) => onToggleDirection(origin, visible)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,20 @@ describe("NetworkControls", () => {
|
|||
expect(onFilterChange).toHaveBeenLastCalledWith("x");
|
||||
});
|
||||
|
||||
it("clears the filter text when the clear button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onFilterChange = vi.fn();
|
||||
renderWithMantine(
|
||||
<NetworkControls
|
||||
{...baseProps}
|
||||
filterText="auth"
|
||||
onFilterChange={onFilterChange}
|
||||
/>,
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Clear" }));
|
||||
expect(onFilterChange).toHaveBeenCalledWith("");
|
||||
});
|
||||
|
||||
it("reflects category visibility via aria-pressed", () => {
|
||||
renderWithMantine(
|
||||
<NetworkControls
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ import {
|
|||
CloseButton,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import type { FetchRequestCategory } from "@inspector/core/mcp/types.js";
|
||||
import { FilterToggleButton } from "../../elements/FilterToggleButton/FilterToggleButton";
|
||||
|
||||
const NETWORK_CATEGORIES: FetchRequestCategory[] = ["auth", "transport"];
|
||||
|
||||
|
|
@ -64,24 +63,15 @@ export function NetworkControls({
|
|||
</SubtleButton>
|
||||
</Group>
|
||||
<Stack gap="xs">
|
||||
{NETWORK_CATEGORIES.map((category) => {
|
||||
const active = visibleCategories[category];
|
||||
return (
|
||||
<UnstyledButton
|
||||
key={category}
|
||||
w="100%"
|
||||
p="sm"
|
||||
variant="listItem"
|
||||
aria-pressed={active}
|
||||
bg={active ? "var(--mantine-primary-color-light)" : undefined}
|
||||
onClick={() => onToggleCategory(category, !active)}
|
||||
>
|
||||
<Text c={CATEGORY_COLORS[category]} ta="center" fw={500}>
|
||||
{category}
|
||||
</Text>
|
||||
</UnstyledButton>
|
||||
);
|
||||
})}
|
||||
{NETWORK_CATEGORIES.map((category) => (
|
||||
<FilterToggleButton
|
||||
key={category}
|
||||
label={category}
|
||||
color={CATEGORY_COLORS[category]}
|
||||
active={visibleCategories[category]}
|
||||
onToggle={(visible) => onToggleCategory(category, visible)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue