feat(ui): refine gem-blue acrylic palette and focus borders
Derive the gem-blue palette with OKLCH tonal and complementary-color rules while preserving acrylic opacity, blur and surface hierarchy. Darker focus tokens exposed stacked field rings, fallback outlines and persistent basemap selection rings. Keep fallback focus styles in the base layer and use one themed field boundary or inset card stroke. Preserve keyboard focus, invalid states and native input semantics. Add browser regression coverage for pointer and keyboard focus, all basemap presentations, narrow layouts and rendered contrast. Validation in an isolated checkout: pnpm lint, pnpm typecheck, pnpm test (393 tests), focused browser suites and pnpm build.
This commit is contained in:
@@ -373,7 +373,7 @@ export function AgentCommandPanel({
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="min-w-0">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<p className="shrink-0 text-xs font-semibold uppercase text-slate-500">
|
||||
<p className="shrink-0 text-xs font-semibold uppercase text-[var(--ink-muted)]">
|
||||
对话主题
|
||||
</p>
|
||||
<StatusBadge
|
||||
@@ -587,8 +587,8 @@ export function AgentCommandPanel({
|
||||
className={cn(
|
||||
"agent-panel-band relative",
|
||||
floatingPresentation
|
||||
? "agent-panel-floating-acrylic agent-panel-composer absolute bottom-3 z-20 rounded-2xl p-1"
|
||||
: "border-t border-slate-200 p-3"
|
||||
? "form-field agent-panel-floating-acrylic agent-panel-composer absolute bottom-3 z-20 rounded-2xl p-1"
|
||||
: "border-t border-[var(--surface-divider)] p-3"
|
||||
)}
|
||||
>
|
||||
{!hasConversation ? (
|
||||
@@ -608,7 +608,7 @@ export function AgentCommandPanel({
|
||||
key={suggestion}
|
||||
suggestion={suggestion}
|
||||
onClick={submitPrompt}
|
||||
className="surface-reading max-w-full border border-slate-200 text-slate-600 hover:bg-blue-50 hover:text-blue-700"
|
||||
className="surface-reading max-w-full border border-[var(--surface-divider)] text-slate-600 hover:bg-[var(--action-soft)] hover:text-[var(--action-blue-hover)]"
|
||||
>
|
||||
<span className="truncate">{suggestion}</span>
|
||||
</Suggestion>
|
||||
@@ -617,7 +617,7 @@ export function AgentCommandPanel({
|
||||
</div>
|
||||
) : null}
|
||||
<PromptInput
|
||||
className="agent-panel-control overflow-hidden rounded-2xl shadow-xs [&>[data-slot=input-group]]:rounded-[inherit] [&>[data-slot=input-group]]:border-0 [&>[data-slot=input-group]]:shadow-none [&>[data-slot=input-group]]:ring-0!"
|
||||
className="form-field agent-panel-control overflow-hidden rounded-2xl shadow-xs [&>[data-slot=input-group]]:rounded-[inherit] [&>[data-slot=input-group]]:border-0 [&>[data-slot=input-group]]:shadow-none"
|
||||
onSubmit={(message) => {
|
||||
const nextPrompt = message.text.trim();
|
||||
if (nextPrompt) {
|
||||
@@ -638,7 +638,7 @@ export function AgentCommandPanel({
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter className="justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{!qaOnly ? <ApprovalModeSelect presentation={presentation} value={approvalMode} disabled={streaming} onValueChange={onApprovalModeChange} /> : <span className="px-1 text-[11px] text-slate-500">任务协作问答</span>}
|
||||
{!qaOnly ? <ApprovalModeSelect presentation={presentation} value={approvalMode} disabled={streaming} onValueChange={onApprovalModeChange} /> : <span className="px-1 text-[11px] text-[var(--ink-muted)]">任务协作问答</span>}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<AgentModelSelect
|
||||
@@ -712,7 +712,7 @@ function VoiceInputButton({
|
||||
"group relative grid h-8 w-8 shrink-0 place-items-center overflow-visible rounded-lg border after:absolute after:-inset-1 after:content-['']",
|
||||
"border-transparent bg-transparent shadow-none",
|
||||
"transition-[color,background-color,border-color,box-shadow,transform] duration-200",
|
||||
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25 focus-visible:ring-offset-1",
|
||||
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)] focus-visible:ring-offset-1",
|
||||
"disabled:cursor-not-allowed disabled:opacity-45",
|
||||
isListening
|
||||
? "status-tone-danger bg-[var(--status-soft)] text-[var(--status-foreground)] [@media(hover:hover)]:hover:border-[var(--status-border)] [@media(hover:hover)]:hover:brightness-95"
|
||||
@@ -822,7 +822,7 @@ function ApprovalModeSelect({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="shrink-0 gap-1.5 border-transparent bg-transparent px-1.5 text-xs text-slate-600 shadow-none hover:bg-slate-50"
|
||||
className="shrink-0 gap-1.5 border-transparent bg-transparent px-1.5 text-xs text-slate-600 shadow-none hover:bg-[var(--surface-reading)]"
|
||||
disabled={disabled || !onValueChange}
|
||||
aria-label="权限批准模式"
|
||||
>
|
||||
@@ -842,28 +842,28 @@ function ApprovalModeSelect({
|
||||
sideOffset={8}
|
||||
className="agent-panel-overlay agent-panel-control w-[220px] rounded-xl p-2 shadow-lg"
|
||||
>
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-slate-500">
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-[var(--ink-muted)]">
|
||||
权限批准模式
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
className={cn(
|
||||
"items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-slate-50",
|
||||
requestApproval && "border-blue-200 bg-blue-50 focus:bg-blue-50"
|
||||
"items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-[var(--surface-reading)]",
|
||||
requestApproval && "border-[var(--surface-border)] bg-[var(--action-soft)] focus:bg-[var(--action-soft)]"
|
||||
)}
|
||||
onSelect={() => onValueChange?.("request")}
|
||||
>
|
||||
<Shield className="mt-0.5 text-blue-600" aria-hidden="true" />
|
||||
<Shield className="mt-0.5 text-[var(--action-blue-hover)]" aria-hidden="true" />
|
||||
<span>
|
||||
<span className="block text-sm font-semibold text-slate-800">请求批准</span>
|
||||
<span className="block text-xs leading-5 text-slate-500">工具权限逐次确认</span>
|
||||
<span className="block text-xs leading-5 text-[var(--ink-muted)]">工具权限逐次确认</span>
|
||||
</span>
|
||||
{requestApproval ? (
|
||||
<CheckCircle2 className="ml-auto mt-0.5 text-blue-600" aria-hidden="true" />
|
||||
<CheckCircle2 className="ml-auto mt-0.5 text-[var(--action-blue-hover)]" aria-hidden="true" />
|
||||
) : null}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className={cn(
|
||||
"mt-1 items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-slate-50",
|
||||
"mt-1 items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-[var(--surface-reading)]",
|
||||
!requestApproval &&
|
||||
"status-tone-success border-[var(--status-border)] bg-[var(--status-soft)] focus:bg-[var(--status-soft)]"
|
||||
)}
|
||||
@@ -875,7 +875,7 @@ function ApprovalModeSelect({
|
||||
/>
|
||||
<span>
|
||||
<span className="block text-sm font-semibold text-slate-800">始终允许</span>
|
||||
<span className="block text-xs leading-5 text-slate-500">自动允许本轮权限请求</span>
|
||||
<span className="block text-xs leading-5 text-[var(--ink-muted)]">自动允许本轮权限请求</span>
|
||||
</span>
|
||||
{!requestApproval ? (
|
||||
<CheckCircle2
|
||||
@@ -993,7 +993,7 @@ function AgentModelSelect({
|
||||
|
||||
if (!models.length) {
|
||||
return (
|
||||
<span className="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-slate-500">模型</span>
|
||||
<span className="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-[var(--ink-muted)]">模型</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1004,7 +1004,7 @@ function AgentModelSelect({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-12 shrink-0 justify-between gap-1 border-transparent bg-transparent px-1 text-xs text-slate-600 shadow-none hover:bg-slate-50 sm:w-24 sm:gap-1.5 sm:px-1.5"
|
||||
className="w-12 shrink-0 justify-between gap-1 border-transparent bg-transparent px-1 text-xs text-slate-600 shadow-none hover:bg-[var(--surface-reading)] sm:w-24 sm:gap-1.5 sm:px-1.5"
|
||||
disabled={!onValueChange}
|
||||
aria-label="选择 Agent 模型"
|
||||
aria-description={`当前模型:${selectedModel?.label ?? "模型"}`}
|
||||
@@ -1024,7 +1024,7 @@ function AgentModelSelect({
|
||||
sideOffset={8}
|
||||
className="agent-panel-overlay agent-panel-control w-[230px] rounded-xl p-2 shadow-lg"
|
||||
>
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-slate-500">
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-[var(--ink-muted)]">
|
||||
模型选择
|
||||
</DropdownMenuLabel>
|
||||
<div className="space-y-1">
|
||||
@@ -1034,10 +1034,10 @@ function AgentModelSelect({
|
||||
<DropdownMenuItem
|
||||
key={model.id}
|
||||
className={cn(
|
||||
"rounded-lg border border-transparent px-2.5 py-2 focus:bg-slate-50",
|
||||
"rounded-lg border border-transparent px-2.5 py-2 focus:bg-[var(--surface-reading)]",
|
||||
selected && (presentation === "desktop-dock"
|
||||
? "border-blue-200 bg-blue-50 focus:bg-blue-50"
|
||||
: "border-violet-200 bg-violet-50 focus:bg-violet-50")
|
||||
? "border-[var(--surface-border)] bg-[var(--action-soft)] focus:bg-[var(--action-soft)]"
|
||||
: "border-[var(--surface-border)] bg-[var(--action-soft)] focus:bg-[var(--action-soft)]")
|
||||
)}
|
||||
onSelect={() => onValueChange?.(model.id)}
|
||||
>
|
||||
@@ -1048,12 +1048,12 @@ function AgentModelSelect({
|
||||
<span className="block truncate text-sm font-semibold text-slate-800">
|
||||
{model.label}
|
||||
</span>
|
||||
<span className="block truncate text-xs leading-4 text-slate-500">
|
||||
<span className="block truncate text-xs leading-4 text-[var(--ink-muted)]">
|
||||
{model.description}
|
||||
</span>
|
||||
</span>
|
||||
{selected ? (
|
||||
<CheckCircle2 size={15} className={presentation === "desktop-dock" ? "text-blue-600" : "text-violet-600"} aria-hidden="true" />
|
||||
<CheckCircle2 size={15} className={presentation === "desktop-dock" ? "text-[var(--action-blue-hover)]" : "text-[var(--action-blue-hover)]"} aria-hidden="true" />
|
||||
) : null}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
@@ -1099,7 +1099,7 @@ function ExpertModelIcon({ size }: { size: number }) {
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className="text-violet-600"
|
||||
className="text-[var(--action-blue-hover)]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
@@ -1116,21 +1116,21 @@ function AgentReadyMark() {
|
||||
<g className="agent-ready-orbit">
|
||||
<path
|
||||
d="M384.1536 952.1664a38.4 38.4 0 0 1-49.3568 22.528 498.3808 498.3808 0 0 1-284.928-273.92 38.4 38.4 0 0 1 70.8608-29.6448 421.5808 421.5808 0 0 0 240.896 231.6288 38.4 38.4 0 0 1 22.528 49.408zM952.1152 384.9728a38.4 38.4 0 0 1-49.4592-22.528 421.5296 421.5296 0 0 0-234.1376-241.5104 38.4 38.4 0 0 1 29.184-71.0656 498.3296 498.3296 0 0 1 276.8896 285.696 38.4 38.4 0 0 1-22.528 49.408z"
|
||||
fill="#CE75FF"
|
||||
fill="var(--palette-brand)"
|
||||
/>
|
||||
<path
|
||||
d="M981.248 768.0512a42.6496 42.6496 0 1 1-85.2992 0 42.6496 42.6496 0 0 1 85.2992 0zM127.9488 256.0512a42.6496 42.6496 0 1 1-85.3504 0 42.6496 42.6496 0 0 1 85.3504 0z"
|
||||
fill="#F62E76"
|
||||
fill="var(--palette-champagne)"
|
||||
/>
|
||||
<path
|
||||
d="M810.496 938.8544a42.6496 42.6496 0 1 1-85.2992 0 42.6496 42.6496 0 0 1 85.3504 0zM298.496 85.504a42.6496 42.6496 0 1 1-85.2992 0 42.6496 42.6496 0 0 1 85.3504 0z"
|
||||
fill="#CD88FF"
|
||||
fill="var(--palette-sand)"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
className="agent-ready-core"
|
||||
d="M511.9488 276.736l-27.8528 114.7392A126.0544 126.0544 0 0 1 391.3216 484.352l-114.7904 27.8528 114.7904 27.8016a126.0544 126.0544 0 0 1 92.7744 92.8256L512 747.52l27.8016-114.7392a126.0544 126.0544 0 0 1 92.8256-92.8256l114.7392-27.8016-114.7392-27.8528a126.0544 126.0544 0 0 1-92.8256-92.8256L512 276.736z m55.6544-62.1568c-14.1312-58.368-97.1776-58.368-111.36 0L417.28 375.296a57.344 57.344 0 0 1-42.1888 42.1888l-160.6656 38.912c-58.4192 14.1824-58.4192 97.28 0 111.4112l160.6656 38.9632c20.8384 5.12 37.12 21.3504 42.1888 42.1888l38.9632 160.7168c14.1824 58.368 97.2288 58.368 111.36 0l38.9632-160.7168a57.344 57.344 0 0 1 42.1888-42.1888l160.7168-38.912c58.368-14.1824 58.368-97.28 0-111.4112l-160.7168-38.9632a57.344 57.344 0 0 1-42.1888-42.1888l-38.912-160.7168z"
|
||||
fill="#F3E2FF"
|
||||
fill="var(--action-selection-soft)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -1200,8 +1200,8 @@ function AgentEmptyState({ compact = false }: { compact?: boolean }) {
|
||||
key={label}
|
||||
className={cn(
|
||||
"agent-capability flex min-h-20 items-center gap-3 px-3.5 py-3 text-left",
|
||||
index % 2 === 0 && "border-r border-slate-300/80",
|
||||
index < 2 && "border-b border-slate-300/80"
|
||||
index % 2 === 0 && "border-r border-[var(--surface-border)]/80",
|
||||
index < 2 && "border-b border-[var(--surface-border)]/80"
|
||||
)}
|
||||
>
|
||||
<span className="agent-capability-icon grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[#f9fbfe] text-[#334155] shadow-[inset_0_0_0_1px_rgba(148,163,184,0.28)]">
|
||||
@@ -1312,7 +1312,7 @@ function BackendMessageList({
|
||||
<p className="whitespace-pre-wrap">{message.content}</p>
|
||||
)
|
||||
) : streaming && index === messages.length - 1 ? (
|
||||
<span className="text-slate-500">正在生成...</span>
|
||||
<span className="text-[var(--ink-muted)]">正在生成...</span>
|
||||
) : null}
|
||||
{message.role === "assistant" && !qaOnly ? (
|
||||
<AgentMessageDetails
|
||||
@@ -1466,7 +1466,7 @@ function AgentSpeechMessage({
|
||||
key="speech-selection-action"
|
||||
type="button"
|
||||
data-agent-presentation={presentation}
|
||||
className="agent-panel-overlay agent-panel-floating-acrylic agent-speech-selection-action pointer-events-auto inline-flex h-10 items-center gap-2 rounded-xl px-2.5 pr-3 text-xs font-semibold text-slate-800 transition-colors hover:text-blue-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25"
|
||||
className="agent-panel-overlay agent-panel-floating-acrylic agent-speech-selection-action pointer-events-auto inline-flex h-10 items-center gap-2 rounded-xl px-2.5 pr-3 text-xs font-semibold text-slate-800 transition-colors hover:text-[var(--action-blue-hover)] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)]"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
@@ -1480,7 +1480,7 @@ function AgentSpeechMessage({
|
||||
onClick={speakFromSelection}
|
||||
>
|
||||
<span
|
||||
className="grid h-6 w-6 shrink-0 place-items-center rounded-lg bg-blue-50/75 text-blue-600"
|
||||
className="grid h-6 w-6 shrink-0 place-items-center rounded-lg bg-[var(--action-soft)]/75 text-[var(--action-blue-hover)]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Volume2 size={15} />
|
||||
@@ -1495,7 +1495,7 @@ function AgentSpeechMessage({
|
||||
: null}
|
||||
|
||||
{isSupported && !disabled ? (
|
||||
<div className="mt-2 flex min-h-8 items-center gap-1 border-t border-slate-100 pt-1.5">
|
||||
<div className="mt-2 flex min-h-8 items-center gap-1 border-t border-[var(--surface-divider)] pt-1.5">
|
||||
{speechState === "idle" ? (
|
||||
<SpeechIconButton
|
||||
label="语音播放"
|
||||
@@ -1507,7 +1507,7 @@ function AgentSpeechMessage({
|
||||
{speechState === "loading" ? (
|
||||
<>
|
||||
<SpeechIconButton label="正在生成语音" disabled>
|
||||
<LoaderCircle size={15} className="animate-spin text-blue-600" aria-hidden="true" />
|
||||
<LoaderCircle size={15} className="animate-spin text-[var(--action-blue-hover)]" aria-hidden="true" />
|
||||
</SpeechIconButton>
|
||||
<SpeechIconButton label="停止语音播放" tone="danger" onClick={onStop}>
|
||||
<Square size={14} fill="currentColor" aria-hidden="true" />
|
||||
@@ -1534,7 +1534,7 @@ function AgentSpeechMessage({
|
||||
</SpeechIconButton>
|
||||
</>
|
||||
) : null}
|
||||
<span className="text-[11px] text-slate-400">
|
||||
<span className="text-[11px] text-[var(--ink-muted)]">
|
||||
{speechState === "idle"
|
||||
? "语音播放"
|
||||
: speechState === "loading"
|
||||
@@ -1572,8 +1572,8 @@ function SpeechIconButton({
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"shrink-0 text-slate-500 hover:bg-slate-100 disabled:cursor-wait",
|
||||
tone === "primary" && "text-blue-600",
|
||||
"shrink-0 text-[var(--ink-muted)] hover:bg-[var(--surface-control)] disabled:cursor-wait",
|
||||
tone === "primary" && "text-[var(--action-blue-hover)]",
|
||||
tone === "danger" && "status-tone-danger text-[var(--status-foreground)]"
|
||||
)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user