Files
star-erp/resources/js/Pages/System/Manual/Index.tsx
sky121113 ac149533f0
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 1m3s
fix: 簡化 prose 類別以解決 Tailwind v4 排版失效問題
2026-02-13 15:57:56 +08:00

153 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Head, Link } from "@inertiajs/react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import {
BookOpen,
Search,
Menu,
FileText,
HelpCircle
} from "lucide-react";
import { useState } from "react";
import { ScrollArea } from "@/Components/ui/scroll-area";
import { Input } from "@/Components/ui/input";
import { cn } from "@/lib/utils";
interface Page {
title: string;
slug: string;
}
interface Section {
title: string;
pages: Page[];
}
interface Props {
toc: Section[];
currentSlug: string;
content: string;
}
export default function ManualIndex({ toc, currentSlug, content }: Props) {
const [searchQuery, setSearchQuery] = useState("");
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
// Filter TOC based on search
const filteredToc = toc.map(section => ({
...section,
pages: section.pages.filter(page =>
page.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
section.title.toLowerCase().includes(searchQuery.toLowerCase())
)
})).filter(section => section.pages.length > 0);
return (
<AuthenticatedLayout breadcrumbs={[
{ label: "系統管理", href: "#" },
{ label: "操作手冊", href: route('system.manual.index'), isPage: true }
]}>
<Head title="操作手冊" />
<div className="flex h-[calc(100vh-140px)] bg-slate-50/50 rounded-xl border border-slate-200 shadow-sm overflow-hidden m-2 md:m-6">
{/* Sidebar */}
<aside className={cn(
"w-72 border-r border-slate-200 bg-white flex flex-col transition-all duration-300",
!isSidebarOpen && "w-0 opacity-0 overflow-hidden"
)}>
<div className="p-5 border-b border-slate-100 bg-slate-50/30">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
<Input
placeholder="搜尋功能手冊..."
className="pl-10 h-10 bg-white border-slate-200 focus:ring-2 focus:ring-primary-lighter transition-all placeholder:text-slate-400"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<ScrollArea className="flex-1">
<div className="p-4 space-y-8">
{filteredToc.map((section, idx) => (
<div key={idx} className="space-y-2">
<h3 className="px-3 text-[11px] font-bold text-slate-400 uppercase tracking-[0.1em]">
{section.title}
</h3>
<div className="space-y-1">
{section.pages.map((page) => (
<Link
key={page.slug}
href={route('system.manual.index', { slug: page.slug })}
className={cn(
"flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-all",
currentSlug === page.slug
? "bg-primary-main text-white shadow-md shadow-primary-main/20"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900"
)}
>
<FileText className={cn("h-4 w-4 shrink-0", currentSlug === page.slug ? "text-white" : "text-slate-400")} />
<span className="truncate">{page.title}</span>
</Link>
))}
</div>
</div>
))}
</div>
</ScrollArea>
</aside>
{/* Main Content */}
<main className="flex-1 flex flex-col min-w-0 bg-white">
{/* Content Header mobile toggle */}
<div className="h-14 border-b border-slate-100 flex items-center px-6 gap-3 bg-white/80 backdrop-blur-md sticky top-0 z-10">
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="p-2 hover:bg-slate-100 rounded-lg text-slate-500 transition-colors border border-transparent hover:border-slate-200"
title={isSidebarOpen ? "收起選單" : "展開選單"}
>
<Menu className="h-5 w-5" />
</button>
<div className="h-5 w-px bg-slate-200 mx-1" />
<div className="flex items-center gap-2 text-slate-800">
<BookOpen className="h-5 w-5 text-primary-main" />
<span className="text-sm font-bold tracking-tight"></span>
</div>
</div>
<ScrollArea className="flex-1 bg-white">
<div className="max-w-4xl mx-auto p-8 md:p-16 lg:p-20">
<article className="prose prose-slate lg:prose-lg max-w-none
prose-headings:text-slate-900 prose-headings:tracking-tight
prose-h1:text-4xl prose-h1:pb-4 prose-h1:border-b prose-h1:border-slate-100
prose-p:text-slate-600 prose-p:leading-8
prose-a:text-primary-main prose-a:no-underline hover:prose-a:underline
prose-blockquote:border-l-4 prose-blockquote:border-primary-main prose-blockquote:bg-slate-50
prose-img:rounded-2xl prose-img:shadow-xl
prose-code:text-primary-main prose-code:bg-primary-lightest prose-code:px-1 prose-code:rounded">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{content}
</ReactMarkdown>
</article>
<div className="mt-24 pt-10 border-t border-slate-100 flex flex-col md:flex-row items-center justify-between gap-6 text-sm">
<div className="flex items-center gap-3 px-4 py-2 bg-slate-50 rounded-full border border-slate-200">
<HelpCircle className="h-4 w-4 text-primary-main" />
<span className="text-slate-600 font-medium whitespace-nowrap"> (分機: 8888)</span>
</div>
<div className="flex items-center gap-4 text-slate-400">
<span>最後更新: 2026-02-13</span>
<span className="h-1 w-1 bg-slate-300 rounded-full" />
<span className="font-semibold tracking-widest uppercase">Star ERP v1.0</span>
</div>
</div>
</div>
</ScrollArea>
</main>
</div>
</AuthenticatedLayout>
);
}