155 lines
5.6 KiB
TypeScript
155 lines
5.6 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import { getDebugFeatureCategories } from "@/config/debug-features";
|
|
import { useDevDebug } from "@/app/context/DevDebugContext";
|
|
|
|
function ToggleSwitch({
|
|
checked,
|
|
onChange,
|
|
label,
|
|
}: {
|
|
checked: boolean;
|
|
onChange: (checked: boolean) => void;
|
|
label: string;
|
|
}) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
role="switch"
|
|
aria-checked={checked}
|
|
aria-label={label}
|
|
onClick={() => onChange(!checked)}
|
|
className={`relative h-6 w-11 shrink-0 rounded-full transition-colors ${
|
|
checked ? "bg-[#ff4d4f]" : "bg-gray-300 dark:bg-gray-600"
|
|
}`}
|
|
>
|
|
<span
|
|
className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform ${
|
|
checked ? "translate-x-5" : "translate-x-0"
|
|
}`}
|
|
/>
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export default function DevDebugFab() {
|
|
const {
|
|
isDev,
|
|
panelOpen,
|
|
setPanelOpen,
|
|
features,
|
|
isFeatureVisible,
|
|
setFeatureVisible,
|
|
resetAll,
|
|
overrides,
|
|
} = useDevDebug();
|
|
|
|
const categories = useMemo(() => getDebugFeatureCategories(), []);
|
|
const overrideCount = Object.keys(overrides).length;
|
|
|
|
if (!isDev) return null;
|
|
|
|
return (
|
|
<>
|
|
{panelOpen && (
|
|
<button
|
|
type="button"
|
|
aria-label="关闭调试面板"
|
|
className="fixed inset-0 z-[9998] bg-black/20 backdrop-blur-[1px]"
|
|
onClick={() => setPanelOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
<div className="fixed bottom-5 right-5 z-[9999] flex flex-col items-end gap-3">
|
|
{panelOpen && (
|
|
<div className="w-[min(92vw,360px)] rounded-2xl border border-gray-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-900">
|
|
<div className="border-b border-gray-100 px-4 py-3 dark:border-gray-800">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<h2 className="text-sm font-bold text-gray-900 dark:text-gray-100">调试模式</h2>
|
|
<p className="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
|
开发环境 · 切换暂时隐藏的功能
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setPanelOpen(false)}
|
|
className="rounded-lg p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-200"
|
|
aria-label="关闭"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="max-h-[min(60vh,420px)] overflow-y-auto p-4 space-y-5">
|
|
{categories.map((category) => (
|
|
<section key={category}>
|
|
<h3 className="mb-2 text-[11px] font-bold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
|
{category}
|
|
</h3>
|
|
<ul className="space-y-2">
|
|
{features
|
|
.filter((feature) => feature.category === category)
|
|
.map((feature) => {
|
|
const visible = isFeatureVisible(feature.id);
|
|
return (
|
|
<li
|
|
key={feature.id}
|
|
className="flex items-center justify-between gap-3 rounded-xl border border-gray-100 bg-gray-50 px-3 py-2.5 dark:border-gray-800 dark:bg-gray-800/60"
|
|
>
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{feature.label}
|
|
</p>
|
|
<p className="mt-0.5 text-[11px] text-gray-500 dark:text-gray-400">
|
|
{feature.description}
|
|
</p>
|
|
</div>
|
|
<ToggleSwitch
|
|
checked={visible}
|
|
onChange={(next) => setFeatureVisible(feature.id, next)}
|
|
label={`${feature.label} ${visible ? "显示" : "隐藏"}`}
|
|
/>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</section>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between border-t border-gray-100 px-4 py-3 dark:border-gray-800">
|
|
<span className="text-[11px] text-gray-400 dark:text-gray-500">
|
|
{overrideCount > 0 ? `已覆盖 ${overrideCount} 项` : "使用默认配置"}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={resetAll}
|
|
className="rounded-lg px-3 py-1.5 text-xs font-medium text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
|
|
>
|
|
重置默认
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setPanelOpen(!panelOpen)}
|
|
className={`flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-all ${
|
|
panelOpen
|
|
? "bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900"
|
|
: "bg-[#ff4d4f] text-white hover:bg-[#ff7a45]"
|
|
}`}
|
|
aria-label={panelOpen ? "关闭调试模式" : "打开调试模式"}
|
|
title="调试模式"
|
|
>
|
|
<span className="text-lg">{panelOpen ? "✕" : "🛠️"}</span>
|
|
</button>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|