diff --git a/clients/web/package-lock.json b/clients/web/package-lock.json index e6de271a..a14dc7d5 100644 --- a/clients/web/package-lock.json +++ b/clients/web/package-lock.json @@ -12,7 +12,6 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.14.0", - "@gfazioli/mantine-border-animate": "^2.0.3", "@hono/node-server": "^1.19.14", "@mantine/core": "^8.3.17", "@mantine/form": "^8.3.17", @@ -1242,19 +1241,6 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, - "node_modules/@gfazioli/mantine-border-animate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@gfazioli/mantine-border-animate/-/mantine-border-animate-2.0.3.tgz", - "integrity": "sha512-6JmO4Cox2KAjIXWWNIewwuudTtTGS5QrcWqjgch8Kn1Zu2bwhZAk4n1y2z7YNJsnfZGVM3XcxLbdGivJcCiNMQ==", - "license": "MIT", - "peerDependencies": { - "@mantine/core": ">=7.0.0", - "@mantine/hooks": ">=7.0.0", - "@tabler/icons-react": "^3.34.0", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, "node_modules/@hono/node-server": { "version": "1.19.14", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", @@ -2689,34 +2675,6 @@ } } }, - "node_modules/@tabler/icons": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.44.0.tgz", - "integrity": "sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/codecalm" - } - }, - "node_modules/@tabler/icons-react": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.44.0.tgz", - "integrity": "sha512-8+rvzBbVm/1Z3sG3x7GUNAaxIKxwgz8xaMhRs23nrCnMTKRFAhEC+82zAIFeAA0seXdrAGX5HFCkaLpGK2rVHg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@tabler/icons": "3.44.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/codecalm" - }, - "peerDependencies": { - "react": ">= 16" - } - }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", diff --git a/clients/web/package.json b/clients/web/package.json index 791ad1c6..9c3e075d 100644 --- a/clients/web/package.json +++ b/clients/web/package.json @@ -36,7 +36,6 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.14.0", - "@gfazioli/mantine-border-animate": "^2.0.3", "@hono/node-server": "^1.19.14", "@mantine/core": "^8.3.17", "@mantine/form": "^8.3.17", diff --git a/clients/web/src/App.css b/clients/web/src/App.css index 9807a3bd..111d4015 100644 --- a/clients/web/src/App.css +++ b/clients/web/src/App.css @@ -50,6 +50,9 @@ /* Red glow pulsed around a newly appearing tab's label (#1450) */ --inspector-glow-red: var(--mantine-color-red-6); + /* Green border drawn around a freshly-added/highlighted server card (#1535) */ + --inspector-highlight-border: var(--mantine-color-green-6); + /* ── Log levels (RFC 5424) ─────────────────────────────── */ --inspector-log-debug: var(--mantine-color-gray-6); --inspector-log-info: var(--mantine-color-blue-6); diff --git a/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx b/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx index bc5b74e6..ccf7c2b5 100644 --- a/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx +++ b/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx @@ -291,6 +291,22 @@ describe("ServerCard", () => { } }); + it("draws the green highlight-variant border when highlighted", () => { + const { container } = renderWithMantine( + , + ); + expect( + container.querySelector('[data-variant="highlighted"]'), + ).not.toBeNull(); + }); + + it("does not draw the highlight border when not highlighted", () => { + const { container } = renderWithMantine(); + expect( + container.querySelector('[data-variant="highlighted"]'), + ).toBeNull(); + }); + it("does not scroll when not highlighted", () => { const scrollIntoView = vi.fn(); const orig = Element.prototype.scrollIntoView; diff --git a/clients/web/src/components/groups/ServerCard/ServerCard.tsx b/clients/web/src/components/groups/ServerCard/ServerCard.tsx index 1c3c3482..a93a6c30 100644 --- a/clients/web/src/components/groups/ServerCard/ServerCard.tsx +++ b/clients/web/src/components/groups/ServerCard/ServerCard.tsx @@ -1,6 +1,5 @@ import { useEffect, useRef, type ReactNode } from "react"; import { Badge, Button, Card, Group, Stack, Text } from "@mantine/core"; -import { BorderAnimate } from "@gfazioli/mantine-border-animate"; import type { MCPServerConfig, ServerEntry, @@ -35,9 +34,8 @@ export interface ServerCardProps extends ServerEntry { */ dragHandle?: ReactNode; /** - * When true, the card is freshly added: it draws an animated border (Mantine - * Border Animate) to draw the eye. Cleared by `onClearHighlight` on any click - * on the card. + * When true, the card is freshly added: it draws a green border to draw the + * eye. Cleared by `onClearHighlight` on any click on the card. */ highlighted?: boolean; /** @@ -47,7 +45,7 @@ export interface ServerCardProps extends ServerEntry { * Defaults to true. */ scrollOnHighlight?: boolean; - /** Called when a highlighted card is clicked, to dismiss the animated border. */ + /** Called when a highlighted card is clicked, to dismiss the green border. */ onClearHighlight?: () => void; } @@ -156,16 +154,18 @@ export function ServerCard({ const protocolVersion = connection.status === "connected" ? connection.protocolVersion : undefined; - const card = ( + // A dimmed card (another server is active) is inert, so the disabled variant + // wins; otherwise a freshly-added card draws the highlighted green border. + const variant = isDimmed + ? "disabled" + : highlighted + ? "highlighted" + : undefined; + + return ( @@ -237,19 +237,4 @@ export function ServerCard({ ); - - // Always render the wrapper and toggle `show`/`animate` rather than - // conditionally wrapping — swapping the element type on clear would remount - // the card (and its connect toggle / buttons), swallowing the click that - // triggered the clear. - return ( - - {card} - - ); } diff --git a/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx b/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx index 19b7bf2a..7bf332ae 100644 --- a/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx +++ b/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx @@ -137,16 +137,16 @@ describe("ServerListScreen", () => { }); describe("freshly-added highlight", () => { - it("draws an animated border on every highlighted server", () => { + it("draws a green highlight border on every highlighted server", () => { const { container } = renderWithMantine( , ); - // One animated border per highlighted card. + // One highlighted-variant card per highlighted server. expect( - container.querySelectorAll(".mantine-BorderAnimate-border"), + container.querySelectorAll('[data-variant="highlighted"]'), ).toHaveLength(2); }); diff --git a/clients/web/src/main.tsx b/clients/web/src/main.tsx index 486061ca..662352e8 100644 --- a/clients/web/src/main.tsx +++ b/clients/web/src/main.tsx @@ -4,7 +4,6 @@ import { MantineProvider, type CSSVariablesResolver } from "@mantine/core"; import { Notifications } from "@mantine/notifications"; import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; -import "@gfazioli/mantine-border-animate/styles.css"; import "./App.css"; import { theme } from "./theme/theme"; import App from "./App"; diff --git a/clients/web/src/theme/Card.ts b/clients/web/src/theme/Card.ts index eaa5b586..1b597ec4 100644 --- a/clients/web/src/theme/Card.ts +++ b/clients/web/src/theme/Card.ts @@ -37,6 +37,19 @@ export const ThemeCard = Card.extend({ }, }; } + if (props.variant === "highlighted") { + // Freshly-added / called-out server card: a prominent green border draws + // the eye until the highlight is dismissed (#1535). Only the color/width + // are set here — the border itself comes from the inherited + // `withBorder: true` default above, so keep that default if changing it. + return { + root: { + backgroundColor: "var(--inspector-surface-card)", + borderColor: "var(--inspector-highlight-border)", + borderWidth: 2, + }, + }; + } if (props.variant === "preview") { // Container for the resource preview / template form panels: sizes to // content (no forced height) but caps at the screen's available area