@@ -921,7 +1078,7 @@ function AgentEmptyState({ compact = false }: { compact?: boolean }) {
@@ -942,7 +1099,7 @@ function AgentEmptyState({ compact = false }: { compact?: boolean }) {
@@ -1021,9 +1178,10 @@ function BackendMessageList({
>
@@ -1209,21 +1367,16 @@ function AgentSpeechMessage({
event.preventDefault()}
onClick={speakFromSelection}
>
@@ -1311,20 +1464,21 @@ function SpeechIconButton({
children: ReactNode;
}) {
return (
-
+
);
}
diff --git a/src/features/agent/components/agent-history-panel.tsx b/src/features/agent/components/agent-history-panel.tsx
index cd2daac..e414311 100644
--- a/src/features/agent/components/agent-history-panel.tsx
+++ b/src/features/agent/components/agent-history-panel.tsx
@@ -1,8 +1,10 @@
import { Check, Loader2, Pencil, RefreshCw, Trash2, X } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
+import { Button } from "@/shared/ui/button";
import { Input } from "@/shared/ui/input";
import { cn } from "@/shared/ui/cn";
+import { StatusBadge, StatusDot } from "@/shared/ui/status";
import type { AgentChatSessionSummary } from "../api/client";
import {
agentLayoutTransition,
@@ -73,30 +75,35 @@ export function AgentHistoryPanel({
历史记录
-
{sessions.length ? `${sessions.length} 个会话` : "暂无历史会话"}
+
+ {sessions.length ? `${sessions.length} 个会话` : "暂无历史会话"}
+
-
+
+
-
+
{loading && sessions.length === 0 ? (
{confirmingDelete ? (
-
-
+
+
删除“{session.title}”?
@@ -153,7 +172,7 @@ export function AgentHistoryPanel({
>
setDraftTitle(event.currentTarget.value)}
@@ -176,7 +195,10 @@ export function AgentHistoryPanel({
variants={agentPanelItemVariants}
onClick={() => onSelectSession(session.id)}
>
-
+
{session.title}
@@ -185,79 +207,89 @@ export function AgentHistoryPanel({
)}
-
+
{session.runStatus === "running" || session.isStreaming ? (
-
+
) : null}
{itemLoading || saving || deleting ? (
-
+
) : null}
{active && !editing && !confirmingDelete ? (
-
- 当前
-
+ 当前
) : null}
{confirmingDelete ? (
<>
-
-
>
) : editing ? (
<>
- saveRename(session)}
>
-
-
+
-
+
>
) : (
<>
- beginRename(session)}
>
-
-
+ {
cancelRename();
@@ -265,7 +297,7 @@ export function AgentHistoryPanel({
}}
>
-
+
>
)}
@@ -273,7 +305,14 @@ export function AgentHistoryPanel({
);
})
) : (
-
+
暂无历史记录
)}
diff --git a/src/features/agent/components/agent-message-details.tsx b/src/features/agent/components/agent-message-details.tsx
index c8c6088..8a9c152 100644
--- a/src/features/agent/components/agent-message-details.tsx
+++ b/src/features/agent/components/agent-message-details.tsx
@@ -10,9 +10,16 @@ import {
type LucideIcon
} from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
-import { useState, type ReactNode } from "react";
+import { useId, useState, type ReactNode } from "react";
import { cn } from "@/shared/ui/cn";
import { Button } from "@/shared/ui/button";
+import {
+ StatusBadge,
+ StatusDot,
+ StatusIcon as SemanticStatusIcon,
+ type StatusActivity,
+ type StatusTone
+} from "@/shared/ui/status";
import {
agentEase,
agentLayoutTransition,
@@ -37,63 +44,64 @@ type QuestionStatus = AgentQuestionRequest["status"];
const STREAMING_PROGRESS_LIMIT = 3;
-const progressStatusMeta: Record = {
+type AgentStatusMeta = {
+ label: string;
+ tone: StatusTone;
+ activity?: StatusActivity;
+};
+
+const progressStatusMeta: Record = {
running: {
label: "进行中",
- className: "bg-blue-100 text-blue-700",
- dotClassName: "border-blue-500 bg-blue-100"
+ tone: "info",
+ activity: "live"
},
completed: {
- label: "完成",
- className: "bg-emerald-100 text-emerald-700",
- dotClassName: "border-emerald-500 bg-emerald-100"
+ label: "已完成",
+ tone: "success"
},
error: {
label: "失败",
- className: "bg-red-100 text-red-700",
- dotClassName: "border-red-500 bg-red-100"
+ tone: "danger"
}
};
-const todoStatusMeta: Record = {
+const todoStatusMeta: Record = {
completed: {
label: "完成",
- className: "bg-emerald-100 text-emerald-700",
- dotClassName: "border-emerald-500 bg-emerald-500"
+ tone: "success"
},
in_progress: {
label: "进行中",
- className: "bg-blue-100 text-blue-700",
- dotClassName: "border-blue-500 bg-blue-100"
+ tone: "info",
+ activity: "live"
},
pending: {
label: "待办",
- className: "bg-slate-100 text-slate-600",
- dotClassName: "border-slate-300 bg-white"
+ tone: "neutral"
},
cancelled: {
label: "取消",
- className: "bg-slate-200 text-slate-500",
- dotClassName: "border-slate-300 bg-slate-200"
+ tone: "neutral"
}
};
-const permissionStatusMeta: Record = {
- approved_always: { label: "已始终允许", className: "bg-emerald-100 text-emerald-700" },
- approved_once: { label: "已允许一次", className: "bg-emerald-100 text-emerald-700" },
- rejected: { label: "已拒绝", className: "bg-red-100 text-red-700" },
- aborted: { label: "已中止", className: "bg-slate-200 text-slate-500" },
- error: { label: "失败", className: "bg-red-100 text-red-700" },
- submitting: { label: "提交中", className: "bg-blue-100 text-blue-700" },
- pending: { label: "待确认", className: "bg-orange-100 text-orange-700" }
+const permissionStatusMeta: Record = {
+ approved_always: { label: "已始终允许", tone: "success" },
+ approved_once: { label: "已允许一次", tone: "success" },
+ rejected: { label: "已拒绝", tone: "danger" },
+ aborted: { label: "已中止", tone: "neutral" },
+ error: { label: "失败", tone: "danger" },
+ submitting: { label: "提交中", tone: "info", activity: "live" },
+ pending: { label: "待确认", tone: "warning" }
};
-const questionStatusMeta: Record = {
- answered: { label: "已回答", className: "bg-emerald-100 text-emerald-700" },
- rejected: { label: "已跳过", className: "bg-slate-200 text-slate-500" },
- error: { label: "失败", className: "bg-red-100 text-red-700" },
- submitting: { label: "提交中", className: "bg-blue-100 text-blue-700" },
- pending: { label: "待回答", className: "bg-blue-100 text-blue-700" }
+const questionStatusMeta: Record = {
+ answered: { label: "已回答", tone: "success" },
+ rejected: { label: "已跳过", tone: "neutral" },
+ error: { label: "失败", tone: "danger" },
+ submitting: { label: "提交中", tone: "info", activity: "live" },
+ pending: { label: "待回答", tone: "info" }
};
export function AgentMessageDetails({
@@ -198,6 +206,7 @@ function AnimatedDetailItem({ children }: { children: ReactNode }) {
function ProgressList({ progress, running }: { progress: AgentChatProgress[]; running: boolean }) {
const [expanded, setExpanded] = useState(false);
+ const listId = useId();
const compact = running && !expanded;
const visibleProgress = expanded
? progress
@@ -207,33 +216,47 @@ function ProgressList({ progress, running }: { progress: AgentChatProgress[]; ru
const listMode = expanded ? "expanded" : running ? "streaming" : "collapsed";
return (
-
-
setExpanded((current) => !current)}
- >
-
- 执行进度
-
-
- {expanded
- ? `全部 ${progress.length} 条`
- : running
- ? `最近 ${Math.min(progress.length, STREAMING_PROGRESS_LIMIT)} 条`
- : `共 ${progress.length} 条`}
+
+
+
+ 执行进度
+
+ {running ? (
+
+ ) : null}
+ {running ? (expanded ? "全部" : "最近") : "步骤"}
+
+ {expanded || !running
+ ? progress.length
+ : Math.min(progress.length, STREAMING_PROGRESS_LIMIT)}
+ /
+ {progress.length}
+
-
-
+ setExpanded((current) => !current)}
+ >
+
+
+
{visibleProgress.length ? (
-
{progressStatusMeta[item.status].label}
@@ -333,7 +338,7 @@ function ProgressList({ progress, running }: { progress: AgentChatProgress[]; ru
function TodoCard({ todoUpdate }: { todoUpdate: AgentTodoUpdate }) {
return (
-
+
{todoUpdate.todos.slice(0, 6).map((todo) => (
@@ -346,20 +351,24 @@ function TodoCard({ todoUpdate }: { todoUpdate: AgentTodoUpdate }) {
variants={agentPanelItemVariants}
transition={agentLayoutTransition}
>
-
- {todo.status === "completed" ? : null}
-
+ {todo.status === "completed" ? : null}
+
{todo.content}
-
+
{todoStatusMeta[todo.status].label}
-
+
))}
@@ -376,7 +385,7 @@ function PermissionList({
onReplyPermission?: (permission: AgentPermissionRequest, reply: AgentPermissionReply) => Promise | void;
}) {
return (
-
+
{permissions.slice(-4).map((permission) => (
@@ -424,18 +433,22 @@ function PermissionRequestCard({
{target}
{permission.error ? (
- {permission.error}
+ {permission.error}
) : null}
-
+
{permissionStatusMeta[permission.status].label}
-
+
{actionable ? (
reply("once")}
>
@@ -457,7 +470,7 @@ function PermissionRequestCard({
type="button"
size="sm"
variant="outline"
- className="h-7 rounded-md border-blue-200 bg-blue-50 px-2 text-xs text-blue-700 hover:bg-blue-100"
+ className="h-8 border-blue-200 bg-blue-50 px-2 text-xs text-blue-700 hover:bg-blue-100"
disabled={disabled}
onClick={() => reply("always")}
>
@@ -469,7 +482,7 @@ function PermissionRequestCard({
type="button"
size="sm"
variant="outline"
- className="h-7 rounded-md border-red-200 bg-red-50 px-2 text-xs text-red-700 hover:bg-red-100"
+ className="status-tone-danger h-8 border-[var(--status-border)] bg-[var(--status-soft)] px-2 text-xs text-[var(--status-foreground)] [@media(hover:hover)]:hover:brightness-95"
disabled={disabled}
onClick={() => reply("reject")}
>
@@ -547,9 +560,12 @@ function QuestionRequestCard({
{request.questions[0]?.header || "问题"}
-
+
{questionStatusMeta[request.status].label}
-
+
@@ -568,7 +584,7 @@ function QuestionRequestCard({
{request.error ? (
- {request.error}
+ {request.error}
) : null}
{request.answers?.length ? (
answer.join("、")).join(";")}>
@@ -580,7 +596,7 @@ function QuestionRequestCard({
{actionable ? (
@@ -601,7 +617,7 @@ function QuestionRequestCard({
type="button"
size="sm"
variant="outline"
- className="h-7 rounded-md border-slate-200 bg-white px-2 text-xs text-slate-600 hover:bg-slate-50"
+ className="h-8 border-slate-200 bg-white px-2 text-xs text-slate-600 hover:bg-slate-50"
disabled={submitting || !onRejectQuestion}
onClick={reject}
>
diff --git a/src/features/agent/components/agent-operational-brief.tsx b/src/features/agent/components/agent-operational-brief.tsx
index c56c559..b23d78f 100644
--- a/src/features/agent/components/agent-operational-brief.tsx
+++ b/src/features/agent/components/agent-operational-brief.tsx
@@ -1,15 +1,9 @@
-import {
- ClipboardCheck,
- Crosshair,
- MapPinned,
- Radio,
- Route
-} from "lucide-react";
+import { ClipboardCheck, Crosshair, MapPinned, Radio, Route } from "lucide-react";
import type { ReactNode } from "react";
-import { cn } from "@/shared/ui/cn";
-import type {
- AgentChatMessage
-} from "../types";
+import { Button } from "@/shared/ui/button";
+import { StatusDot, type StatusTone } from "@/shared/ui/status";
+import { countPendingAgentInputs } from "../message-state";
+import type { AgentChatMessage } from "../types";
export function AgentOperationalBrief({
messages,
@@ -26,52 +20,71 @@ export function AgentOperationalBrief({
return (
-
-
-
-
- {brief.task}
-
+
- } label="证据" value={brief.evidence} />
- } label="计划" value={brief.plan} />
- } label="确认" value={brief.confirmation} />
+ }
+ label="证据"
+ value={brief.evidence}
+ />
+ }
+ label="计划"
+ value={brief.plan}
+ />
+ }
+ label="确认"
+ value={brief.confirmation}
+ />
- onSubmitCommand(brief.primaryCommand)}
>
预览影响
-
-
+ onSubmitCommand(brief.secondaryCommand)}
>
检查证据
-
+
);
}
-function AgentBriefMetric({ icon, label, value }: { icon: ReactNode; label: string; value: string }) {
+function AgentBriefMetric({
+ icon,
+ label,
+ value
+}: {
+ icon: ReactNode;
+ label: string;
+ value: string;
+}) {
return (
-
-
+
+
{icon}
{label}
@@ -82,26 +95,31 @@ function AgentBriefMetric({ icon, label, value }: { icon: ReactNode; label: stri
);
}
-function createOperationalBrief(messages: AgentChatMessage[], statusLabel: string, streaming: boolean) {
- const lastUserMessage = [...messages].reverse().find((message) => message.role === "user" && message.content.trim());
+function createOperationalBrief(
+ messages: AgentChatMessage[],
+ statusLabel: string,
+ streaming: boolean
+) {
+ const lastUserMessage = [...messages]
+ .reverse()
+ .find((message) => message.role === "user" && message.content.trim());
const progressItems = messages.flatMap((message) => message.progress ?? []);
const todoItems = messages.flatMap((message) => message.todos?.todos ?? []);
- const permissionItems = messages.flatMap((message) => message.permissions ?? []);
- const questionItems = messages.flatMap((message) => message.questions ?? []);
const latestProgress = progressItems.at(-1);
const activeTodo =
[...todoItems].reverse().find((todo) => todo.status === "in_progress") ??
[...todoItems].reverse().find((todo) => todo.status === "pending");
- const pendingConfirmations =
- permissionItems.filter((permission) => permission.status === "pending" || permission.status === "error").length +
- questionItems.filter((question) => question.status === "pending" || question.status === "error").length;
+ const pendingConfirmations = countPendingAgentInputs(messages);
const completedSteps = progressItems.filter((item) => item.status === "completed").length;
const task = lastUserMessage?.content.trim() || "等待调度指令";
const evidence = latestProgress?.detail || latestProgress?.title || statusLabel;
- const plan = activeTodo?.content || (completedSteps > 0 ? `${completedSteps} 项已完成` : "待生成");
+ const plan =
+ activeTodo?.content || (completedSteps > 0 ? `${completedSteps} 项已完成` : "待生成");
const confirmation = pendingConfirmations > 0 ? `${pendingConfirmations} 项待确认` : "无需确认";
- const stateLabel = pendingConfirmations > 0 ? "等待人工确认" : streaming ? "Agent 分析中" : "分析完成";
- const statusDotClassName = pendingConfirmations > 0 ? "bg-orange-500" : streaming ? "bg-blue-500" : "bg-emerald-500";
+ const stateLabel =
+ pendingConfirmations > 0 ? "等待人工确认" : streaming ? "Agent 分析中" : "分析完成";
+ const statusTone: StatusTone =
+ pendingConfirmations > 0 ? "warning" : streaming ? "info" : "success";
const promptContext = lastUserMessage?.content.trim() || "当前供水管网运行态势";
return {
@@ -110,7 +128,7 @@ function createOperationalBrief(messages: AgentChatMessage[], statusLabel: strin
plan,
confirmation,
stateLabel,
- statusDotClassName,
+ statusTone,
primaryCommand: `请基于“${promptContext}”预览空间影响范围,并列出需要确认的操作。`,
secondaryCommand: `请检查“${promptContext}”的证据链,按数据来源、风险和不确定性汇总。`
};
diff --git a/src/features/agent/components/agent-ui-envelope-renderer.tsx b/src/features/agent/components/agent-ui-envelope-renderer.tsx
index be187f2..099c745 100644
--- a/src/features/agent/components/agent-ui-envelope-renderer.tsx
+++ b/src/features/agent/components/agent-ui-envelope-renderer.tsx
@@ -2,6 +2,7 @@ import { Activity, BarChart3, Database, History, LineChart, MonitorCog } from "l
import { AnimatePresence, MotionConfig, motion } from "motion/react";
import type { ReactNode } from "react";
import { cn } from "@/shared/ui/cn";
+import { StatusIcon } from "@/shared/ui/status";
import { normalizeSafeChartData, parseChartSpec, type SafeChartData, type SafeChartSeries, type SafeChartSpec } from "../chart-data";
import type { AgentUiResult } from "../types";
import type { UIEnvelope } from "../ui-envelope";
@@ -282,7 +283,7 @@ function TrustedPanelShell({
return (
- {icon}
+ {icon}
{title}
{children}
diff --git a/src/features/agent/index.ts b/src/features/agent/index.ts
index c8f619d..0e6c0be 100644
--- a/src/features/agent/index.ts
+++ b/src/features/agent/index.ts
@@ -1,5 +1,6 @@
export { AgentCollapsedRail } from "./components/agent-collapsed-rail";
export { AgentCommandPanel } from "./components/agent-command-panel";
+export { countPendingAgentInputs, hasPendingAgentInput } from "./message-state";
export type { AgentCommandPanelPresentation } from "./components/agent-command-panel";
export { AgentPersona } from "./components/agent-persona";
export { createAgentApiClient } from "./api/client";
@@ -56,6 +57,7 @@ export type {
AgentApprovalMode,
AgentChatMessage,
AgentChatProgress,
+ AgentConnectionStatus,
AgentInteractionToolRef,
AgentModelOption,
AgentPermissionReply,
diff --git a/src/features/agent/message-state.test.ts b/src/features/agent/message-state.test.ts
new file mode 100644
index 0000000..2f378de
--- /dev/null
+++ b/src/features/agent/message-state.test.ts
@@ -0,0 +1,27 @@
+import { describe, expect, it } from "vitest";
+import { countPendingAgentInputs, hasPendingAgentInput } from "./message-state";
+import type { AgentChatMessage } from "./types";
+
+describe("Agent message state", () => {
+ it("counts pending and failed operator inputs from one shared rule", () => {
+ const messages = [
+ {
+ id: "assistant-1",
+ role: "assistant",
+ content: "",
+ permissions: [
+ { status: "pending" },
+ { status: "approved_once" }
+ ],
+ questions: [
+ { status: "error" },
+ { status: "answered" }
+ ]
+ }
+ ] as AgentChatMessage[];
+
+ expect(countPendingAgentInputs(messages)).toBe(2);
+ expect(hasPendingAgentInput(messages)).toBe(true);
+ expect(hasPendingAgentInput([])).toBe(false);
+ });
+});
diff --git a/src/features/agent/message-state.ts b/src/features/agent/message-state.ts
new file mode 100644
index 0000000..0439ff2
--- /dev/null
+++ b/src/features/agent/message-state.ts
@@ -0,0 +1,19 @@
+import type { AgentChatMessage } from "./types";
+
+export function countPendingAgentInputs(messages: AgentChatMessage[]) {
+ return messages.reduce((total, message) => {
+ const pendingPermissions =
+ message.permissions?.filter(
+ (permission) => permission.status === "pending" || permission.status === "error"
+ ).length ?? 0;
+ const pendingQuestions =
+ message.questions?.filter(
+ (question) => question.status === "pending" || question.status === "error"
+ ).length ?? 0;
+ return total + pendingPermissions + pendingQuestions;
+ }, 0);
+}
+
+export function hasPendingAgentInput(messages: AgentChatMessage[]) {
+ return countPendingAgentInputs(messages) > 0;
+}
diff --git a/src/features/agent/types.ts b/src/features/agent/types.ts
index a6f12c7..40fa91f 100644
--- a/src/features/agent/types.ts
+++ b/src/features/agent/types.ts
@@ -14,6 +14,8 @@ export type AgentStreamRenderMessageState = {
export type AgentStreamRenderState = Record
;
+export type AgentConnectionStatus = "connecting" | "online" | "offline";
+
export type AgentChatProgress = {
id: string;
phase: string;
diff --git a/src/features/map/core/components/control-panel.tsx b/src/features/map/core/components/control-panel.tsx
index bda74fc..93025b0 100644
--- a/src/features/map/core/components/control-panel.tsx
+++ b/src/features/map/core/components/control-panel.tsx
@@ -158,7 +158,7 @@ export function MapActionRow({
disabled={disabled}
onClick={onClick}
className={cn(
- "flex min-h-12 w-full items-center gap-2.5 border px-2.5 py-2 text-left transition duration-150 active:translate-y-px active:scale-[0.99]",
+ "flex min-h-12 w-full items-center gap-2.5 border px-2.5 py-2 text-left",
MAP_COMPACT_RADIUS_CLASS_NAME,
isPrimary
? "border-blue-600 bg-blue-600 text-white shadow-lg shadow-blue-600/25 hover:bg-blue-700"
@@ -172,7 +172,7 @@ export function MapActionRow({
(isPrimary
? "cursor-not-allowed opacity-55 hover:border-blue-600 hover:bg-blue-600 hover:text-white"
: "cursor-not-allowed opacity-55 hover:border-white/70 hover:bg-white/95 hover:text-slate-700"),
- disabled && "active:translate-y-0 active:scale-100"
+ disabled && "shadow-none"
)}
>
diff --git a/src/features/map/core/components/layer-panel.tsx b/src/features/map/core/components/layer-panel.tsx
index 0d24510..b8accb2 100644
--- a/src/features/map/core/components/layer-panel.tsx
+++ b/src/features/map/core/components/layer-panel.tsx
@@ -61,7 +61,7 @@ function LayerVisibilitySection({ items, onToggleLayer }: LayerVisibilitySection
title={item.description}
onClick={() => onToggleLayer(item.id, !item.visible)}
className={cn(
- "relative flex min-h-[76px] w-full flex-col items-center justify-center gap-1.5 border px-1.5 py-2 text-center transition duration-150 active:translate-y-px active:scale-[0.98]",
+ "relative flex min-h-[76px] w-full flex-col items-center justify-center gap-1.5 border px-1.5 py-2 text-center",
MAP_COMPACT_RADIUS_CLASS_NAME,
item.visible
? "border-blue-200 bg-blue-50/90 text-blue-950 shadow-xs shadow-blue-950/5"
diff --git a/src/features/map/core/components/legend.tsx b/src/features/map/core/components/legend.tsx
index 08d08e6..e68cd54 100644
--- a/src/features/map/core/components/legend.tsx
+++ b/src/features/map/core/components/legend.tsx
@@ -1,5 +1,6 @@
import type { LucideIcon } from "lucide-react";
import { cn } from "@/shared/ui/cn";
+import { StatusDot } from "@/shared/ui/status";
import {
MAP_ICON_CELL_RADIUS_CLASS_NAME,
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
@@ -66,7 +67,7 @@ export function MapLegendList({ items, className = "", showStatusDot = false }:
{item.label}
{item.description ? {item.description} : null}
- {showStatusDot ? : null}
+ {showStatusDot ? : null}
);
})}
diff --git a/src/features/map/core/components/map-control-styles.ts b/src/features/map/core/components/map-control-styles.ts
index da06419..a5f7e2e 100644
--- a/src/features/map/core/components/map-control-styles.ts
+++ b/src/features/map/core/components/map-control-styles.ts
@@ -1,3 +1,5 @@
+import { buttonInteractionVariants } from "@/shared/ui/button-interaction";
+
export const MAP_MAJOR_PANEL_RADIUS_CLASS_NAME = "rounded-2xl";
export const MAP_CONTROL_RADIUS_CLASS_NAME = "rounded-xl";
@@ -24,4 +26,4 @@ export const MAP_READABLE_SURFACE_STRONG_CLASS_NAME =
"surface-control border";
export const MAP_CONTROL_HOVER_CLASS_NAME =
- "hover:bg-blue-100/70 hover:text-blue-700 hover:ring-1 hover:ring-blue-200/80 active:bg-blue-100";
+ buttonInteractionVariants({ tone: "neutral" });
diff --git a/src/features/map/core/components/measure-panel.tsx b/src/features/map/core/components/measure-panel.tsx
index a7a4813..2136e6b 100644
--- a/src/features/map/core/components/measure-panel.tsx
+++ b/src/features/map/core/components/measure-panel.tsx
@@ -117,7 +117,7 @@ export function MapMeasurePanel({
type="button"
onClick={() => onSelectUnit?.(unit.id)}
className={cn(
- "h-7 text-xs font-semibold transition",
+ "h-10 text-xs font-semibold",
MAP_COMPACT_RADIUS_CLASS_NAME,
unit.id === activeUnitId
? "bg-blue-600 text-white"
diff --git a/src/features/map/core/components/notice-actions.tsx b/src/features/map/core/components/notice-actions.tsx
index a238753..8969d06 100644
--- a/src/features/map/core/components/notice-actions.tsx
+++ b/src/features/map/core/components/notice-actions.tsx
@@ -44,7 +44,7 @@ export function showMapNotice(options: MapNoticeOptions) {
: undefined,
classNames: {
cancelButton: cn(
- "absolute right-2 top-2 z-10 grid h-7 w-7 place-items-center border border-transparent bg-transparent p-0 text-slate-400 transition hover:border-slate-200 hover:bg-slate-50 hover:text-slate-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25",
+ "absolute right-2 top-2 z-10 grid h-8 w-8 place-items-center border border-transparent bg-transparent p-0 text-slate-400 after:absolute after:-inset-1 after:content-[''] hover:border-slate-200 hover:bg-slate-50 hover:text-slate-700",
MAP_ICON_CELL_RADIUS_CLASS_NAME
)
},
diff --git a/src/features/map/core/components/notice-presentation.tsx b/src/features/map/core/components/notice-presentation.tsx
index 029a133..43fc1e5 100644
--- a/src/features/map/core/components/notice-presentation.tsx
+++ b/src/features/map/core/components/notice-presentation.tsx
@@ -1,6 +1,11 @@
import { AlertTriangle, CheckCircle2, Info, Loader2, X, XCircle } from "lucide-react";
import type { ReactNode } from "react";
import { cn } from "@/shared/ui/cn";
+import { Button } from "@/shared/ui/button";
+import {
+ StatusIcon as SemanticStatusIcon,
+ type StatusTone
+} from "@/shared/ui/status";
import {
MAP_COMPACT_RADIUS_CLASS_NAME,
MAP_ICON_CELL_RADIUS_CLASS_NAME,
@@ -8,37 +13,13 @@ import {
} from "./map-control-styles";
import type { MapNoticeOptions, MapNoticeTone } from "./notice-types";
-const toneClassNames: Record = {
- info: "glass-transient text-slate-900",
- success: "glass-transient text-slate-900",
- warning: "glass-transient text-slate-900",
- error: "glass-transient text-slate-900",
- loading: "glass-transient text-slate-900"
-};
-
-const iconClassNames: Record = {
- info: "text-blue-600",
- success: "text-green-600",
- warning: "text-orange-500",
- error: "text-red-500",
- loading: "text-blue-600"
-};
-
-const noticeAccentClassNames: Record = {
- info: "bg-blue-600",
- success: "bg-green-500",
- warning: "bg-orange-500",
- error: "bg-red-500",
- loading: "bg-blue-600"
-};
-
-const noticeIconSurfaceClassNames: Record = {
- info: "border-blue-100 bg-blue-50",
- success: "border-green-100 bg-green-50",
- warning: "border-orange-100 bg-orange-50",
- error: "border-red-100 bg-red-50",
- loading: "border-blue-100 bg-blue-50"
-};
+const noticeTones = {
+ info: "info",
+ success: "success",
+ warning: "warning",
+ error: "danger",
+ loading: "info"
+} satisfies Record;
export function MapNoticeContent({
title,
@@ -79,15 +60,10 @@ function MapNoticeFrame({ tone, children }: { tone: MapNoticeTone; children: Rea
-
{children}
);
@@ -101,20 +77,14 @@ function MapNoticeIcon({
icon: ReturnType;
}) {
return (
-
-
-
+
+
);
}
@@ -142,16 +112,15 @@ function MapNoticeBody({
function MapNoticeAction({ label, onClick }: { label: string; onClick: () => void }) {
return (
-
{label}
-
+
);
}
diff --git a/src/features/map/core/components/notice.tsx b/src/features/map/core/components/notice.tsx
index 12f05f4..e392c6a 100644
--- a/src/features/map/core/components/notice.tsx
+++ b/src/features/map/core/components/notice.tsx
@@ -17,7 +17,7 @@ export function MapToaster() {
classNames: {
toast: "group pointer-events-auto",
closeButton:
- "absolute right-2 top-2 grid h-6 w-6 place-items-center rounded-lg border border-transparent text-slate-400 transition hover:bg-white/70 hover:text-slate-700"
+ "absolute right-2 top-2 grid h-8 w-8 place-items-center rounded-lg border border-transparent text-slate-400 after:absolute after:-inset-1 after:content-[''] hover:bg-white/70 hover:text-slate-700"
}
}}
/>
diff --git a/src/features/map/core/components/toolbar.tsx b/src/features/map/core/components/toolbar.tsx
index debd581..fab8323 100644
--- a/src/features/map/core/components/toolbar.tsx
+++ b/src/features/map/core/components/toolbar.tsx
@@ -44,10 +44,10 @@ export function MapToolbar({