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:
92
frontend/src/pages/GuidePage/GuideTableOfContents.tsx
Normal file
92
frontend/src/pages/GuidePage/GuideTableOfContents.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user