www-new/components/OrganizedContent.tsx

219 lines
5.1 KiB
TypeScript

import NextLink from "next/link";
import React, { ReactNode, ComponentType } from "react";
import styles from "./OrganizedContent.module.css";
type Link = ComponentType<LinkProps>;
interface Section {
id: string;
title: string;
}
const READ_ALL_TITLE = "Read All";
export const READ_ALL_ID = "read-all";
interface Props {
sections: Section[];
id: string;
children: ReactNode;
link: Link;
}
export function OrganizedContent({
sections,
id,
children,
link: Link,
}: Props) {
const currentIndex = sections.findIndex(
({ id: sectionId }) => sectionId === id
);
if (currentIndex < 0) {
throw new Error(`Section with ID ${id} 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={Link} />
<div className={styles.content}>
{isReadAll ? (
children
) : (
<>
<section>
<h1>{section.title}</h1>
{children}
</section>
<Footer
sections={sections}
currentIndex={currentIndex}
link={Link}
/>
</>
)}
</div>
</div>
);
}
interface NavProps {
sections: Section[];
currentIndex: number;
link: Link;
}
function Nav({ sections, currentIndex, link: Link }: NavProps) {
return (
<nav 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>
);
})}
</nav>
);
}
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>
);
}
export interface SectionWithContent {
section: Section;
Content: ComponentType;
}
export function createReadAllSection(
sections: Section[],
content: false
): Section;
export function createReadAllSection(
sections: SectionWithContent[],
content: true
): SectionWithContent;
export function createReadAllSection(
sections: SectionWithContent[] | Section[],
content = true
): SectionWithContent | Section {
const readAllSection = {
id: READ_ALL_ID,
title: READ_ALL_TITLE,
};
return content
? {
section: readAllSection,
Content: function ReadAllContent() {
return (
<>
{(sections as SectionWithContent[]).map(
({ section: { id, title }, Content }) => (
<section key={id}>
<h1>{title}</h1>
<Content />
</section>
)
)}
</>
);
},
}
: readAllSection;
}
export interface LinkProps {
className?: string;
id: string;
children: ReactNode;
}
export function createLink(page: string) {
let base = page.startsWith("/") ? page : `/${page}`;
base = base.endsWith("/") ? base : `${base}/`;
return function Link({ className, id, children }: LinkProps) {
const href = id === READ_ALL_ID ? base : base + id;
return (
<NextLink href={href}>
<a className={`${styles.link} ${className ?? ""}`}>{children}</a>
</NextLink>
);
};
}
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>
);
}