ttrpg-tools/src/components/DocDialog.tsx

171 lines
5.4 KiB
TypeScript

import {
Component,
createSignal,
For,
Show,
onCleanup,
onMount,
} from "solid-js";
import { docEntries, type DocEntry } from "./doc-data";
export interface DocDialogProps {
isOpen: boolean;
onClose: () => void;
}
const DocDialog: Component<DocDialogProps> = (props) => {
const [selectedTag, setSelectedTag] = createSignal(docEntries[0]?.tag ?? "");
const selectedEntry = () =>
docEntries.find((e) => e.tag === selectedTag()) ?? docEntries[0];
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") props.onClose();
};
onMount(() => {
document.addEventListener("keydown", handleKeyDown);
});
onCleanup(() => {
document.removeEventListener("keydown", handleKeyDown);
});
return (
<Show when={props.isOpen}>
<div
class="fixed inset-0 bg-black/50 z-50 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">
<h2 class="text-lg font-bold text-gray-900"></h2>
<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={docEntries}>
{(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>
<span class="font-mono text-xs">{`:${entry.tag}`}</span>
</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: DocEntry }> = (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="mb-6">
<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.examples}
</div>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-500 uppercase mb-2">
使
</h3>
<p class="text-sm text-gray-600">{e.usage}</p>
</div>
</div>
);
};
export default DocDialog;