mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-07-10 09:38:52 +00:00
Add MCP widget design variants
This commit is contained in:
parent
a0dd93ff0f
commit
1af2e85620
18 changed files with 741 additions and 203 deletions
|
|
@ -69,26 +69,24 @@ export function WidgetShell({
|
|||
return (
|
||||
<div className={shellClassName}>
|
||||
<div aria-hidden className="mcp-widget-glow" />
|
||||
{immersive ? null : (
|
||||
<header className="mcp-widget-brand">
|
||||
<span aria-hidden className="mcp-widget-brand-mark">
|
||||
<svg aria-hidden="true" viewBox="0 0 314 256">
|
||||
<path
|
||||
d="M313.728 100.982H197.297V0H159.68V109.567C159.68 121.205 164.284 132.381 172.466 140.615L267.535 236.283L294.134 209.517L223.917 138.858H313.75V101.004L313.728 100.982Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M19.616 46.5043L89.8323 117.163H0V155.017H116.431V255.999H154.048V146.432C154.048 134.795 149.444 123.618 141.262 115.384L46.2144 19.7383L19.616 46.5043Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span className="mcp-widget-brand-copy">
|
||||
<span className="mcp-widget-brand-name">supermemory</span>
|
||||
<span className="mcp-widget-brand-mode">MCP</span>
|
||||
</span>
|
||||
</header>
|
||||
)}
|
||||
<header className="mcp-widget-brand">
|
||||
<span aria-hidden className="mcp-widget-brand-mark">
|
||||
<svg aria-hidden="true" viewBox="0 0 314 256">
|
||||
<path
|
||||
d="M313.728 100.982H197.297V0H159.68V109.567C159.68 121.205 164.284 132.381 172.466 140.615L267.535 236.283L294.134 209.517L223.917 138.858H313.75V101.004L313.728 100.982Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M19.616 46.5043L89.8323 117.163H0V155.017H116.431V255.999H154.048V146.432C154.048 134.795 149.444 123.618 141.262 115.384L46.2144 19.7383L19.616 46.5043Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span className="mcp-widget-brand-copy">
|
||||
<span className="mcp-widget-brand-name">supermemory</span>
|
||||
<span className="mcp-widget-brand-mode">MCP</span>
|
||||
</span>
|
||||
</header>
|
||||
<main className="mcp-widget-content">{children}</main>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export function PermissionBadge({ permission }: Props) {
|
|||
const isWrite = permission === "write"
|
||||
return (
|
||||
<span
|
||||
className="text-[#8B8B8B]"
|
||||
className="text-text-muted"
|
||||
style={{ fontFamily: "var(--font-brand)" }}
|
||||
>
|
||||
{isWrite ? "Read/write" : "Read only"}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { ContainerTag, ContainerTagAccess } from "../../shared/types"
|
||||
import { Card, Stack } from "../design/ui"
|
||||
import { Card } from "../design/ui"
|
||||
import { Check } from "../lib/icons"
|
||||
import { formatTagLabel } from "../lib/formatTag"
|
||||
import { PermissionBadge } from "./PermissionBadge"
|
||||
|
||||
interface Props {
|
||||
|
|
@ -16,46 +17,53 @@ export function WorkspaceCard({
|
|||
access,
|
||||
onClick,
|
||||
}: Props) {
|
||||
const name = containerTag.name || containerTag.containerTag
|
||||
const name = containerTag.name || formatTagLabel(containerTag.containerTag)
|
||||
return (
|
||||
<Card
|
||||
as="button"
|
||||
className="min-h-[104px] w-full p-3"
|
||||
className="workspace-card w-full p-3"
|
||||
onClick={() => onClick(containerTag.containerTag)}
|
||||
variant={active ? "active" : "interactive"}
|
||||
>
|
||||
<div className="absolute right-3 top-3 text-[11px] font-medium leading-none">
|
||||
{access ? <PermissionBadge permission={access.permission} /> : null}
|
||||
</div>
|
||||
{active ? (
|
||||
<Check className="absolute bottom-3 right-3 size-3 shrink-0 text-accent" />
|
||||
) : null}
|
||||
<Stack className="h-full" gap="xs">
|
||||
<Stack align="center" direction="row" gap="sm" justify="between">
|
||||
<span
|
||||
className="min-w-0 max-w-[calc(100%-88px)] truncate text-sm font-medium text-white"
|
||||
style={{ fontFamily: "var(--font-brand)" }}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</Stack>
|
||||
<div className="truncate text-xs leading-normal text-[#8B8B8B]">
|
||||
{containerTag.containerTag}
|
||||
</div>
|
||||
{(containerTag.documentCount > 0 || containerTag.memoryCount > 0) && (
|
||||
<div className="mt-0.5 flex items-center gap-(--space-2) text-xs leading-normal text-[#8B8B8B]">
|
||||
<span>
|
||||
{containerTag.documentCount} doc
|
||||
{containerTag.documentCount === 1 ? "" : "s"}
|
||||
</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>
|
||||
{containerTag.memoryCount}{" "}
|
||||
{containerTag.memoryCount === 1 ? "memory" : "memories"}
|
||||
<div className="workspace-card-inner">
|
||||
<div className="workspace-card-main">
|
||||
<div className="workspace-card-heading">
|
||||
<span
|
||||
className="workspace-card-title truncate text-sm font-medium text-text-primary"
|
||||
style={{ fontFamily: "var(--font-brand)" }}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
{active ? (
|
||||
<Check
|
||||
aria-hidden
|
||||
className="workspace-card-check size-3 shrink-0 text-accent"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
{(containerTag.documentCount > 0 ||
|
||||
containerTag.memoryCount > 0) && (
|
||||
<div className="workspace-card-meta flex items-center gap-(--space-2) text-[11px] leading-normal text-text-muted">
|
||||
<span>
|
||||
{containerTag.documentCount} doc
|
||||
{containerTag.documentCount === 1 ? "" : "s"}
|
||||
</span>
|
||||
<span aria-hidden className="opacity-40">
|
||||
·
|
||||
</span>
|
||||
<span>
|
||||
{containerTag.memoryCount}{" "}
|
||||
{containerTag.memoryCount === 1 ? "memory" : "memories"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{access ? (
|
||||
<div className="workspace-card-trailing">
|
||||
<PermissionBadge permission={access.permission} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,14 +88,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse at 50% 112%,
|
||||
rgba(75, 160, 250, 0.34) 0%,
|
||||
rgba(20, 16, 255, 0.22) 28%,
|
||||
rgba(11, 17, 25, 0) 62%
|
||||
),
|
||||
linear-gradient(180deg, #0b1119 0%, #070b11 100%);
|
||||
background: var(--app-bg);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-sans);
|
||||
letter-spacing: -0.01em;
|
||||
|
|
@ -123,26 +116,13 @@
|
|||
isolation: isolate;
|
||||
min-height: 280px;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(rgba(75, 160, 250, 0.04) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(75, 160, 250, 0.04) 1px, transparent 1px),
|
||||
radial-gradient(
|
||||
circle at 50% 108%,
|
||||
rgba(75, 160, 250, 0.18),
|
||||
transparent 42%
|
||||
),
|
||||
linear-gradient(180deg, #0b1119 0%, #070b11 100%);
|
||||
background-size:
|
||||
36px 36px,
|
||||
36px 36px,
|
||||
100% 100%,
|
||||
100% 100%;
|
||||
background: var(--widget-shell-bg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mcp-widget-shell-graph {
|
||||
min-height: 420px;
|
||||
background: #05080d;
|
||||
background: var(--widget-shell-graph-bg);
|
||||
}
|
||||
|
||||
.mcp-widget-shell-graph::before,
|
||||
|
|
@ -177,24 +157,379 @@
|
|||
|
||||
.mcp-widget-glow {
|
||||
position: absolute;
|
||||
right: -18%;
|
||||
bottom: -42%;
|
||||
left: -18%;
|
||||
right: -16%;
|
||||
bottom: -44%;
|
||||
left: -16%;
|
||||
z-index: -1;
|
||||
height: 70%;
|
||||
height: 68%;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
ellipse at 50% 48%,
|
||||
rgba(255, 255, 255, 0.22) 0%,
|
||||
rgba(71, 168, 253, 0.3) 12%,
|
||||
rgba(0, 153, 255, 0.22) 28%,
|
||||
rgba(20, 16, 255, 0.18) 48%,
|
||||
ellipse at 50% 42%,
|
||||
var(--widget-glow-inner) 0%,
|
||||
var(--widget-glow-mid) 22%,
|
||||
var(--widget-glow-outer) 46%,
|
||||
transparent 74%
|
||||
);
|
||||
filter: blur(20px);
|
||||
opacity: 0.78;
|
||||
filter: blur(18px);
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
|
||||
/* Studio-only visual variants. Production widgets do not set this attribute. */
|
||||
[data-widget-version="v1"] .mcp-widget-shell {
|
||||
--brand-accent: var(--accent);
|
||||
--brand-opacity: 0.86;
|
||||
--widget-glow-inner: rgba(255, 255, 255, 0.22);
|
||||
--widget-glow-mid: rgba(71, 168, 253, 0.3);
|
||||
--widget-glow-outer: rgba(20, 16, 255, 0.18);
|
||||
background:
|
||||
linear-gradient(rgba(75, 160, 250, 0.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(75, 160, 250, 0.045) 1px, transparent 1px),
|
||||
radial-gradient(
|
||||
circle at 50% 108%,
|
||||
rgba(75, 160, 250, 0.16),
|
||||
transparent 42%
|
||||
),
|
||||
var(--widget-shell-bg);
|
||||
background-size:
|
||||
36px 36px,
|
||||
36px 36px,
|
||||
100% 100%,
|
||||
100% 100%;
|
||||
}
|
||||
|
||||
[data-widget-version="v1"] .super-loader,
|
||||
[data-widget-version="v1"] .spinner {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
[data-widget-version="v1"] .spinner {
|
||||
border-top-color: var(--accent);
|
||||
box-shadow: 0 0 20px rgba(75, 160, 250, 0.42);
|
||||
}
|
||||
[data-widget-version="v2"] .mcp-widget-shell {
|
||||
--brand-accent: var(--accent);
|
||||
--brand-opacity: 1;
|
||||
--widget-shell-bg: color-mix(in srgb, var(--widget-shell-bg) 86%, var(--bg-muted));
|
||||
--card-bg: color-mix(in srgb, var(--card-bg) 88%, var(--bg-muted));
|
||||
--panel-bg: color-mix(in srgb, var(--panel-bg) 88%, var(--bg-muted));
|
||||
--widget-glow-mid: color-mix(in srgb, var(--widget-glow-mid) 70%, transparent);
|
||||
--widget-glow-outer: color-mix(in srgb, var(--widget-glow-outer) 70%, transparent);
|
||||
}
|
||||
|
||||
[data-widget-version="v2"] .mcp-widget-shell-graph::before {
|
||||
background-image:
|
||||
linear-gradient(rgba(105, 167, 240, 0.12) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(105, 167, 240, 0.12) 1px, transparent 1px);
|
||||
background-size: 32px 32px;
|
||||
mask-image: radial-gradient(ellipse at center, black 60%, transparent 100%);
|
||||
}
|
||||
|
||||
[data-theme="light"] [data-widget-version="v2"] .mcp-widget-shell-graph::before,
|
||||
:root:not([data-theme="dark"]) [data-widget-version="v2"] .mcp-widget-shell-graph::before {
|
||||
background-image:
|
||||
linear-gradient(rgba(75, 160, 250, 0.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(75, 160, 250, 0.045) 1px, transparent 1px);
|
||||
}
|
||||
|
||||
[data-theme="light"] [data-widget-version="v2"] .mcp-widget-shell-graph::after,
|
||||
:root:not([data-theme="dark"]) [data-widget-version="v2"] .mcp-widget-shell-graph::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
[data-theme="light"] [data-widget-version="v2"] .mcp-soft-button,
|
||||
:root:not([data-theme="dark"]) [data-widget-version="v2"] .mcp-soft-button {
|
||||
background: var(--button-secondary-bg);
|
||||
color: var(--button-secondary-text);
|
||||
border: 1px solid var(--button-secondary-border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
[data-theme="light"] [data-widget-version="v2"] .mcp-soft-button:hover,
|
||||
:root:not([data-theme="dark"]) [data-widget-version="v2"] .mcp-soft-button:hover {
|
||||
background: var(--button-secondary-hover);
|
||||
}
|
||||
|
||||
/* Card/panel glow — hidden by default, activated by v3/v4. */
|
||||
.card-glow,
|
||||
.panel-glow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mcp-panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mcp-panel > :not(.panel-glow) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Workspace picker cards — grid layout (default). */
|
||||
.workspace-card {
|
||||
min-height: 104px;
|
||||
}
|
||||
|
||||
.workspace-card-inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.workspace-card-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
min-width: 0;
|
||||
padding-right: 72px;
|
||||
}
|
||||
|
||||
.workspace-card-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-card-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-card-trailing {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* v3 — Bold inner glow on cards/panels (web-app BgGrad-inspired), plain shell background. */
|
||||
[data-widget-version="v3"] .mcp-widget-shell {
|
||||
--brand-accent: var(--accent);
|
||||
--brand-opacity: 1;
|
||||
background: var(--widget-shell-bg);
|
||||
}
|
||||
|
||||
[data-widget-version="v3"] .mcp-widget-glow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-widget-version="v3"] .card-glow,
|
||||
[data-widget-version="v3"] .panel-glow {
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse 80% 50% at 50% 100%,
|
||||
rgba(20, 16, 255, 0.12) 0%,
|
||||
rgba(0, 144, 255, 0.08) 30%,
|
||||
transparent 70%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 60% 40% at 30% 90%,
|
||||
rgba(71, 168, 253, 0.10) 0%,
|
||||
transparent 60%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 60% 40% at 70% 90%,
|
||||
rgba(75, 160, 250, 0.10) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
}
|
||||
|
||||
/* v4 — Fully refined for white/light + proper dark mode support. */
|
||||
[data-widget-version="v4"] .mcp-widget-shell {
|
||||
--brand-accent: var(--accent);
|
||||
--brand-opacity: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--widget-shell-bg);
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .mcp-widget-glow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-theme="light"] [data-widget-version="v4"] .mcp-widget-shell,
|
||||
:root:not([data-theme="dark"]) [data-widget-version="v4"] .mcp-widget-shell {
|
||||
--widget-shell-bg: #faf9f7;
|
||||
--card-bg: #ffffff;
|
||||
--card-border: rgba(0, 0, 0, 0.06);
|
||||
--card-border-hover: rgba(0, 0, 0, 0.10);
|
||||
--card-active-border: rgba(63, 126, 232, 0.32);
|
||||
--card-active-bg: #fafcff;
|
||||
--panel-bg: #ffffff;
|
||||
--panel-border: rgba(0, 0, 0, 0.06);
|
||||
--panel-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 6px 16px rgba(0, 0, 0, 0.03);
|
||||
--bg-control: #fafafa;
|
||||
--bg-control-hover: #f5f5f5;
|
||||
--border-control: rgba(0, 0, 0, 0.08);
|
||||
--text-secondary: #6b7280;
|
||||
--text-muted: #9ca3af;
|
||||
}
|
||||
|
||||
[data-theme="dark"] [data-widget-version="v4"] .mcp-widget-shell {
|
||||
--widget-shell-bg: #111518;
|
||||
--card-bg: #181d22;
|
||||
--card-border: rgba(255, 255, 255, 0.06);
|
||||
--card-border-hover: rgba(255, 255, 255, 0.10);
|
||||
--card-active-border: rgba(106, 166, 248, 0.36);
|
||||
--card-active-bg: #161c24;
|
||||
--panel-bg: #181d22;
|
||||
--panel-border: rgba(255, 255, 255, 0.06);
|
||||
--panel-shadow: 0 1px 3px rgba(0, 0, 0, 0.16), 0 6px 16px rgba(0, 0, 0, 0.14);
|
||||
--bg-control: #14191f;
|
||||
--bg-control-hover: #1b222a;
|
||||
--border-control: rgba(255, 255, 255, 0.08);
|
||||
--text-secondary: #a0a8b4;
|
||||
--text-muted: #6b7580;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .card-glow,
|
||||
[data-widget-version="v4"] .panel-glow {
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
ellipse 80% 50% at 50% 100%,
|
||||
rgba(20, 16, 255, 0.07) 0%,
|
||||
rgba(0, 144, 255, 0.04) 30%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
/* v4 — Compact inline wordmark: logo + supermemory, no MCP subtitle. */
|
||||
[data-widget-version="v4"] .mcp-widget-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
margin: var(--space-3) var(--page-header-px) 0;
|
||||
padding-bottom: var(--space-3);
|
||||
border-bottom: 1px solid var(--border-muted);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .mcp-widget-brand-mark {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .mcp-widget-brand-mark svg {
|
||||
width: 15px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .mcp-widget-brand-copy {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .mcp-widget-brand-name {
|
||||
font-family: var(--font-brand);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .mcp-widget-brand-mode {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* v4 — Vertically center loading in the shell below the header. */
|
||||
[data-widget-version="v4"] .mcp-widget-content:has(.mcp-widget-loading) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .mcp-widget-loading {
|
||||
flex: 1;
|
||||
min-height: 180px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* v4 — Workspace picker as compact list rows. */
|
||||
[data-widget-version="v4"] .workspace-picker-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card {
|
||||
min-height: 0;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card:not(:last-child) {
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card:hover {
|
||||
background: var(--card-bg-hover);
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card .card-glow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card-inner {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card-main {
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
min-width: 0;
|
||||
padding-right: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card-heading {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card-title {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card-meta {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-widget-version="v4"] .workspace-card-trailing {
|
||||
position: static;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.mcp-widget-shell-graph .mcp-widget-glow {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -203,12 +538,14 @@
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: var(--space-4) var(--page-header-px) 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
opacity: 0.86;
|
||||
opacity: var(--brand-opacity);
|
||||
}
|
||||
|
||||
.mcp-widget-brand-mark {
|
||||
|
|
@ -216,7 +553,7 @@
|
|||
width: 20px;
|
||||
height: 20px;
|
||||
place-items: center;
|
||||
color: var(--accent);
|
||||
color: var(--brand-accent);
|
||||
}
|
||||
|
||||
.mcp-widget-brand-mark svg {
|
||||
|
|
@ -265,7 +602,7 @@
|
|||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.12);
|
||||
border-top-color: var(--accent);
|
||||
border-top-color: var(--brand-accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
box-shadow: 0 0 20px rgba(75, 160, 250, 0.42);
|
||||
|
|
@ -282,7 +619,7 @@
|
|||
min-width: calc(var(--super-loader-size, 42px) + 10px);
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
color: var(--accent);
|
||||
color: var(--brand-accent);
|
||||
}
|
||||
|
||||
.super-loader-mark {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
--bg-primary: #faf9f4;
|
||||
--bg-secondary: #f3f1ec;
|
||||
--bg-muted: #e8e5e0;
|
||||
--bg-elevated: #ffffff;
|
||||
--bg-elevated: #f8f7f3;
|
||||
--bg-overlay: rgba(250, 249, 244, 0.7);
|
||||
|
||||
/* Borders */
|
||||
|
|
@ -276,100 +276,231 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Nova widget theme
|
||||
/* Cursor MCP widget theme
|
||||
*
|
||||
* PR-1120's widget screens run inside agent cells, so the light/dark host
|
||||
* theme can vary independently from the Supermemory product theme. Force the
|
||||
* MCP app surfaces onto Nova's dark product palette so all widget tools match
|
||||
* apps/web: DM Sans, dark cards, soft borders, and the #4ba0fa accent.
|
||||
* The web app palette is a useful reference, but these widgets live inside
|
||||
* Cursor's chat surface. Keep the brand typography, then soften both themes so
|
||||
* the widget feels embedded instead of like a separate high-contrast app frame.
|
||||
*/
|
||||
:root,
|
||||
[data-theme="light"],
|
||||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
|
||||
--accent-start: #369bfd;
|
||||
--accent-mid: #4ba0fa;
|
||||
--accent-end: #36fdfd;
|
||||
--accent-gradient: linear-gradient(
|
||||
94deg,
|
||||
var(--accent-start),
|
||||
var(--accent-end)
|
||||
);
|
||||
|
||||
--accent: #4ba0fa;
|
||||
--accent-start: #4f8df7;
|
||||
--accent-mid: #4f8df7;
|
||||
--accent-end: #8ec8ff;
|
||||
--accent-gradient: linear-gradient(94deg, var(--accent-start), var(--accent-end));
|
||||
--accent: #3f7ee8;
|
||||
--accent-foreground: #ffffff;
|
||||
--accent-hover: #369bfd;
|
||||
--accent-muted: rgba(75, 160, 250, 0.12);
|
||||
--accent-hover: #2f6fd8;
|
||||
--accent-muted: rgba(63, 126, 232, 0.1);
|
||||
|
||||
--bg-primary: #0b1119;
|
||||
--bg-secondary: #101822;
|
||||
--bg-muted: #131b28;
|
||||
--bg-elevated: #080b0f;
|
||||
--bg-overlay: rgba(13, 18, 26, 0.78);
|
||||
--bg-panel: #101822;
|
||||
--bg-control: #080b0f;
|
||||
--bg-control-hover: #0d121a;
|
||||
--app-bg: #f3f1ec;
|
||||
--widget-shell-bg: #f6f4ef;
|
||||
--widget-shell-graph-bg: #f4f2ec;
|
||||
--brand-accent: #78746c;
|
||||
--brand-opacity: 0.66;
|
||||
--widget-glow-inner: rgba(255, 255, 255, 0.62);
|
||||
--widget-glow-mid: rgba(110, 154, 220, 0.2);
|
||||
--widget-glow-outer: rgba(63, 126, 232, 0.08);
|
||||
|
||||
--border: #263348;
|
||||
--border-muted: #161f2c;
|
||||
--border-control: #263348;
|
||||
--border-accent: rgba(75, 160, 250, 0.5);
|
||||
--bg-primary: #f6f4ef;
|
||||
--bg-secondary: #efede7;
|
||||
--bg-muted: #e7e3da;
|
||||
--bg-elevated: #f8f7f3;
|
||||
--bg-overlay: rgba(246, 244, 239, 0.74);
|
||||
--bg-panel: #f1efe8;
|
||||
--bg-control: #fbfaf7;
|
||||
--bg-control-hover: #f4f2ec;
|
||||
|
||||
--text-primary: #fafafa;
|
||||
--text-secondary: #d0dae7;
|
||||
--text-muted: #8b8b8b;
|
||||
--text-inverse: #0b1119;
|
||||
--border: #ddd8ce;
|
||||
--border-muted: #e9e5dc;
|
||||
--border-control: #dcd6ca;
|
||||
--border-accent: rgba(63, 126, 232, 0.42);
|
||||
|
||||
--success: #4ade80;
|
||||
--success-muted: rgba(74, 222, 128, 0.14);
|
||||
--error: #fb7185;
|
||||
--error-muted: rgba(251, 113, 133, 0.14);
|
||||
--warning: #fbbf24;
|
||||
--warning-muted: rgba(251, 191, 36, 0.14);
|
||||
--info: #60a5fa;
|
||||
--info-muted: rgba(96, 165, 250, 0.14);
|
||||
--danger: #ef4444;
|
||||
--card-bg: #f8f7f3;
|
||||
--card-bg-hover: #f3f1eb;
|
||||
--card-border: #ddd8ce;
|
||||
--card-border-hover: #cfc7b8;
|
||||
--card-active-bg: #f8f7f3;
|
||||
--card-active-border: rgba(63, 126, 232, 0.42);
|
||||
|
||||
--font-sans:
|
||||
"DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-display:
|
||||
"DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-brand:
|
||||
"DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--panel-bg: #f1efe8;
|
||||
--panel-border: #ddd8ce;
|
||||
--panel-shadow: 0 10px 24px rgba(74, 68, 58, 0.08);
|
||||
|
||||
--button-primary-bg: #111827;
|
||||
--button-primary-hover: #1f2937;
|
||||
--button-primary-active: #0b1120;
|
||||
--button-primary-text: #ffffff;
|
||||
--button-secondary-bg: #f8f7f3;
|
||||
--button-secondary-hover: #f1eee7;
|
||||
--button-secondary-text: #111827;
|
||||
--button-secondary-border: #dcd6ca;
|
||||
--button-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 1px 2px rgba(15, 23, 42, 0.1);
|
||||
|
||||
--text-primary: #111827;
|
||||
--text-secondary: #4b5563;
|
||||
--text-muted: #8a94a3;
|
||||
--text-inverse: #ffffff;
|
||||
|
||||
--success: #16a34a;
|
||||
--success-muted: rgba(22, 163, 74, 0.1);
|
||||
--error: #dc2626;
|
||||
--error-muted: rgba(220, 38, 38, 0.1);
|
||||
--warning: #d97706;
|
||||
--warning-muted: rgba(217, 119, 6, 0.12);
|
||||
--info: #2563eb;
|
||||
--info-muted: rgba(37, 99, 235, 0.1);
|
||||
--danger: #dc2626;
|
||||
|
||||
--font-sans: "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-display: "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-brand: "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
|
||||
--shadow-sm: 0 2px 12px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 12px 34px rgba(0, 0, 0, 0.28);
|
||||
--shadow-lg: 0 20px 54px rgba(0, 0, 0, 0.38);
|
||||
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
|
||||
--shadow-md: 0 12px 28px rgba(15, 23, 42, 0.09);
|
||||
--shadow-lg: 0 20px 44px rgba(15, 23, 42, 0.12);
|
||||
|
||||
--graph-bg: #0b1119;
|
||||
--graph-doc-fill: #1b1f24;
|
||||
--graph-doc-stroke: rgba(255, 255, 255, 0.12);
|
||||
--graph-doc-inner: #0d121a;
|
||||
--graph-mem-fill: #0d2034;
|
||||
--graph-mem-fill-hover: #112840;
|
||||
--graph-mem-stroke: #4ba0fa;
|
||||
--graph-accent: #4ba0fa;
|
||||
--graph-text-primary: #fafafa;
|
||||
--graph-text-secondary: #d0dae7;
|
||||
--graph-text-muted: #a0aec4;
|
||||
--graph-edge-derives: #fbbf24;
|
||||
--graph-edge-updates: #4ba0fa;
|
||||
--graph-edge-extends: #36fdfd;
|
||||
--graph-mem-border-forgotten: #ef4444;
|
||||
--graph-mem-border-expiring: #f59e0b;
|
||||
--graph-mem-border-recent: #10b981;
|
||||
--graph-glow: #4ba0fa;
|
||||
--graph-icon: #4ba0fa;
|
||||
--graph-popover-bg: rgba(27, 31, 36, 0.94);
|
||||
--graph-popover-border: rgba(255, 255, 255, 0.12);
|
||||
--graph-popover-text-primary: #fafafa;
|
||||
--graph-popover-text-secondary: #d0dae7;
|
||||
--graph-popover-text-muted: #a0aec4;
|
||||
--graph-control-bg: rgba(13, 18, 26, 0.78);
|
||||
--graph-control-border: rgba(255, 255, 255, 0.12);
|
||||
--graph-bg: #f3f1ec;
|
||||
--graph-doc-fill: #f8f7f3;
|
||||
--graph-doc-stroke: #ddd8ce;
|
||||
--graph-doc-inner: #efede7;
|
||||
--graph-mem-fill: #edf2f7;
|
||||
--graph-mem-fill-hover: #e3ebf3;
|
||||
--graph-mem-stroke: #6aa6f8;
|
||||
--graph-accent: #3f7ee8;
|
||||
--graph-text-primary: #111827;
|
||||
--graph-text-secondary: #4b5563;
|
||||
--graph-text-muted: #8a94a3;
|
||||
--graph-edge-derives: #c08403;
|
||||
--graph-edge-updates: #3f7ee8;
|
||||
--graph-edge-extends: #0891b2;
|
||||
--graph-mem-border-forgotten: #dc2626;
|
||||
--graph-mem-border-expiring: #d97706;
|
||||
--graph-mem-border-recent: #16a34a;
|
||||
--graph-glow: #6aa6f8;
|
||||
--graph-icon: #6b7280;
|
||||
--graph-popover-bg: #f8f7f3;
|
||||
--graph-popover-border: #ddd8ce;
|
||||
--graph-popover-text-primary: #111827;
|
||||
--graph-popover-text-secondary: #4b5563;
|
||||
--graph-popover-text-muted: #8a94a3;
|
||||
--graph-control-bg: #f8f7f3;
|
||||
--graph-control-border: #ddd8ce;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
|
||||
--accent-start: #6aa6f8;
|
||||
--accent-mid: #6aa6f8;
|
||||
--accent-end: #8bd4ff;
|
||||
--accent-gradient: linear-gradient(94deg, var(--accent-start), var(--accent-end));
|
||||
--accent: #6aa6f8;
|
||||
--accent-foreground: #0f141a;
|
||||
--accent-hover: #82b7ff;
|
||||
--accent-muted: rgba(106, 166, 248, 0.12);
|
||||
|
||||
--app-bg: #12161b;
|
||||
--widget-shell-bg: #151a20;
|
||||
--widget-shell-graph-bg: #11161c;
|
||||
--brand-accent: #7f8996;
|
||||
--brand-opacity: 0.56;
|
||||
--widget-glow-inner: rgba(255, 255, 255, 0.12);
|
||||
--widget-glow-mid: rgba(106, 166, 248, 0.16);
|
||||
--widget-glow-outer: rgba(106, 166, 248, 0.08);
|
||||
|
||||
--bg-primary: #151a20;
|
||||
--bg-secondary: #181e25;
|
||||
--bg-muted: #20262e;
|
||||
--bg-elevated: #1a2027;
|
||||
--bg-overlay: rgba(21, 26, 32, 0.72);
|
||||
--bg-panel: #1d232b;
|
||||
--bg-control: #14191f;
|
||||
--bg-control-hover: #1b222a;
|
||||
|
||||
--border: #2b333d;
|
||||
--border-muted: #232a33;
|
||||
--border-control: #303946;
|
||||
--border-accent: rgba(106, 166, 248, 0.38);
|
||||
|
||||
--card-bg: #1a2026;
|
||||
--card-bg-hover: #202731;
|
||||
--card-border: #2b333d;
|
||||
--card-border-hover: #3a4654;
|
||||
--card-active-bg: #1b222b;
|
||||
--card-active-border: rgba(106, 166, 248, 0.44);
|
||||
|
||||
--panel-bg: #1c222a;
|
||||
--panel-border: #2b333d;
|
||||
--panel-shadow: 0 14px 34px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
||||
|
||||
--button-primary-bg: #eef4ff;
|
||||
--button-primary-hover: #ffffff;
|
||||
--button-primary-active: #dceaff;
|
||||
--button-primary-text: #101820;
|
||||
--button-secondary-bg: #202731;
|
||||
--button-secondary-hover: #27303b;
|
||||
--button-secondary-text: #eef2f7;
|
||||
--button-secondary-border: #34404d;
|
||||
--button-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 1px 2px rgba(0, 0, 0, 0.22);
|
||||
|
||||
--text-primary: #f4f6f8;
|
||||
--text-secondary: #c1c9d2;
|
||||
--text-muted: #87909c;
|
||||
--text-inverse: #111827;
|
||||
|
||||
--success: #4ade80;
|
||||
--success-muted: rgba(74, 222, 128, 0.12);
|
||||
--error: #fb7185;
|
||||
--error-muted: rgba(251, 113, 133, 0.12);
|
||||
--warning: #fbbf24;
|
||||
--warning-muted: rgba(251, 191, 36, 0.12);
|
||||
--info: #8bbcff;
|
||||
--info-muted: rgba(139, 188, 255, 0.12);
|
||||
--danger: #fb7185;
|
||||
|
||||
--font-sans: "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-display: "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-brand: "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||
--shadow-md: 0 12px 30px rgba(0, 0, 0, 0.22);
|
||||
--shadow-lg: 0 20px 48px rgba(0, 0, 0, 0.28);
|
||||
|
||||
--graph-bg: #151a20;
|
||||
--graph-doc-fill: #202731;
|
||||
--graph-doc-stroke: #35404c;
|
||||
--graph-doc-inner: #171d24;
|
||||
--graph-mem-fill: #172536;
|
||||
--graph-mem-fill-hover: #1b2d42;
|
||||
--graph-mem-stroke: #6aa6f8;
|
||||
--graph-accent: #6aa6f8;
|
||||
--graph-text-primary: #f4f6f8;
|
||||
--graph-text-secondary: #c1c9d2;
|
||||
--graph-text-muted: #87909c;
|
||||
--graph-edge-derives: #fbbf24;
|
||||
--graph-edge-updates: #6aa6f8;
|
||||
--graph-edge-extends: #67e8f9;
|
||||
--graph-mem-border-forgotten: #fb7185;
|
||||
--graph-mem-border-expiring: #f59e0b;
|
||||
--graph-mem-border-recent: #4ade80;
|
||||
--graph-glow: #6aa6f8;
|
||||
--graph-icon: #7ab2ff;
|
||||
--graph-popover-bg: #1c222a;
|
||||
--graph-popover-border: #34404d;
|
||||
--graph-popover-text-primary: #f4f6f8;
|
||||
--graph-popover-text-secondary: #c1c9d2;
|
||||
--graph-popover-text-muted: #87909c;
|
||||
--graph-control-bg: #1c222a;
|
||||
--graph-control-border: #34404d;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { cn } from "../lib/cn"
|
|||
const cardStyles = cva(
|
||||
[
|
||||
"group relative overflow-hidden text-left",
|
||||
"rounded-lg border border-[#0D121A] bg-bg-elevated p-4",
|
||||
"rounded-lg border border-[var(--card-border)] bg-[var(--card-bg)] p-4",
|
||||
"transition-all duration-200",
|
||||
].join(" "),
|
||||
{
|
||||
|
|
@ -17,9 +17,9 @@ const cardStyles = cva(
|
|||
variant: {
|
||||
default: "",
|
||||
interactive:
|
||||
"cursor-pointer hover:border-[#3A4455] hover:bg-bg-control-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/20 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary",
|
||||
"cursor-pointer hover:border-[var(--card-border-hover)] hover:bg-[var(--card-bg-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/20 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary",
|
||||
active:
|
||||
"cursor-pointer border-[#263348] bg-bg-elevated hover:border-[#3A4455] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/20 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary",
|
||||
"cursor-pointer border-[var(--card-active-border)] bg-[var(--card-active-bg)] hover:border-[var(--card-border-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/20 focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary",
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: "default" },
|
||||
|
|
@ -37,8 +37,14 @@ type ButtonElementProps = ButtonHTMLAttributes<HTMLButtonElement> &
|
|||
export type CardProps = DivProps | ButtonElementProps
|
||||
|
||||
export const Card = forwardRef<HTMLElement, CardProps>(
|
||||
({ className, variant, as = "div", ...props }, ref) => {
|
||||
({ className, variant, as = "div", children, ...props }, ref) => {
|
||||
const cls = cn(cardStyles({ variant }), className)
|
||||
const inner = (
|
||||
<>
|
||||
<div aria-hidden className="card-glow" />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
if (as === "button") {
|
||||
return (
|
||||
<button
|
||||
|
|
@ -46,7 +52,9 @@ export const Card = forwardRef<HTMLElement, CardProps>(
|
|||
ref={ref as React.Ref<HTMLButtonElement>}
|
||||
type="button"
|
||||
{...(props as ButtonHTMLAttributes<HTMLButtonElement>)}
|
||||
/>
|
||||
>
|
||||
{inner}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
return (
|
||||
|
|
@ -54,7 +62,9 @@ export const Card = forwardRef<HTMLElement, CardProps>(
|
|||
className={cls}
|
||||
ref={ref as React.Ref<HTMLDivElement>}
|
||||
{...(props as HTMLAttributes<HTMLDivElement>)}
|
||||
/>
|
||||
>
|
||||
{inner}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const chipStyles = cva(
|
|||
selected: {
|
||||
true: "bg-accent-muted border-accent text-accent",
|
||||
false:
|
||||
"bg-bg-elevated border-[#0D121A] text-text-secondary hover:border-[#3A4455] hover:bg-bg-control-hover",
|
||||
"bg-bg-elevated border-[var(--card-border)] text-text-secondary hover:border-[var(--card-border-hover)] hover:bg-bg-control-hover",
|
||||
},
|
||||
},
|
||||
defaultVariants: { selected: false },
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export function FileUpload({
|
|||
"bg-[var(--bg-control)] cursor-pointer shadow-sm transition-all",
|
||||
dragOver
|
||||
? "border-accent bg-accent-muted"
|
||||
: "border-[var(--border-control)] hover:border-[#3A4455] hover:bg-[var(--bg-control-hover)]",
|
||||
: "border-[var(--border-control)] hover:border-[var(--card-border-hover)] hover:bg-[var(--bg-control-hover)]",
|
||||
disabled && "pointer-events-none opacity-50",
|
||||
className,
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { type ReactNode, useEffect, useMemo, useState } from "react"
|
||||
import { Check, ChevronDown, Package, Search } from "../../lib/icons"
|
||||
import { Check, ChevronDown, Search } from "../../lib/icons"
|
||||
import { cn } from "../lib/cn"
|
||||
import { Button } from "./Button"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./Popover"
|
||||
|
|
@ -63,13 +63,12 @@ export function WorkspaceSelect({
|
|||
<Button
|
||||
brandFont={false}
|
||||
className={cn(
|
||||
"w-full justify-between normal-case tracking-normal",
|
||||
"workspace-select-trigger mcp-soft-button w-full justify-between normal-case tracking-normal",
|
||||
"focus-visible:ring-0 focus-visible:ring-offset-0",
|
||||
"data-[state=open]:bg-bg-muted",
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
iconLeft={<Package className="size-4 text-text-secondary" />}
|
||||
iconRight={<ChevronDown className="size-3 text-text-muted" />}
|
||||
size="sm"
|
||||
type="button"
|
||||
|
|
@ -127,9 +126,7 @@ export function WorkspaceSelect({
|
|||
>
|
||||
<span className="flex min-w-0 flex-col gap-0.5">
|
||||
<span className="flex items-center gap-(--space-2)">
|
||||
{option.icon ?? (
|
||||
<Package className="size-3.5 text-text-muted shrink-0" />
|
||||
)}
|
||||
{option.icon ?? null}
|
||||
<span className="text-(length:--text-sm) text-text-primary truncate">
|
||||
{option.label}
|
||||
</span>
|
||||
|
|
|
|||
17
apps/mcp/src/widget/lib/formatTag.ts
Normal file
17
apps/mcp/src/widget/lib/formatTag.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Turns a raw container-tag identifier into a human-friendly label.
|
||||
*
|
||||
* "sm_project_marketing" → "Marketing"
|
||||
* "sm_project_eng_rfcs" → "Eng Rfcs"
|
||||
* "my_custom_workspace" → "My Custom Workspace"
|
||||
*/
|
||||
export function formatTagLabel(raw: string): string {
|
||||
let slug = raw
|
||||
.replace(/^sm_project_/i, "")
|
||||
.replace(/^sm_/i, "")
|
||||
|
||||
return slug
|
||||
.split("_")
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
.join(" ")
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ import {
|
|||
|
||||
type Theme = "light" | "dark"
|
||||
type Width = "narrow" | "wide" | "cursor"
|
||||
type Version = "v1" | "v2" | "v3" | "v4"
|
||||
|
||||
const NARROW = 420
|
||||
const WIDE = 720
|
||||
|
|
@ -122,6 +123,7 @@ function Swatch({ name, varName }: { name: string; varName: string }) {
|
|||
export function Studio() {
|
||||
const [theme, setTheme] = useState<Theme>("light")
|
||||
const [width, setWidth] = useState<Width>("cursor")
|
||||
const [version, setVersion] = useState<Version>("v1")
|
||||
const frameWidth =
|
||||
width === "narrow" ? NARROW : width === "wide" ? WIDE : CURSOR
|
||||
|
||||
|
|
@ -165,6 +167,23 @@ export function Studio() {
|
|||
Dark
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="flex items-center gap-(--space-2)">
|
||||
<span className="text-(length:--text-xs) text-text-muted">
|
||||
Version
|
||||
</span>
|
||||
<Chip onClick={() => setVersion("v1")} selected={version === "v1"}>
|
||||
v1
|
||||
</Chip>
|
||||
<Chip onClick={() => setVersion("v2")} selected={version === "v2"}>
|
||||
v2
|
||||
</Chip>
|
||||
<Chip onClick={() => setVersion("v3")} selected={version === "v3"}>
|
||||
v3
|
||||
</Chip>
|
||||
<Chip onClick={() => setVersion("v4")} selected={version === "v4"}>
|
||||
v4
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="flex items-center gap-(--space-2)">
|
||||
<span className="text-(length:--text-xs) text-text-muted">
|
||||
Width
|
||||
|
|
@ -235,7 +254,7 @@ export function Studio() {
|
|||
<div className="flex flex-wrap items-center gap-(--space-2)">
|
||||
<Chip selected>Selected</Chip>
|
||||
<Chip>Unselected</Chip>
|
||||
<Chip>sm_project_marketing</Chip>
|
||||
<Chip>Marketing</Chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -346,7 +365,7 @@ export function Studio() {
|
|||
description="The same widget shell Cursor renders, with mock MCP tool data."
|
||||
title="Cursor widgets"
|
||||
>
|
||||
<div className="flex flex-wrap gap-(--space-8)">
|
||||
<div data-widget-version={version} className="flex flex-wrap gap-(--space-8)">
|
||||
<Frame label="Picker" width={frameWidth}>
|
||||
<WidgetShell>
|
||||
<Picker
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Badge, Stack } from "../design/ui"
|
||||
import { CheckCircle } from "../lib/icons"
|
||||
import { formatTagLabel } from "../lib/formatTag"
|
||||
|
||||
interface Props {
|
||||
containerTag: string
|
||||
|
|
@ -9,15 +10,16 @@ export function Confirmation({ containerTag }: Props) {
|
|||
return (
|
||||
<Stack
|
||||
align="center"
|
||||
className="mx-(--page-header-px) my-(--space-6) rounded-[20px] bg-[#1B1F24] px-(--page-header-px) py-(--space-10) text-center shadow-[0_2.842px_14.211px_0_rgba(0,0,0,0.25),inset_0.711px_0.711px_0.711px_0_rgba(255,255,255,0.10)]"
|
||||
className="mcp-panel mx-(--page-header-px) my-(--space-6) rounded-[20px] border border-[var(--panel-border)] bg-[var(--panel-bg)] px-(--page-header-px) py-(--space-10) text-center shadow-[var(--panel-shadow)]"
|
||||
gap="md"
|
||||
>
|
||||
<div aria-hidden className="panel-glow" />
|
||||
<CheckCircle className="size-12 text-success" />
|
||||
<Stack align="center" gap="xs">
|
||||
<div className="text-(length:--text-sm) font-medium text-text-primary">
|
||||
Active workspace set
|
||||
</div>
|
||||
<Badge variant="accent">{containerTag}</Badge>
|
||||
<Badge variant="accent">{formatTagLabel(containerTag)}</Badge>
|
||||
</Stack>
|
||||
<p className="max-w-xs text-(length:--text-xs) text-text-muted">
|
||||
All subsequent saves and recalls will use this workspace until you
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function ErrorView({ message }: Props) {
|
|||
return (
|
||||
<Stack
|
||||
align="center"
|
||||
className="mx-(--page-header-px) my-(--space-6) rounded-[20px] bg-[#1B1F24] px-(--page-header-px) py-(--space-10) text-center shadow-[0_2.842px_14.211px_0_rgba(0,0,0,0.25),inset_0.711px_0.711px_0.711px_0_rgba(255,255,255,0.10)]"
|
||||
className="mx-(--page-header-px) my-(--space-6) rounded-[20px] border border-[var(--panel-border)] bg-[var(--panel-bg)] px-(--page-header-px) py-(--space-10) text-center shadow-[var(--panel-shadow)]"
|
||||
gap="md"
|
||||
>
|
||||
<WarningCircle className="size-12 text-error" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export function Loading() {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-(--space-12)">
|
||||
<div className="mcp-widget-loading flex items-center justify-center py-(--space-12)">
|
||||
<output
|
||||
aria-label="Loading..."
|
||||
className="super-loader"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { WorkspaceCard } from "../components/WorkspaceCard"
|
|||
import { Input, PageHeader } from "../design/ui"
|
||||
import { useApp } from "../hooks/useApp"
|
||||
import { useLog } from "../hooks/useLog"
|
||||
import { formatTagLabel } from "../lib/formatTag"
|
||||
import { Search } from "../lib/icons"
|
||||
|
||||
interface Props {
|
||||
|
|
@ -62,11 +63,11 @@ export function Picker({
|
|||
const description =
|
||||
count === 0
|
||||
? "No workspaces available."
|
||||
: `${count} workspace${count === 1 ? "" : "s"} available — select one to set your active context.`
|
||||
: `${count} workspace${count === 1 ? "" : "s"} available`
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<PageHeader description={description} title="Select Workspace" />
|
||||
<PageHeader description={description} title="Workspaces" />
|
||||
<div className="flex flex-col gap-(--space-4) px-(--page-header-px) pb-(--space-6)">
|
||||
{count >= SEARCH_THRESHOLD ? (
|
||||
<div className="relative max-w-sm">
|
||||
|
|
@ -87,7 +88,7 @@ export function Picker({
|
|||
No workspaces match “{query}”.
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-(--space-2)">
|
||||
<div className="workspace-picker-grid grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-(--space-2)">
|
||||
{filtered.map((tag) => {
|
||||
const access = assignedTags?.find(
|
||||
(t) => t.containerTag === tag.containerTag,
|
||||
|
|
@ -108,7 +109,7 @@ export function Picker({
|
|||
|
||||
{pending ? (
|
||||
<p className="text-(length:--text-xs) text-text-muted">
|
||||
Setting workspace to {pending}…
|
||||
Setting workspace to {formatTagLabel(pending)}…
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "../design/ui"
|
||||
import { useApp } from "../hooks/useApp"
|
||||
import { useLog } from "../hooks/useLog"
|
||||
import { formatTagLabel } from "../lib/formatTag"
|
||||
|
||||
interface Props {
|
||||
activeTag?: string | null
|
||||
|
|
@ -42,7 +43,12 @@ export function Save({
|
|||
}, [selectedTag, writableTags])
|
||||
|
||||
const options = useMemo(
|
||||
() => writableTags.map((tag) => ({ value: tag, label: tag })),
|
||||
() =>
|
||||
writableTags.map((tag) => ({
|
||||
value: tag,
|
||||
label: formatTagLabel(tag),
|
||||
description: tag,
|
||||
})),
|
||||
[writableTags],
|
||||
)
|
||||
|
||||
|
|
@ -73,7 +79,8 @@ export function Save({
|
|||
title="Add Memory"
|
||||
/>
|
||||
<div className="px-(--page-header-px) pb-(--space-6)">
|
||||
<div className="rounded-[20px] bg-[#1B1F24] p-(--space-4) shadow-[0_2.842px_14.211px_0_rgba(0,0,0,0.25),inset_0.711px_0.711px_0.711px_0_rgba(255,255,255,0.10)]">
|
||||
<div className="mcp-panel rounded-[20px] border border-[var(--panel-border)] bg-[var(--panel-bg)] p-(--space-4) shadow-[var(--panel-shadow)]">
|
||||
<div aria-hidden className="panel-glow" />
|
||||
<Stack gap="lg">
|
||||
<Field label="Memory">
|
||||
<TextArea
|
||||
|
|
@ -97,6 +104,7 @@ export function Save({
|
|||
<div className="flex justify-end pt-(--space-2)">
|
||||
<ActionGroup>
|
||||
<Button
|
||||
className="mcp-soft-button"
|
||||
disabled={!canSave}
|
||||
loading={saving}
|
||||
onClick={handleSave}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Badge, Stack } from "../design/ui"
|
||||
import { CheckCircle } from "../lib/icons"
|
||||
import { formatTagLabel } from "../lib/formatTag"
|
||||
|
||||
interface SaveProps {
|
||||
kind: "save"
|
||||
|
|
@ -21,15 +22,16 @@ export function Success(props: Props) {
|
|||
return (
|
||||
<Stack
|
||||
align="center"
|
||||
className="mx-(--page-header-px) my-(--space-6) rounded-[20px] bg-[#1B1F24] px-(--page-header-px) py-(--space-10) text-center shadow-[0_2.842px_14.211px_0_rgba(0,0,0,0.25),inset_0.711px_0.711px_0.711px_0_rgba(255,255,255,0.10)]"
|
||||
className="mcp-panel mx-(--page-header-px) my-(--space-6) rounded-[20px] border border-[var(--panel-border)] bg-[var(--panel-bg)] px-(--page-header-px) py-(--space-10) text-center shadow-[var(--panel-shadow)]"
|
||||
gap="md"
|
||||
>
|
||||
<div aria-hidden className="panel-glow" />
|
||||
<CheckCircle className="size-12 text-success" />
|
||||
<Stack align="center" gap="xs">
|
||||
<div className="text-(length:--text-sm) font-medium text-text-primary">
|
||||
{isUpload ? `Uploaded ${props.fileName}` : "Memory saved"}
|
||||
</div>
|
||||
<Badge variant="accent">{props.containerTag}</Badge>
|
||||
<Badge variant="accent">{formatTagLabel(props.containerTag)}</Badge>
|
||||
</Stack>
|
||||
<p className="break-all font-mono text-(length:--text-xs) text-text-muted">
|
||||
{props.id}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "../design/ui"
|
||||
import { useApp } from "../hooks/useApp"
|
||||
import { useLog } from "../hooks/useLog"
|
||||
import { formatTagLabel } from "../lib/formatTag"
|
||||
import { FileText, X } from "../lib/icons"
|
||||
import { readFileAsBase64 } from "../lib/readFileAsBase64"
|
||||
|
||||
|
|
@ -39,7 +40,12 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) {
|
|||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
const options = useMemo(
|
||||
() => writableTags.map((tag) => ({ value: tag, label: tag })),
|
||||
() =>
|
||||
writableTags.map((tag) => ({
|
||||
value: tag,
|
||||
label: formatTagLabel(tag),
|
||||
description: tag,
|
||||
})),
|
||||
[writableTags],
|
||||
)
|
||||
|
||||
|
|
@ -78,7 +84,8 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) {
|
|||
title="Upload File"
|
||||
/>
|
||||
<div className="px-(--page-header-px) pb-(--space-6)">
|
||||
<div className="rounded-[20px] bg-[#1B1F24] p-(--space-4) shadow-[0_2.842px_14.211px_0_rgba(0,0,0,0.25),inset_0.711px_0.711px_0.711px_0_rgba(255,255,255,0.10)]">
|
||||
<div className="mcp-panel rounded-[20px] border border-[var(--panel-border)] bg-[var(--panel-bg)] p-(--space-4) shadow-[var(--panel-shadow)]">
|
||||
<div aria-hidden className="panel-glow" />
|
||||
<Stack gap="lg">
|
||||
{file ? (
|
||||
<div className="flex items-center justify-between gap-(--space-3) rounded-(--radius-lg) border border-[var(--border-control)] bg-[var(--bg-control)] p-(--space-3) shadow-sm">
|
||||
|
|
@ -122,6 +129,7 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) {
|
|||
<div className="flex justify-end pt-(--space-2)">
|
||||
<ActionGroup>
|
||||
<Button
|
||||
className="mcp-soft-button"
|
||||
disabled={!canUpload}
|
||||
loading={uploading}
|
||||
onClick={handleUpload}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue