From 70916e21dd00adfd9c1e21890cb8a4e1ff47264f Mon Sep 17 00:00:00 2001 From: Huarch Date: Wed, 22 Jul 2026 10:38:53 +0800 Subject: [PATCH] fix: preserve Fast Refresh component boundaries --- .../agent/components/agent-command-panel.tsx | 2 +- .../map/core/components/notice-actions.tsx | 60 +++++ .../core/components/notice-presentation.tsx | 154 ++++++++++++ .../map/core/components/notice-types.ts | 14 ++ src/features/map/core/components/notice.tsx | 224 +----------------- .../workbench/components/feature-popover.tsx | 2 +- .../scheduled-condition-detail-panel.tsx | 2 +- .../components/scheduled-condition-feed.tsx | 2 +- .../workbench/hooks/use-workbench-agent.ts | 2 +- src/features/workbench/map-workbench-page.tsx | 2 +- src/shared/ai-elements/code-block.tsx | 2 +- src/shared/ai-elements/conversation.tsx | 2 +- src/shared/ai-elements/prompt-input.tsx | 10 +- src/shared/ui/badge.tsx | 2 +- src/shared/ui/button-group.tsx | 1 - src/shared/ui/button.tsx | 2 +- 16 files changed, 248 insertions(+), 235 deletions(-) create mode 100644 src/features/map/core/components/notice-actions.tsx create mode 100644 src/features/map/core/components/notice-presentation.tsx create mode 100644 src/features/map/core/components/notice-types.ts diff --git a/src/features/agent/components/agent-command-panel.tsx b/src/features/agent/components/agent-command-panel.tsx index 4a65d9d..535571b 100644 --- a/src/features/agent/components/agent-command-panel.tsx +++ b/src/features/agent/components/agent-command-panel.tsx @@ -23,7 +23,7 @@ import { AnimatePresence, MotionConfig, motion, useReducedMotion } from "motion/ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { useStickToBottomContext } from "use-stick-to-bottom"; -import { showMapNotice } from "@/features/map/core/components/notice"; +import { showMapNotice } from "@/features/map/core/components/notice-actions"; import { Agent, AgentContent } from "@/shared/ai-elements/agent"; import { Conversation, diff --git a/src/features/map/core/components/notice-actions.tsx b/src/features/map/core/components/notice-actions.tsx new file mode 100644 index 0000000..548302e --- /dev/null +++ b/src/features/map/core/components/notice-actions.tsx @@ -0,0 +1,60 @@ +"use client"; + +import type { ComponentProps } from "react"; +import { toast, Toaster } from "sonner"; +import { cn } from "@/lib/utils"; +import { MAP_ICON_CELL_RADIUS_CLASS_NAME } from "./map-control-styles"; +import { MapNoticeCloseIcon, MapNoticeContent } from "./notice-presentation"; +import type { MapNoticeOptions, MapNoticePosition } from "./notice-types"; + +type SonnerPosition = NonNullable["position"]>; + +const DEFAULT_POSITION: MapNoticePosition = "top-center"; + +const positionClassNames: Record = { + "top-center": "left-1/2! right-auto! top-[84px] [translate:-50%_0]", + "top-right": "top-[84px] right-[72px]", + "bottom-left": "bottom-12 left-4", + "bottom-right": "bottom-12 right-4" +}; + +const sonnerPositions: Record = { + "top-center": "top-center", + "top-right": "top-right", + "bottom-left": "bottom-left", + "bottom-right": "bottom-right" +}; + +export function showMapNotice(options: MapNoticeOptions) { + const tone = options.tone ?? "info"; + const position = options.position ?? DEFAULT_POSITION; + const duration = options.duration ?? (tone === "info" || tone === "success" ? 3200 : Infinity); + const dismissible = options.dismissible ?? tone !== "loading"; + + return toast.custom( + () => , + { + id: options.id, + duration, + position: sonnerPositions[position], + dismissible, + cancel: dismissible + ? { + label: , + onClick: (event) => event.stopPropagation() + } + : 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", + MAP_ICON_CELL_RADIUS_CLASS_NAME + ) + }, + className: cn("fixed! z-[60]! w-[min(420px,calc(100vw-24px))]!", positionClassNames[position]) + } + ); +} + +export function dismissMapNotice(id?: string | number) { + toast.dismiss(id); +} diff --git a/src/features/map/core/components/notice-presentation.tsx b/src/features/map/core/components/notice-presentation.tsx new file mode 100644 index 0000000..97a2b7c --- /dev/null +++ b/src/features/map/core/components/notice-presentation.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { AlertTriangle, CheckCircle2, Info, Loader2, X, XCircle } from "lucide-react"; +import type { ReactNode } from "react"; +import { cn } from "@/lib/utils"; +import { + MAP_COMPACT_RADIUS_CLASS_NAME, + MAP_ICON_CELL_RADIUS_CLASS_NAME, + MAP_READABLE_RADIUS_CLASS_NAME +} from "./map-control-styles"; +import type { MapNoticeOptions, MapNoticeTone } from "./notice-types"; + +const toneClassNames: Record = { + info: "acrylic-panel text-slate-900", + success: "acrylic-panel text-slate-900", + warning: "acrylic-panel text-slate-900", + error: "acrylic-panel text-slate-900", + loading: "acrylic-panel 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" +}; + +export function MapNoticeContent({ + title, + message, + tone = "info", + actionLabel, + onAction +}: MapNoticeOptions) { + const Icon = getNoticeIcon(tone); + + return ( + + + : null} + /> + + ); +} + +export function MapNoticeCloseIcon() { + return ( + <> + 关闭通知 +