chore: 恢复旧布局基线(迁移改动丢失后重建,apps/packages 布局暂存)
This commit is contained in:
1 parent
a9510235bb
commit
456fa6954b
203 files changed
+18787
-48
No files matched your search
@@ -0,0 +1,229 @@
|
||||
import { useEffect } from "react";
|
||||
import { useWorkspaceStore } from "./store";
|
||||
import type { ConfirmRequest } from "./store";
|
||||
import type { NotifyKind, NotifyPayload } from "./notify";
|
||||
import { OpsModal } from "./OpsModal";
|
||||
import { settleInputRequest, useInputRequest } from "./dialogs";
|
||||
|
||||
// C6:items 字段由 WS-C 在 store.ts 正式补充,此处本地类型扩展先行渲染
|
||||
type ConfirmRequestWithItems = ConfirmRequest & { items?: string[] };
|
||||
|
||||
/**
|
||||
* 分级语义色:统一走 --ops-* 令牌(修 V2,去除 slate 硬编码),亮暗主题随行
|
||||
* error→danger / warning→warning / info→accent / success→success
|
||||
*/
|
||||
const KIND_META: Record<NotifyKind, { color: string; label: string }> = {
|
||||
error: { color: "var(--ops-danger)", label: "错误" },
|
||||
warning: { color: "var(--ops-warning)", label: "提示" },
|
||||
info: { color: "var(--ops-accent)", label: "信息" },
|
||||
success: { color: "var(--ops-success)", label: "成功" },
|
||||
};
|
||||
|
||||
function KindIcon({ kind }: { kind: NotifyKind }) {
|
||||
const meta = KIND_META[kind];
|
||||
const glyph =
|
||||
kind === "error" ? "!" : kind === "warning" ? "!" : kind === "success" ? "✓" : "i";
|
||||
return (
|
||||
<div
|
||||
className="grid h-9 w-9 shrink-0 place-items-center rounded-full text-sm font-bold"
|
||||
style={{
|
||||
background: `color-mix(in srgb, ${meta.color} 15%, transparent)`,
|
||||
color: meta.color,
|
||||
}}
|
||||
>
|
||||
{glyph}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** 通知模态:走 OpsModal info 变体(Esc/遮罩关闭、焦点管理由 OpsModal 负责) */
|
||||
function ModalCard({ n, onClose }: { n: NotifyPayload; onClose: () => void }) {
|
||||
const meta = KIND_META[n.kind];
|
||||
return (
|
||||
<OpsModal
|
||||
variant="info"
|
||||
title={
|
||||
<span className="flex items-center gap-2.5">
|
||||
<KindIcon kind={n.kind} />
|
||||
<span>
|
||||
<span
|
||||
className="block font-semibold uppercase"
|
||||
style={{ fontSize: "var(--ops-text-xs)", letterSpacing: "0.05em", color: meta.color }}
|
||||
>
|
||||
{meta.label}
|
||||
</span>
|
||||
<span className="block" style={{ fontSize: "var(--ops-text-lg)" }}>
|
||||
{n.title}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
onOk={onClose}
|
||||
>
|
||||
<p className="m-0">{n.message}</p>
|
||||
|
||||
{n.tips && n.tips.length > 0 && (
|
||||
<ul
|
||||
className="m-0 mt-2 space-y-1.5 rounded border px-3 py-2.5"
|
||||
style={{
|
||||
fontSize: "var(--ops-text-sm)",
|
||||
borderColor: "var(--ops-border-soft)",
|
||||
background: "var(--ops-surface-2)",
|
||||
color: "var(--ops-muted)",
|
||||
listStyle: "none",
|
||||
}}
|
||||
>
|
||||
{n.tips.map((t) => (
|
||||
<li key={t} className="flex gap-2">
|
||||
<span className="mt-0.5" style={{ color: "var(--ops-accent)" }}>
|
||||
•
|
||||
</span>
|
||||
<span>{t}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{n.detail && (
|
||||
<details
|
||||
className="group mt-2 rounded border"
|
||||
style={{ borderColor: "var(--ops-border-soft)", background: "var(--ops-editor-bg)" }}
|
||||
>
|
||||
<summary
|
||||
className="cursor-pointer select-none px-3 py-2 hover:underline"
|
||||
style={{ fontSize: "var(--ops-text-sm)", color: "var(--ops-muted)" }}
|
||||
>
|
||||
技术详情
|
||||
</summary>
|
||||
<pre
|
||||
className="max-h-40 overflow-auto border-t px-3 py-2 font-mono leading-5 whitespace-pre-wrap break-all"
|
||||
style={{
|
||||
fontSize: "var(--ops-text-xs)",
|
||||
borderColor: "var(--ops-border-soft)",
|
||||
color: "var(--ops-muted)",
|
||||
}}
|
||||
>
|
||||
{n.detail}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</OpsModal>
|
||||
);
|
||||
}
|
||||
|
||||
/** Toast:保留分级图标 + 自动关闭,样式走令牌 */
|
||||
function ToastCard({ n, onClose }: { n: NotifyPayload; onClose: () => void }) {
|
||||
const meta = KIND_META[n.kind];
|
||||
useEffect(() => {
|
||||
const ms = n.durationMs ?? 3200;
|
||||
const t = window.setTimeout(onClose, ms);
|
||||
return () => window.clearTimeout(t);
|
||||
}, [n.durationMs, onClose]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="ops-toast"
|
||||
style={{ borderColor: `color-mix(in srgb, ${meta.color} 45%, var(--ops-border))` }}
|
||||
role="status"
|
||||
>
|
||||
<KindIcon kind={n.kind} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="font-semibold" style={{ fontSize: "var(--ops-text-md)", color: "var(--ops-text)" }}>
|
||||
{n.title}
|
||||
</div>
|
||||
<div className="mt-0.5 leading-relaxed" style={{ fontSize: "var(--ops-text-sm)", color: "var(--ops-muted)" }}>
|
||||
{n.message}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="ops-btn-ghost"
|
||||
style={{ height: "auto", padding: "2px 6px" }}
|
||||
aria-label="关闭"
|
||||
onClick={onClose}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** 危险/普通确认框:走 OpsModal confirm 变体;items 语句清单前 3 条 + 折叠其余(修 C2) */
|
||||
function ConfirmCard() {
|
||||
const confirm = useWorkspaceStore((s) => s.confirm) as ConfirmRequestWithItems | null;
|
||||
const resolveConfirm = useWorkspaceStore((s) => s.resolveConfirm);
|
||||
if (!confirm) return null;
|
||||
const items = confirm.items;
|
||||
return (
|
||||
<OpsModal
|
||||
variant="confirm"
|
||||
title={confirm.title}
|
||||
danger={confirm.danger}
|
||||
okLabel={confirm.okLabel || "确定"}
|
||||
zIndex="var(--ops-z-confirm)"
|
||||
onOk={() => resolveConfirm(true)}
|
||||
onCancel={() => resolveConfirm(false)}
|
||||
>
|
||||
<p className="m-0">{confirm.message}</p>
|
||||
{items && items.length > 0 && (
|
||||
<div className="ops-modal-items">
|
||||
{items.slice(0, 3).map((s, i) => (
|
||||
<div key={i}>{s}</div>
|
||||
))}
|
||||
{items.length > 3 && (
|
||||
<details className="mt-1">
|
||||
<summary className="cursor-pointer select-none" style={{ color: "var(--ops-accent)" }}>
|
||||
还有 {items.length - 3} 条,展开查看
|
||||
</summary>
|
||||
{items.slice(3).map((s, i) => (
|
||||
<div key={i + 3} className="mt-1">
|
||||
{s}
|
||||
</div>
|
||||
))}
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</OpsModal>
|
||||
);
|
||||
}
|
||||
|
||||
/** askInput(C6)渲染宿主:队列中的输入请求逐个以 OpsModal input 变体展示 */
|
||||
function DialogHost() {
|
||||
const req = useInputRequest();
|
||||
if (!req) return null;
|
||||
return (
|
||||
<OpsModal
|
||||
key={req.id}
|
||||
variant="input"
|
||||
title={req.title}
|
||||
label={req.label}
|
||||
defaultValue={req.defaultValue}
|
||||
placeholder={req.placeholder}
|
||||
onOk={(v) => settleInputRequest(v ?? null)}
|
||||
onCancel={() => settleInputRequest(null)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function NotifyHost() {
|
||||
const modal = useWorkspaceStore((s) => s.notifyModal);
|
||||
const toasts = useWorkspaceStore((s) => s.notifyToasts);
|
||||
const dismissNotify = useWorkspaceStore((s) => s.dismissNotify);
|
||||
|
||||
return (
|
||||
<>
|
||||
{modal && <ModalCard n={modal} onClose={() => dismissNotify(modal.id)} />}
|
||||
<ConfirmCard />
|
||||
<DialogHost />
|
||||
<div
|
||||
className="pointer-events-none fixed bottom-4 right-4 flex flex-col-reverse gap-2"
|
||||
style={{ zIndex: "var(--ops-z-toast)" }}
|
||||
>
|
||||
{toasts.map((n) => (
|
||||
<ToastCard key={n.id} n={n} onClose={() => dismissNotify(n.id)} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user