Update z-index from 50 to 60 for the dialog overlay and dropdown to ensure they appear above other UI components.
238 lines
8.2 KiB
TypeScript
238 lines
8.2 KiB
TypeScript
import {
|
|
Component,
|
|
createSignal,
|
|
For,
|
|
Show,
|
|
onCleanup,
|
|
onMount,
|
|
} from "solid-js";
|
|
import { docEntries, type DocEntry } from "./doc-data";
|
|
import { journalDocEntries, type JournalDocEntry } from "./journal-doc-data";
|
|
|
|
type DocSet = "directives" | "journal";
|
|
type AnyEntry = DocEntry | JournalDocEntry;
|
|
|
|
export interface DocDialogProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const DocDialog: Component<DocDialogProps> = (props) => {
|
|
const [docSet, setDocSet] = createSignal<DocSet>("directives");
|
|
const [selectedTag, setSelectedTag] = createSignal(docEntries[0]?.tag ?? "");
|
|
const [dropdownOpen, setDropdownOpen] = createSignal(false);
|
|
let dropdownRef!: HTMLDivElement;
|
|
|
|
const currentEntries = () =>
|
|
docSet() === "directives"
|
|
? (docEntries as AnyEntry[])
|
|
: (journalDocEntries as AnyEntry[]);
|
|
|
|
const selectedEntry = () =>
|
|
currentEntries().find((e) => e.tag === selectedTag()) ??
|
|
currentEntries()[0];
|
|
|
|
const docSetLabel = () =>
|
|
docSet() === "directives" ? "指令组件文档" : "Journal 服务器文档";
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") props.onClose();
|
|
};
|
|
|
|
const switchDocSet = (set: DocSet) => {
|
|
setDocSet(set);
|
|
setDropdownOpen(false);
|
|
const entries = set === "directives" ? docEntries : journalDocEntries;
|
|
setSelectedTag(entries[0]?.tag ?? "");
|
|
};
|
|
|
|
onMount(() => {
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
// Close dropdown on outside click
|
|
document.addEventListener("click", (e) => {
|
|
if (dropdownRef && !dropdownRef.contains(e.target as Node)) {
|
|
setDropdownOpen(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
onCleanup(() => {
|
|
document.removeEventListener("keydown", handleKeyDown);
|
|
});
|
|
|
|
return (
|
|
<Show when={props.isOpen}>
|
|
<div
|
|
class="fixed inset-0 bg-black/50 z-60 flex items-center justify-center p-4"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) props.onClose();
|
|
}}
|
|
>
|
|
<div class="bg-white rounded-lg shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col overflow-hidden">
|
|
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 shrink-0">
|
|
<div ref={dropdownRef} class="relative">
|
|
<button
|
|
onClick={() => setDropdownOpen((v) => !v)}
|
|
class="text-lg font-bold text-gray-900 hover:text-blue-600 flex items-center gap-1"
|
|
>
|
|
{docSetLabel()}
|
|
<span class="text-gray-400 text-sm">▾</span>
|
|
</button>
|
|
<Show when={dropdownOpen()}>
|
|
<div class="absolute top-full left-0 mt-1 w-52 bg-white border border-gray-200 rounded shadow-lg z-60 py-1">
|
|
<button
|
|
onClick={() => switchDocSet("directives")}
|
|
class={`w-full text-left px-3 py-2 text-sm hover:bg-gray-100 flex items-center gap-2 ${
|
|
docSet() === "directives"
|
|
? "bg-blue-50 text-blue-700"
|
|
: "text-gray-700"
|
|
}`}
|
|
>
|
|
<span>📖</span>
|
|
<span>指令组件文档</span>
|
|
<Show when={docSet() === "directives"}>
|
|
<span class="ml-auto text-blue-500">✓</span>
|
|
</Show>
|
|
</button>
|
|
<button
|
|
onClick={() => switchDocSet("journal")}
|
|
class={`w-full text-left px-3 py-2 text-sm hover:bg-gray-100 flex items-center gap-2 ${
|
|
docSet() === "journal"
|
|
? "bg-blue-50 text-blue-700"
|
|
: "text-gray-700"
|
|
}`}
|
|
>
|
|
<span>📋</span>
|
|
<span>Journal 服务器文档</span>
|
|
<Show when={docSet() === "journal"}>
|
|
<span class="ml-auto text-blue-500">✓</span>
|
|
</Show>
|
|
</button>
|
|
</div>
|
|
</Show>
|
|
</div>
|
|
<button
|
|
onClick={props.onClose}
|
|
class="text-gray-400 hover:text-gray-600 text-xl leading-none p-1"
|
|
title="关闭"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex flex-1 min-h-0">
|
|
<nav class="w-48 shrink-0 border-r border-gray-200 overflow-y-auto p-3 bg-gray-50">
|
|
<For each={currentEntries()}>
|
|
{(entry) => (
|
|
<button
|
|
onClick={() => setSelectedTag(entry.tag)}
|
|
class={`w-full text-left px-3 py-2 rounded mb-1 text-sm transition-colors ${
|
|
selectedTag() === entry.tag
|
|
? "bg-blue-100 text-blue-800 font-medium"
|
|
: "text-gray-700 hover:bg-gray-100"
|
|
}`}
|
|
>
|
|
<span class="mr-2">{entry.icon}</span>
|
|
<Show
|
|
when={docSet() === "directives"}
|
|
fallback={<span class="text-xs">{entry.title}</span>}
|
|
>
|
|
<span class="font-mono text-xs">{`:${entry.tag}`}</span>
|
|
</Show>
|
|
</button>
|
|
)}
|
|
</For>
|
|
</nav>
|
|
|
|
<div class="flex-1 overflow-y-auto p-6">
|
|
<Show when={selectedEntry()} keyed>
|
|
{(entry) => <DocContent entry={entry} />}
|
|
</Show>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Show>
|
|
);
|
|
};
|
|
|
|
/** Single entry documentation content */
|
|
const DocContent: Component<{ entry: AnyEntry }> = (props) => {
|
|
const e = props.entry;
|
|
|
|
return (
|
|
<div class="max-w-none">
|
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">
|
|
{e.icon} {e.title}
|
|
<code class="ml-3 text-base font-mono bg-gray-100 px-2 py-0.5 rounded text-blue-700">
|
|
:{e.tag}
|
|
</code>
|
|
</h2>
|
|
|
|
<p class="text-gray-600 mb-6">{e.description}</p>
|
|
|
|
<div class="mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-500 uppercase mb-2">
|
|
基本语法
|
|
</h3>
|
|
<pre class="bg-gray-900 text-gray-100 px-4 py-3 rounded-lg text-sm overflow-x-auto">
|
|
<code>{e.syntax}</code>
|
|
</pre>
|
|
</div>
|
|
|
|
<Show when={e.props.length > 0}>
|
|
<div class="mb-6">
|
|
<h3 class="text-sm font-semibold text-gray-500 uppercase mb-2">
|
|
属性
|
|
</h3>
|
|
<table class="w-full text-sm border-collapse">
|
|
<thead>
|
|
<tr class="border-b border-gray-200">
|
|
<th class="text-left py-2 pr-4 font-medium text-gray-600">
|
|
属性
|
|
</th>
|
|
<th class="text-left py-2 pr-4 font-medium text-gray-600">
|
|
类型
|
|
</th>
|
|
<th class="text-left py-2 pr-4 font-medium text-gray-600">
|
|
默认值
|
|
</th>
|
|
<th class="text-left py-2 font-medium text-gray-600">说明</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<For each={e.props}>
|
|
{(prop) => (
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 pr-4">
|
|
<code class="bg-gray-100 px-1.5 py-0.5 rounded text-xs text-pink-700">
|
|
{prop.name}
|
|
</code>
|
|
</td>
|
|
<td class="py-2 pr-4">
|
|
<code class="text-xs text-gray-600">{prop.type}</code>
|
|
</td>
|
|
<td class="py-2 pr-4 text-xs text-gray-400">
|
|
{prop.default ?? "—"}
|
|
</td>
|
|
<td class="py-2 text-sm text-gray-600">{prop.desc}</td>
|
|
</tr>
|
|
)}
|
|
</For>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</Show>
|
|
|
|
<div class="prose-sm max-w-none">
|
|
<h3 class="text-sm font-semibold text-gray-500 uppercase mb-2">示例</h3>
|
|
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4 text-sm leading-relaxed whitespace-pre-wrap font-mono text-gray-800">
|
|
{e.body}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DocDialog;
|