feat: add guide page foundation and routing (refs #203)

- Create GuidePage with responsive layout (sticky TOC sidebar desktop, collapsible accordion mobile)
- Add GuideTableOfContents with scroll-based active section tracking
- Create GuideScreenshot and GuideTable shared components
- Add guideTypes.ts with section metadata for all 10 sections
- Add lazy-loaded /guide route in App.tsx with public access
- Placeholder section components for all 10 guide sections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-15 16:45:17 -06:00
parent d8ab00970d
commit 864da55cec
17 changed files with 652 additions and 3 deletions

View File

@@ -0,0 +1,92 @@
import { useState, useEffect } from 'react';
import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { guideSections } from './guideTypes';
interface GuideTableOfContentsProps {
activeSection: string;
}
export const GuideTableOfContents = ({ activeSection }: GuideTableOfContentsProps) => {
const [isMobile, setIsMobile] = useState(false);
const [tocExpanded, setTocExpanded] = useState(false);
useEffect(() => {
const check = () => setIsMobile(window.innerWidth < 768);
check();
window.addEventListener('resize', check);
return () => window.removeEventListener('resize', check);
}, []);
const handleClick = (sectionId: string) => {
const element = document.getElementById(sectionId);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
if (isMobile) {
setTocExpanded(false);
}
};
const tocContent = (
<nav aria-label="Table of contents">
<ul className="space-y-1">
{guideSections.map((section) => (
<li key={section.id}>
<button
onClick={() => handleClick(section.id)}
className={`w-full text-left px-3 py-3 rounded-md text-sm transition-colors ${
activeSection === section.id
? 'bg-primary-500/15 text-primary-400 font-semibold'
: 'text-titanio/70 hover:text-titanio hover:bg-white/5'
}`}
>
{section.title}
</button>
</li>
))}
</ul>
</nav>
);
if (isMobile) {
return (
<div className="mb-6">
<Accordion
expanded={tocExpanded}
onChange={(_, expanded) => setTocExpanded(expanded)}
sx={{
backgroundColor: 'rgba(255,255,255,0.03)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '8px !important',
'&:before': { display: 'none' },
}}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ color: 'rgba(168,184,192,0.7)' }} />}
sx={{
color: 'rgba(242,243,246,0.9)',
fontWeight: 600,
minHeight: 48,
'& .MuiAccordionSummary-content': { margin: '12px 0' },
}}
>
Table of Contents
</AccordionSummary>
<AccordionDetails sx={{ pt: 0 }}>
{tocContent}
</AccordionDetails>
</Accordion>
</div>
);
}
return (
<aside className="sticky top-20 w-64 flex-shrink-0 max-h-[calc(100vh-6rem)] overflow-y-auto pr-4">
<h2 className="text-sm font-semibold text-titanio/50 uppercase tracking-wider mb-4 px-3">
Contents
</h2>
{tocContent}
</aside>
);
};