Skip to content

Commit 53f9b3f

Browse files
committed
feat: add docs site
1 parent 86ef48f commit 53f9b3f

26 files changed

Lines changed: 1809 additions & 371 deletions

docs/src/App.tsx

Lines changed: 55 additions & 367 deletions
Large diffs are not rendered by default.

docs/src/components/CodeBlock.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { useEffect, useRef, useState } from "react";
2+
import { highlightElement } from "@speed-highlight/core/dist/index.js";
3+
import { IconButton } from "soda-material";
4+
import { mdiContentCopy, mdiCheck } from "@mdi/js";
5+
6+
const LANG_MAP: Record<string, string> = {
7+
tsx: "ts",
8+
jsx: "js",
9+
typescript: "ts",
10+
ts: "ts",
11+
html: "html",
12+
css: "css",
13+
bash: "bash",
14+
json: "json",
15+
js: "js",
16+
javascript: "js",
17+
};
18+
19+
interface CodeBlockProps {
20+
code: string;
21+
language?: string;
22+
filename?: string;
23+
}
24+
25+
export default function CodeBlock({ code, language = "tsx", filename }: CodeBlockProps) {
26+
const codeRef = useRef<HTMLElement>(null);
27+
const [copied, setCopied] = useState(false);
28+
29+
useEffect(() => {
30+
const el = codeRef.current;
31+
if (!el) return;
32+
el.textContent = code;
33+
highlightElement(el, LANG_MAP[language] ?? language, "multiline", { hideLineNumbers: true });
34+
}, [code, language]);
35+
36+
const handleCopy = () => {
37+
navigator.clipboard.writeText(code).then(() => {
38+
setCopied(true);
39+
setTimeout(() => setCopied(false), 2000);
40+
});
41+
};
42+
43+
return (
44+
<div
45+
style={{
46+
position: "relative",
47+
borderRadius: 8,
48+
margin: "1rem 0",
49+
border: "1px solid var(--md-sys-color-outline-variant)",
50+
fontSize: 13,
51+
}}
52+
>
53+
{filename && (
54+
<div
55+
style={{
56+
padding: "5px 12px",
57+
fontSize: 12,
58+
fontFamily: "monospace",
59+
background: "var(--md-sys-color-surface-container)",
60+
borderBottom: "1px solid var(--md-sys-color-outline-variant)",
61+
color: "var(--md-sys-color-on-surface-variant)",
62+
borderRadius: "8px 8px 0 0",
63+
}}
64+
>
65+
{filename}
66+
</div>
67+
)}
68+
<div style={{ position: "absolute", top: filename ? 28 : 4, right: 4, zIndex: 1 }}>
69+
<IconButton path={copied ? mdiCheck : mdiContentCopy} size={0.8} onClick={handleCopy} />
70+
</div>
71+
<code ref={codeRef} style={{ display: "block", overflowX: "auto", scrollbarWidth: "thin" }} />
72+
</div>
73+
);
74+
}

docs/src/components/Demo.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, { Component, type ReactNode } from "react";
2+
3+
class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
4+
state = { error: null };
5+
static getDerivedStateFromError(error: Error) {
6+
return { error };
7+
}
8+
render() {
9+
if (this.state.error) {
10+
return (
11+
<div style={{ padding: "1rem", color: "red", fontFamily: "monospace", fontSize: 12 }}>
12+
Error: {(this.state.error as Error).message}
13+
</div>
14+
);
15+
}
16+
return this.props.children;
17+
}
18+
}
19+
20+
interface DemoProps {
21+
title?: string;
22+
children: ReactNode;
23+
}
24+
25+
export default function Demo({ title, children }: DemoProps) {
26+
return (
27+
<div
28+
style={{
29+
border: "1px solid var(--md-sys-color-outline-variant)",
30+
borderRadius: 8,
31+
overflow: "hidden",
32+
margin: "1rem 0",
33+
}}
34+
>
35+
{title && (
36+
<div
37+
style={{
38+
padding: "8px 16px",
39+
fontSize: 12,
40+
fontWeight: 600,
41+
background: "var(--md-sys-color-surface-container)",
42+
borderBottom: "1px solid var(--md-sys-color-outline-variant)",
43+
color: "var(--md-sys-color-on-surface-variant)",
44+
}}
45+
>
46+
{title}
47+
</div>
48+
)}
49+
<div
50+
style={{
51+
padding: "1.5rem",
52+
background: "var(--md-sys-color-surface)",
53+
}}
54+
>
55+
<ErrorBoundary>{children}</ErrorBoundary>
56+
</div>
57+
</div>
58+
);
59+
}

docs/src/components/Icon.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { forwardRef } from "react";
2+
import type { CSSProperties, SVGProps } from "react";
3+
4+
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "path" | "color"> {
5+
path: string;
6+
size?: number | string | null;
7+
color?: string | null;
8+
}
9+
10+
const Icon = forwardRef<SVGSVGElement, IconProps>(
11+
({ path, size = null, color = "currentColor", style, ...rest }, ref) => {
12+
const computedStyle: CSSProperties = { ...style };
13+
if (size !== null) {
14+
const dimension = typeof size === "string" ? size : `${size * 1.5}rem`;
15+
computedStyle.width = dimension;
16+
computedStyle.height = dimension;
17+
}
18+
return (
19+
<svg ref={ref} viewBox="0 0 24 24" style={computedStyle} role="presentation" {...rest}>
20+
<path d={path} style={{ fill: color ?? "currentColor" }} />
21+
</svg>
22+
);
23+
},
24+
);
25+
26+
Icon.displayName = "Icon";
27+
export default Icon;

docs/src/components/Layout.tsx

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import React, { useState } from "react";
2+
import { TopAppBar, IconButton } from "soda-material";
3+
import { useWindowSizeType } from "soda-material/dist/hooks/use-media-query";
4+
import { mdiMenu, mdiGithub } from "@mdi/js";
5+
import type { NavSection } from "../App.tsx";
6+
7+
interface LayoutProps {
8+
navSections: NavSection[];
9+
currentHash: string;
10+
onNavigate: (hash: string) => void;
11+
children: React.ReactNode;
12+
}
13+
14+
function Sidebar({
15+
navSections,
16+
currentHash,
17+
onNavigate,
18+
}: Pick<LayoutProps, "navSections" | "currentHash" | "onNavigate">) {
19+
return (
20+
<nav
21+
style={{
22+
display: "flex",
23+
flexDirection: "column",
24+
padding: "1.5rem 0 2rem",
25+
gap: 0,
26+
}}
27+
>
28+
{navSections.map((section) => (
29+
<div key={section.title} style={{ marginBottom: "1.25rem" }}>
30+
{/* Section label */}
31+
<div
32+
style={{
33+
padding: "0 1.5rem 0.4rem",
34+
fontSize: 11,
35+
fontWeight: 700,
36+
letterSpacing: "0.1em",
37+
textTransform: "uppercase",
38+
color: "var(--md-sys-color-on-surface-variant)",
39+
opacity: 0.6,
40+
}}
41+
>
42+
{section.title}
43+
</div>
44+
45+
{/* Nav items */}
46+
{section.items.map((item) => {
47+
const active = currentHash === item.hash;
48+
return (
49+
<a
50+
key={item.hash}
51+
href={item.hash}
52+
onClick={(e) => {
53+
e.preventDefault();
54+
onNavigate(item.hash);
55+
}}
56+
style={{
57+
display: "block",
58+
padding: "0.45rem 1.5rem",
59+
fontSize: 14,
60+
fontWeight: active ? 600 : 400,
61+
color: active
62+
? "var(--md-sys-color-primary)"
63+
: "var(--md-sys-color-on-surface-variant)",
64+
textDecoration: "none",
65+
borderLeft: `2px solid ${active ? "var(--md-sys-color-primary)" : "transparent"}`,
66+
background: active ? "var(--md-sys-color-primary-container)" : "transparent",
67+
transition: "color 150ms, background 150ms, border-color 150ms",
68+
}}
69+
onMouseEnter={(e) => {
70+
if (!active) {
71+
(e.currentTarget as HTMLAnchorElement).style.color =
72+
"var(--md-sys-color-on-surface)";
73+
(e.currentTarget as HTMLAnchorElement).style.background =
74+
"var(--md-sys-color-surface-container)";
75+
}
76+
}}
77+
onMouseLeave={(e) => {
78+
if (!active) {
79+
(e.currentTarget as HTMLAnchorElement).style.color =
80+
"var(--md-sys-color-on-surface-variant)";
81+
(e.currentTarget as HTMLAnchorElement).style.background = "transparent";
82+
}
83+
}}
84+
>
85+
{item.label}
86+
</a>
87+
);
88+
})}
89+
</div>
90+
))}
91+
</nav>
92+
);
93+
}
94+
95+
export default function Layout({ navSections, currentHash, onNavigate, children }: LayoutProps) {
96+
const [drawerOpen, setDrawerOpen] = useState(false);
97+
const sizeType = useWindowSizeType();
98+
const isExpanded = sizeType === "expanded";
99+
100+
const handleNavClick = (hash: string) => {
101+
location.hash = hash;
102+
onNavigate(hash);
103+
setDrawerOpen(false);
104+
};
105+
106+
return (
107+
<div
108+
style={{
109+
display: "flex",
110+
height: "100vh",
111+
overflow: "hidden",
112+
background: "var(--md-sys-color-surface)",
113+
}}
114+
>
115+
{/* Desktop sidebar */}
116+
{isExpanded && (
117+
<aside
118+
style={{
119+
width: 240,
120+
flexShrink: 0,
121+
display: "flex",
122+
flexDirection: "column",
123+
borderRight: "1px solid var(--md-sys-color-outline-variant)",
124+
overflowY: "auto",
125+
}}
126+
>
127+
{/* Logo */}
128+
<div
129+
style={{
130+
padding: "1.25rem 1.5rem 0.75rem",
131+
fontSize: 15,
132+
fontWeight: 700,
133+
letterSpacing: "-0.01em",
134+
color: "var(--md-sys-color-on-surface)",
135+
borderBottom: "1px solid var(--md-sys-color-outline-variant)",
136+
}}
137+
>
138+
module-tsx
139+
</div>
140+
<Sidebar
141+
navSections={navSections}
142+
currentHash={currentHash}
143+
onNavigate={handleNavClick}
144+
/>
145+
</aside>
146+
)}
147+
148+
{/* Mobile drawer overlay */}
149+
{!isExpanded && drawerOpen && (
150+
<>
151+
<div
152+
onClick={() => setDrawerOpen(false)}
153+
style={{
154+
position: "fixed",
155+
inset: 0,
156+
background: "rgba(0,0,0,0.32)",
157+
zIndex: 100,
158+
}}
159+
/>
160+
<aside
161+
style={{
162+
position: "fixed",
163+
top: 0,
164+
left: 0,
165+
bottom: 0,
166+
width: 280,
167+
background: "var(--md-sys-color-surface)",
168+
borderRight: "1px solid var(--md-sys-color-outline-variant)",
169+
overflowY: "auto",
170+
zIndex: 101,
171+
}}
172+
>
173+
<div
174+
style={{
175+
padding: "1.25rem 1.5rem 0.75rem",
176+
fontSize: 15,
177+
fontWeight: 700,
178+
letterSpacing: "-0.01em",
179+
color: "var(--md-sys-color-on-surface)",
180+
borderBottom: "1px solid var(--md-sys-color-outline-variant)",
181+
}}
182+
>
183+
module-tsx
184+
</div>
185+
<Sidebar
186+
navSections={navSections}
187+
currentHash={currentHash}
188+
onNavigate={handleNavClick}
189+
/>
190+
</aside>
191+
</>
192+
)}
193+
194+
{/* Main column */}
195+
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
196+
<TopAppBar
197+
leadingNavigationIcon={
198+
!isExpanded ? (
199+
<IconButton path={mdiMenu} onClick={() => setDrawerOpen((v) => !v)} />
200+
) : undefined
201+
}
202+
trailingIcon={
203+
<IconButton
204+
path={mdiGithub}
205+
onClick={() => window.open("https://github.com/YieldRay/module-tsx", "_blank")}
206+
/>
207+
}
208+
>
209+
module-tsx
210+
</TopAppBar>
211+
<main className="sd-scrollbar" style={{ flex: 1, overflowY: "auto" }}>
212+
<div style={{ maxWidth: 860, margin: "0 auto", padding: "2rem 1.5rem 4rem" }}>
213+
{children}
214+
</div>
215+
</main>
216+
</div>
217+
</div>
218+
);
219+
}

0 commit comments

Comments
 (0)