📦 EqualifyEverything / equalify-reflow-docs

📄 MarkdownPage.tsx · 96 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeHighlight from 'rehype-highlight';

const remarkPlugins = [remarkGfm];
const rehypePlugins = [rehypeRaw, rehypeHighlight];

interface MarkdownPageProps {
    filePath: string;
}

export const MarkdownPage = ({ filePath }: MarkdownPageProps) => {
    const [content, setContent] = useState<string>('');
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<string | null>(null);
    const navigate = useNavigate();

    const markdownComponents = {
        a: ({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement> & { children?: React.ReactNode }) => {
            if (href?.startsWith('#/')) {
                return (
                    <a
                        href={href}
                        onClick={(e) => {
                            e.preventDefault();
                            navigate(href.slice(1));
                        }}
                        {...props}
                    >
                        {children}
                    </a>
                );
            }
            return <a href={href} {...props}>{children}</a>;
        },
    };

    useEffect(() => {
        setLoading(true);
        setError(null);

        const modules = import.meta.glob('../content/*.md', { query: '?raw', import: 'default' });
        const key = `../content/${filePath}.md`;

        if (modules[key]) {
            (modules[key]() as Promise<string>)
                .then(text => {
                    setContent(text);
                    setLoading(false);
                })
                .catch(() => {
                    setError('Failed to load document.');
                    setLoading(false);
                });
        } else {
            setError(`Document "${filePath}" not found.`);
            setLoading(false);
        }
    }, [filePath]);

    if (loading) {
        return (
            <div className="animate-pulse space-y-4">
                <p className="sr-only">Loading document...</p>
                <div className="h-8 bg-gray-200 rounded w-3/4"></div>
                <div className="h-4 bg-gray-200 rounded w-full"></div>
                <div className="h-4 bg-gray-200 rounded w-5/6"></div>
                <div className="h-4 bg-gray-200 rounded w-4/6"></div>
            </div>
        );
    }

    if (error) {
        return (
            <div className="text-center py-12" role="alert">
                <p className="text-gray-500 text-lg">{error}</p>
            </div>
        );
    }

    return (
        <article className="prose prose-lg max-w-none prose-headings:text-uic-blue prose-a:text-uic-red prose-a:no-underline hover:prose-a:underline prose-code:text-sm prose-table:border-collapse prose-th:bg-gray-100 prose-th:border prose-th:border-gray-300 prose-th:px-3 prose-th:py-2 prose-td:border prose-td:border-gray-300 prose-td:px-3 prose-td:py-2">
            <ReactMarkdown
                remarkPlugins={remarkPlugins}
                rehypePlugins={rehypePlugins}
                components={markdownComponents}
            >
                {content}
            </ReactMarkdown>
        </article>
    );
};