176 lines
4.1 KiB
TypeScript
176 lines
4.1 KiB
TypeScript
import React, { ReactNode, ComponentType } from "react";
|
|
|
|
import styles from "./OrganizedContent.module.css";
|
|
|
|
export interface LinkProps {
|
|
className?: string;
|
|
id: string;
|
|
children: ReactNode;
|
|
}
|
|
|
|
type Link = ComponentType<LinkProps>;
|
|
|
|
interface Section {
|
|
id: string;
|
|
title: string;
|
|
Content: ComponentType;
|
|
}
|
|
|
|
const READ_ALL_TITLE = "Read All";
|
|
export const READ_ALL_ID = "read-all";
|
|
|
|
interface Props {
|
|
sections: Section[];
|
|
currentId: string;
|
|
link: Link;
|
|
}
|
|
|
|
export function OrganizedContent(props: Props) {
|
|
const sections = createSections(props.sections);
|
|
const currentIndex = sections.findIndex(({ id }) => id === props.currentId);
|
|
|
|
if (currentIndex < 0) {
|
|
throw new Error(`Section with ID ${props.currentId} was not found`);
|
|
}
|
|
|
|
const section = sections[currentIndex];
|
|
const isReadAll = section.id === READ_ALL_ID;
|
|
|
|
return (
|
|
<div className={styles.wrapper}>
|
|
<Nav sections={sections} currentIndex={currentIndex} link={props.link} />
|
|
<div className={styles.content}>
|
|
{isReadAll ? (
|
|
<section.Content />
|
|
) : (
|
|
<>
|
|
<div>
|
|
<h1>{section.title}</h1>
|
|
<section.Content />
|
|
</div>
|
|
<Footer
|
|
sections={sections}
|
|
currentIndex={currentIndex}
|
|
link={props.link}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface NavProps {
|
|
sections: Section[];
|
|
currentIndex: number;
|
|
link: Link;
|
|
}
|
|
|
|
function Nav({ sections, currentIndex, link: Link }: NavProps) {
|
|
return (
|
|
<div className={styles.nav}>
|
|
{sections.map((section, index) => {
|
|
const classNames = [styles.navItem];
|
|
|
|
if (index === currentIndex) {
|
|
classNames.push(styles.selected);
|
|
}
|
|
|
|
if (section.id === READ_ALL_ID) {
|
|
classNames.push(styles.readAll);
|
|
}
|
|
|
|
return (
|
|
<Link
|
|
className={classNames.join(" ")}
|
|
id={section.id}
|
|
key={section.id}
|
|
>
|
|
<span className={styles.marker} />
|
|
<div>{section.title}</div>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface FooterProps {
|
|
sections: Section[];
|
|
currentIndex: number;
|
|
link: Link;
|
|
}
|
|
|
|
function Footer({ sections, currentIndex, link: Link }: FooterProps) {
|
|
const prevSection =
|
|
currentIndex > 0 && sections[currentIndex - 1].id !== READ_ALL_ID
|
|
? sections[currentIndex - 1]
|
|
: undefined;
|
|
const nextSection =
|
|
currentIndex < sections.length - 1 &&
|
|
sections[currentIndex + 1].id !== READ_ALL_ID
|
|
? sections[currentIndex + 1]
|
|
: undefined;
|
|
|
|
return (
|
|
<div className={styles.footer}>
|
|
{prevSection && (
|
|
<Link className={styles.previous} id={prevSection.id}>
|
|
<Arrow direction="left" />
|
|
<div>
|
|
<div>Previous</div>
|
|
<div className={styles.arrowHeading}>{prevSection.title}</div>
|
|
</div>
|
|
</Link>
|
|
)}
|
|
{nextSection && (
|
|
<Link className={styles.next} id={nextSection.id}>
|
|
<div>
|
|
<div>Next</div>
|
|
<div className={styles.arrowHeading}>{nextSection.title}</div>
|
|
</div>
|
|
<Arrow direction="right" />
|
|
</Link>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function createSections(sections: Section[]) {
|
|
return [
|
|
{
|
|
id: READ_ALL_ID,
|
|
title: READ_ALL_TITLE,
|
|
Content() {
|
|
return (
|
|
<>
|
|
{sections.map(({ id, title, Content: SectionContent }) => (
|
|
<div key={id}>
|
|
<h1>{title}</h1>
|
|
<SectionContent />
|
|
</div>
|
|
))}
|
|
</>
|
|
);
|
|
},
|
|
},
|
|
...sections,
|
|
];
|
|
}
|
|
|
|
function Arrow({ direction }: { direction: "left" | "right" }) {
|
|
return (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="14"
|
|
height="9"
|
|
viewBox="0 0 14 9"
|
|
className={`${styles.arrow} ${
|
|
direction === "left" ? styles.prevArrow : styles.nextArrow
|
|
}`}
|
|
>
|
|
<path d="M6.24407 8.12713C6.64284 8.58759 7.35716 8.58759 7.75593 8.12713L13.3613 1.65465C13.9221 1.00701 13.4621 0 12.6053 0H1.39467C0.537918 0 0.0778675 1.00701 0.638743 1.65465L6.24407 8.12713Z" />
|
|
</svg>
|
|
);
|
|
}
|