Merge branch 'main' into feat/organized-content

This commit is contained in:
William Tran 2021-08-26 18:12:42 -04:00
commit 2b8362f89c
885 changed files with 15465 additions and 1284 deletions

View File

@ -1,35 +1,52 @@
---
kind: pipeline
type: docker
name: node14
name: node16
steps:
- name: check-lockfile
image: node:16
commands:
- node ./check-lockfile.js
- name: install-deps
image: node:14
image: node:16
depends_on:
- check-lockfile
commands:
- npm install
- name: lint
image: node:14
image: node:16
depends_on:
- install-deps
commands:
- npm run lint
- name: build
image: node:14
image: node:16
depends_on:
- install-deps
commands:
- npm run build
- name: export
image: node:14
image: node:16
depends_on:
- build
commands:
- npm run export
- name: deploy (staging)
image: node:16
depends_on:
- export
environment:
TOKEN:
from_secret: STAGING_TOKEN
commands:
- 'curl -XPOST -H "Authorization: $TOKEN" -H "X-Branch: $DRONE_BRANCH" "https://csclub.uwaterloo.ca/~a3thakra/update-csc/"'
trigger:
event:
exclude:

View File

@ -9,6 +9,9 @@ module.exports = {
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript",
"plugin:react/recommended",
"plugin:prettier/recommended",
],
@ -16,12 +19,37 @@ module.exports = {
rules: {
"prettier/prettier": "error",
"import/first": "error",
"import/order": [
"error",
{
"newlines-between": "always",
"alphabetize": {
"order": "asc",
"caseInsensitive": true,
},
"pathGroups": [
{
"pattern": "@/**",
"group": "external",
"position": "after",
},
{
"pattern": "./*.css",
"group": "index",
"position": "after",
}
],
},
],
"react/prop-types": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
// Turn off these rules
"@typescript-eslint/explicit-module-boundary-types": "off",
"import/no-unresolved": "off",
},
settings: {
react: {

13
.vscode/settings.json vendored
View File

@ -4,9 +4,7 @@
"eslint.codeActionsOnSave.mode": "all",
"[css]": {
"editor.suggest.insertMode": "replace",
"gitlens.codeLens.scopes": [
"document"
],
"gitlens.codeLens.scopes": ["document"],
"editor.formatOnSave": true
},
"[javascript]": {
@ -38,5 +36,10 @@
"files.exclude": {
"node_modules": true
},
"editor.tabSize": 2
}
"editor.tabSize": 2,
"files.eol": "\n",
"[markdown]": {
"editor.wordWrap": "on",
"editor.quickSuggestions": false
}
}

10
check-lockfile.js Normal file
View File

@ -0,0 +1,10 @@
const lockfile = require('./package-lock.json')
if (lockfile.lockfileVersion !== 2) {
console.error(`
Please upgrade to npm v7 and revert changes to the lockfile.
- \`npm i -g npm\` to upgrade.
`.trim())
process.exit(1)
}

View File

@ -1,7 +1,71 @@
.bubble {
.container {
position: relative;
padding: calc(36rem / 16) 0;
overflow-x: clip;
}
.bubble:nth-child(odd) {
background-color: var(--blue-1-20);
.bubble {
--border-radius: calc(5000rem / 16);
display: flex;
flex-direction: row;
position: absolute;
top: 0;
height: 100%;
width: 100%;
justify-content: center;
align-items: flex-start;
}
.bar {
height: 100%;
width: 100%;
}
.decoration {
display: none;
width: calc(128rem / 16);
}
.margin {
display: none;
}
.content {
position: relative;
z-index: 1;
}
.container:nth-child(odd) .bar {
background-color: var(--primary-accent-light);
}
@media only screen and (min-width: calc(1350rem / 16)) {
.container:nth-child(odd) .decoration {
display: block;
position: absolute;
}
.container:nth-child(4n + 1) .bar {
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
transform: translateX(-12.5vw);
}
.container:nth-child(4n + 1) .decoration {
top: calc(-50rem / 16);
right: 8vw;
}
.container:nth-child(4n + 3) .bar {
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
transform: translateX(12.5vw);
}
.container:nth-child(4n + 3) .decoration {
top: calc(-50rem / 16);
left: 8vw;
transform: rotateY(180deg);
}
}

View File

@ -1,11 +1,24 @@
import React from "react";
import { Image } from "@/components/Image";
import { DefaultLayout } from "./DefaultLayout";
import styles from "./Bubble.module.css";
export default function Bubble(props: { children: React.ReactNode }) {
export function Bubble(props: { children: React.ReactNode }) {
return (
<div className={styles.bubble}>
<DefaultLayout>{props.children}</DefaultLayout>
<div className={styles.container}>
<div className={styles.bubble} aria-hidden>
<div className={styles.bar} />
<Image
className={styles.decoration}
src="/images/bubble-decoration.svg"
/>
</div>
<div className={styles.content}>
<DefaultLayout>{props.children}</DefaultLayout>
</div>
</div>
);
}

View File

@ -2,8 +2,8 @@
.link {
font-family: "Poppins", "sans-serif";
border-radius: calc(20rem / 16);
background-color: var(--blue-2);
color: white;
background-color: var(--primary-accent);
color: var(--primary-background);
border: none;
outline: none;
transition-duration: 0.3s;
@ -13,7 +13,7 @@
.button:hover,
.link:hover {
background-color: var(--teal-2);
background-color: var(--secondary-accent);
cursor: pointer;
}

View File

@ -1,4 +1,5 @@
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from "react";
import styles from "./Button.module.css";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {

View File

@ -0,0 +1,6 @@
.code {
padding: 0 calc(4rem / 16);
background: var(--code-background);
border-radius: calc(5rem / 16);
word-wrap: break-word;
}

9
components/Code.tsx Normal file
View File

@ -0,0 +1,9 @@
import React, { HTMLAttributes } from "react";
import styles from "./Code.module.css";
export function Code(props: HTMLAttributes<HTMLElement>) {
const classes = [styles.code, props.className ?? ""];
return <code {...props} className={classes.join(" ")} />;
}

View File

@ -1,8 +1,7 @@
.header {
color: var(--blue-2);
color: var(--primary-accent);
font-weight: 600;
font-size: calc(36rem / 16);
margin-bottom: calc(12rem / 16);
}
.socialLinks {

View File

@ -1,7 +1,9 @@
import React from "react";
import styles from "./ConnectWithUs.module.css";
import { SocialLinks } from "./SocialLinks";
import { Link } from "./Link";
import { SocialLinks } from "./SocialLinks";
import styles from "./ConnectWithUs.module.css";
export function ConnectWithUs() {
return (

View File

@ -2,5 +2,4 @@
margin: 0 auto;
max-width: calc(800rem / 16);
padding: 0 calc(20rem / 16);
padding-bottom: calc(20rem / 16);
}

View File

@ -1,4 +1,5 @@
import React from "react";
import styles from "./DefaultLayout.module.css";
export function DefaultLayout(props: { children: React.ReactNode }) {

View File

@ -1,17 +1,13 @@
.container form {
box-sizing: border-box;
}
.header {
color: var(--blue-2);
color: var(--primary-accent);
font-weight: 600;
font-size: calc(36rem / 16);
margin-bottom: calc(12rem / 16);
}
.button {
margin-top: calc(34rem / 16);
margin-top: calc(26rem / 16);
display: block;
width: fit-content;
}
@media only screen and (max-width: calc(768rem / 16)) {

View File

@ -1,19 +1,24 @@
import React from "react";
import styles from "./EmailSignup.module.css";
import { Button } from "./Button";
import { Input } from "./Input";
import styles from "./EmailSignup.module.css";
export function EmailSignup() {
return (
<section className={styles.container}>
<h1 className={styles.header}>Join Our Mailing List!</h1>
<form className={styles.form} action="">
<Input type="text" placeholder="Full Name*" required />
<Input type="email" placeholder="Email*" required />
<Button type="submit" className={styles.button}>
Subscribe
</Button>
</form>
<h1 className={styles.header}>Join our mailing list!</h1>
<p>
Join our mailing list to receive email notifications about important
news and upcoming events!
</p>
<Button
isLink={true}
href="https://mailman.csclub.uwaterloo.ca/postorius/lists/csc-general.csclub.uwaterloo.ca/"
className={styles.button}
>
Subscribe
</Button>
</section>
);
}

View File

@ -41,7 +41,7 @@
}
.content > h2 {
color: var(--purple-2);
color: var(--primary-heading);
font-size: 1rem;
margin-bottom: calc(14rem / 16);
}

View File

@ -1,10 +1,11 @@
import React, { ReactNode } from "react";
import { Button } from "./Button";
import styles from "./EventCard.module.css";
import { Button } from "./Button";
import { EventSetting } from "./EventSetting";
import { Image } from "./Image";
import styles from "./EventCard.module.css";
interface EventCardProps {
name: string;
short: string;

View File

@ -4,7 +4,7 @@
max-width: calc(540rem / 16);
padding: calc(24rem / 16);
border-radius: calc(20rem / 16);
background-color: white;
background-color: var(--primary-background);
}
.poster {
@ -35,7 +35,7 @@
.setting {
margin: 0;
color: var(--blue-2);
color: var(--primary-accent);
font-size: calc(14rem / 16);
font-weight: 600;
}
@ -50,6 +50,7 @@
@media only screen and (max-width: calc(768rem / 16)) {
.card {
max-width: unset;
padding: 0;
background-color: transparent;
}
@ -66,7 +67,7 @@
.poster {
width: calc(95rem / 16);
height: calc(95rem / 16);
border: 1px solid var(--purple-2);
border: 1px solid var(--primary-heading);
box-sizing: border-box;
margin-right: calc(14rem / 16);
}

View File

@ -1,10 +1,12 @@
import React from "react";
import { Button } from "./Button";
import { Image } from "./Image";
import { EventSetting } from "./EventSetting";
import styles from "./EventDescriptionCard.module.css";
import { Image } from "./Image";
import { Discord, Twitch, Instagram, Facebook } from "./SocialLinks";
import styles from "./EventDescriptionCard.module.css";
interface Props {
name: string;
short: string;

View File

@ -1,4 +1,5 @@
import React from "react";
import styles from "./EventSetting.module.css";
interface Props {

View File

@ -1,6 +1,6 @@
.footer {
box-sizing: border-box;
background: var(--purple-2);
background: var(--primary-heading);
height: calc(66rem / 16);
padding: calc(14rem / 16) 0;
width: 100%;
@ -18,11 +18,16 @@
}
.text {
color: var(--white);
color: var(--primary-background);
font-style: normal;
text-align: center;
}
.email {
color: unset;
text-decoration: unset;
}
@media only screen and (max-width: calc(768rem / 16)) {
.footer {
height: calc(120rem / 16);

View File

@ -1,13 +1,19 @@
import Link from "next/link";
import React from "react";
import styles from "./Footer.module.css";
import { SocialLinks } from "./SocialLinks";
import styles from "./Footer.module.css";
export function Footer() {
return (
<footer className={styles.footer}>
<div className={styles.container}>
<div className={styles.text}>
Have questions? Email us at XX@XXX.COM
Have questions? Email us at{" "}
<Link href="mailto:exec@csclub.uwaterloo.ca">
<a className={styles.email}>exec@csclub.uwaterloo.ca</a>
</Link>
</div>
<SocialLinks color="white" size="small" />
</div>

View File

@ -0,0 +1,7 @@
.line {
display: block;
margin: calc(1rem / 16) 0 calc(34rem / 16);
height: calc(1rem / 16);
border: none;
background-color: var(--primary-heading);
}

View File

@ -0,0 +1,7 @@
import React from "react";
import styles from "./HorizontalLine.module.css";
export function HorizontalLine() {
return <hr className={styles.line} />;
}

View File

@ -8,20 +8,20 @@
border: 0;
border-radius: calc(20rem / 16);
background-color: var(--grey-1-24);
background-color: var(--input-background);
font-size: calc(18rem / 16);
line-height: calc(30rem / 16);
color: var(--grey-3);
color: var(--input-text);
}
.input::placeholder {
color: var(--grey-2);
color: var(--input-placeholder-text);
font-weight: 700;
}
.input:is(:active, :hover, :focus) {
box-sizing: border-box;
border: calc(3rem / 16) solid var(--blue-1);
border: calc(3rem / 16) solid var(--primary-accent-soft);
border-radius: calc(20rem / 16);
outline: none;
padding: calc(7rem / 16) calc(28rem / 16);

View File

@ -1,4 +1,5 @@
import React, { InputHTMLAttributes } from "react";
import styles from "./Input.module.css";
export function Input(props: InputHTMLAttributes<HTMLInputElement>) {

View File

@ -1,10 +1,10 @@
.link {
color: var(--blue-2);
color: var(--primary-accent);
transition-duration: 0.3s;
text-decoration: none;
white-space: normal;
}
.link:hover {
color: var(--teal-2);
color: var(--secondary-accent);
}

View File

@ -1,7 +1,7 @@
import NextLink, { LinkProps as NextLinkProps } from "next/link";
import React from "react";
import styles from "./Link.module.css";
import NextLink from "next/link";
import { LinkProps as NextLinkProps } from "next/link";
type Props = Omit<NextLinkProps, "href"> & { href: string };

View File

@ -5,7 +5,7 @@
}
.card:nth-child(odd) {
background-color: var(--teal-2-20);
background-color: var(--secondary-accent-light);
}
.name {
@ -19,7 +19,7 @@
}
.info {
color: var(--purple-2);
color: var(--primary-heading);
margin-bottom: calc(12rem / 16);
}
@ -29,7 +29,7 @@
right: 0;
cursor: pointer;
margin: calc(20rem / 16);
color: var(--blue-2);
color: var(--primary-accent);
font-size: calc(14rem / 16);
}
@ -46,5 +46,26 @@
}
.dropDownIcon {
fill: var(--blue-2);
fill: var(--primary-accent);
}
@media only screen and (max-width: calc(768rem / 16)) {
.details {
bottom: 0;
left: 0;
top: unset;
right: unset;
}
.dropDownIcon {
display: none;
}
.card {
padding-bottom: calc(48rem / 16);
}
.nameSpacer {
display: none;
}
}

View File

@ -1,5 +1,7 @@
import React, { ReactNode } from "react";
import { EventSetting } from "./EventSetting";
import styles from "./MiniEventCard.module.css";
interface Props {
@ -32,10 +34,8 @@ export const MiniEventCard: React.FC<Props> = ({
</div>
<p className={styles.shortDescription}>{short}</p>
</div>
<p className={styles.details}>View details {dropDownIcon}</p>
<div className={styles.details}>View details {dropDownIcon}</div>
</summary>
<div>{description}</div>
</details>
);

View File

@ -0,0 +1,40 @@
.card {
display: flex;
flex-direction: row;
box-sizing: border-box;
padding: calc(16rem / 16);
color: var(--purple-2);
font-size: 1rem;
}
.card aside {
max-width: calc(142rem / 16);
margin-right: calc(45rem / 16);
display: flex;
justify-content: center;
align-items: center;
}
.card aside img {
max-width: calc(142rem / 16);
max-height: 100%;
object-fit: cover;
}
.content {
padding: calc(4rem / 16);
}
.content h1 {
margin: 0;
margin-top: calc(4rem / 16);
font-size: calc(18rem / 16);
}
.card section {
padding-bottom: 0;
}
.spacer {
margin-top: calc(76rem / 16);
}

View File

@ -0,0 +1,23 @@
import React from "react";
import { Image } from "./Image";
import styles from "./MiniTechTalkCard.module.css";
interface MiniTechTalkProps {
name: string;
short: string;
poster?: string;
}
export function MiniTechTalkCard({ name, poster, short }: MiniTechTalkProps) {
return (
<article className={styles.card}>
<aside>{poster && <Image alt={name} src={poster} />}</aside>
<div className={styles.content}>
<h1>{name}</h1>
<p>{short}</p>
</div>
</article>
);
}

View File

@ -2,8 +2,9 @@
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
background-color: var(--white);
background-color: var(--primary-background);
}
.navContent {
@ -62,16 +63,16 @@
}
.navMenu a {
color: var(--purple-2);
color: var(--primary-heading);
text-decoration: none;
}
.navMenu a.currentPage {
color: var(--blue-2);
color: var(--primary-accent);
}
.navMenu > li > a:hover {
color: var(--blue-2);
color: var(--primary-accent);
font-weight: 600;
}
@ -115,8 +116,8 @@
padding: 0;
border-radius: calc(8rem / 16);
background-color: var(--white);
box-shadow: 0 calc(8rem / 16) 1rem var(--blue-1-20);
background-color: var(--primary-background);
box-shadow: 0 calc(8rem / 16) 1rem var(--primary-accent-light);
font-size: calc(14rem / 16);
}
@ -133,7 +134,7 @@
.dropdown li:hover a,
.dropdown li a:focus {
background-color: var(--blue-1-20);
background-color: var(--primary-accent-light);
}
.dropdown li:first-child a {
@ -154,13 +155,13 @@
/* On a smaller desktop screen, keep the same navbar layout but decrease the
* horizontal padding so it still fits
*/
@media screen and (max-width: calc(960rem / 16)) {
@media only screen and (max-width: calc(960rem / 16)) {
.navContent {
padding: calc(28rem / 16) calc(64rem / 16);
}
}
@media screen and (max-width: calc(768rem / 16)) {
@media only screen and (max-width: calc(768rem / 16)) {
.navContent {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
@ -223,7 +224,7 @@
left: 0;
z-index: 20;
background-color: var(--navbar-gray);
background-color: var(--navbar-page-overlay);
opacity: 0;
transition: 0.5s;
@ -243,7 +244,7 @@
padding: calc(calc(64rem / 16) - 1rem);
padding-left: calc(calc(78rem / 16) - 1rem);
background-color: var(--off-white);
background-color: var(--secondary-background);
transform: translateX(100vw);
transition: 0.5s;

View File

@ -1,7 +1,9 @@
import React, { useReducer } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useReducer } from "react";
import { Image } from "./Image";
import styles from "./Navbar.module.css";
type Menu = {
@ -60,6 +62,10 @@ const menu: Menu = [
name: "Services",
route: "/resources/services",
},
{
name: "Machine Usage",
route: "/resources/machine-usage-agreement",
},
{
name: "Tech Talks",
route: "/resources/tech-talks",
@ -68,6 +74,14 @@ const menu: Menu = [
name: "CS Club Wiki",
route: "https://wiki.csclub.uwaterloo.ca",
},
{
name: "Advice",
route: "/resources/advice/coop",
},
{
name: "Internships",
route: "https://github.com/uwcsc/winter2022-internships",
},
],
},
];

View File

@ -1,17 +1,23 @@
.card {
padding: calc(30rem / 16) calc(40rem / 16);
max-width: calc(524rem / 16);
background-color: var(--white);
background-color: var(--primary-background);
border-radius: calc(20rem / 16);
}
.fit.card {
max-width: unset;
padding: unset;
border-radius: unset;
}
.date {
font-size: calc(18rem / 16);
margin: 0;
}
.author {
color: var(--purple-1);
color: var(--secondary-heading);
font-style: normal;
}
@ -22,9 +28,14 @@
@media only screen and (max-width: calc(768rem / 16)) {
.card {
padding: 0;
max-width: unset;
background-color: transparent;
}
.date {
font-weight: 600;
}
.content > *:first-child {
margin-top: 1rem;
}

View File

@ -1,19 +1,24 @@
import React, { ReactNode } from "react";
import styles from "./NewsCard.module.css";
interface NewsCardProps {
date: Date;
author: string;
children: ReactNode;
fit?: boolean;
}
export const NewsCard: React.FC<NewsCardProps> = ({
date,
author,
children,
fit = false,
}) => {
const classes = fit ? [styles.card, styles.fit] : [styles.card];
return (
<article className={styles.card}>
<article className={classes.join(" ")}>
<h1 className={styles.date}>
<time dateTime={date.toISOString()}>
{date.toLocaleDateString("en-US", {

View File

@ -9,15 +9,23 @@
}
.content h1 {
margin: 1rem 0;
font-size: calc(24rem / 16);
font-weight: 600;
color: var(--blue-2);
color: var(--primary-accent);
}
.content h2,
.content h3 {
font-size: calc(18rem / 16);
margin-top: calc(24rem / 16);
margin-bottom: calc(16rem / 16);
}
.nav {
top: calc(20rem / 16);
position: sticky;
height: 100%;
margin: calc(8rem / 16) calc(32rem / 16) 0 0;
color: var(--purple-2);
color: var(--primary-heading);
font-weight: 500;
}
@ -26,10 +34,10 @@
overflow: hidden;
white-space: nowrap;
font-size: calc(14rem / 16);
border-bottom: calc(1rem / 16) solid var(--blue-2-25);
border-bottom: calc(1rem / 16) solid var(--primary-accent-light);
align-items: center;
height: calc(40rem / 16);
width: calc(284rem / 16);
width: calc(200rem / 16);
padding: 0 calc(14rem / 16);
cursor: pointer;
}
@ -41,8 +49,8 @@
}
.selected {
background-color: var(--blue-1-05);
color: var(--blue-2);
background-color: var(--primary-accent-dim);
color: var(--primary-accent);
font-weight: 700;
}
@ -56,7 +64,7 @@
.selected .marker {
display: inline;
background-color: var(--blue-2);
background-color: var(--primary-accent);
height: calc(24rem / 16);
width: calc(4rem / 16);
margin-right: 1rem;
@ -73,7 +81,7 @@
flex: 1;
display: flex;
cursor: pointer;
color: var(--purple-2);
color: var(--primary-heading);
font-size: calc(12rem / 16);
}
@ -88,15 +96,15 @@
}
.arrowHeading {
color: var(--blue-2);
color: var(--primary-accent);
font-size: calc(14rem / 16);
font-weight: 700;
border-bottom: calc(2rem / 16) solid var(--blue-2);
border-bottom: calc(2rem / 16) solid var(--primary-accent);
padding-bottom: calc(4rem / 16);
}
.arrow {
fill: var(--blue-2);
fill: var(--primary-accent);
margin-top: calc(27rem / 16);
}
@ -110,6 +118,12 @@
padding-left: calc(8rem / 16);
}
.link,
.link:visited {
color: inherit;
text-decoration: none;
}
.burger {
display: none;
}
@ -152,6 +166,18 @@
padding: 0 calc(16rem / 16);
}
.content h1 {
font-size: calc(18rem / 16);
}
.content h2,
.content h3,
.content h4 {
font-size: calc(18rem / 16);
margin-top: calc(24rem / 16);
margin-bottom: calc(8rem / 16);
}
.nav {
margin: 0;
}

View File

@ -7,8 +7,9 @@ import React, {
useCallback,
} from "react";
import styles from "./OrganizedContent.module.css";
import NextLink from "next/link";
export interface LinkProps {
export interface LinkProps { //todo check if we need this
className?: string;
id: string;
children: ReactNode;
@ -20,7 +21,6 @@ type Link = ComponentType<LinkProps>;
interface Section {
id: string;
title: string;
Content: ComponentType;
}
const READ_ALL_TITLE = "Read All";
@ -28,18 +28,26 @@ export const READ_ALL_ID = "read-all";
interface Props {
sections: Section[];
currentId: string;
id: string;
children: ReactNode;
pageTitle: string;
link: Link;
}
export function OrganizedContent(props: Props) {
const sections = createSections(props.sections);
const currentIndex = sections.findIndex(({ id }) => id === props.currentId);
export function OrganizedContent({
sections,
id,
children,
pageTitle,
link: Link,
}: Props) {
const [mobileNavOpen, setMobileNavOpen] = useState(false);
const currentIndex = sections.findIndex(
({ id: sectionId }) => sectionId === id
);
if (currentIndex < 0) {
throw new Error(`Section with ID ${props.currentId} was not found`);
throw new Error(`Section with ID ${id} was not found`);
}
const section = sections[currentIndex];
@ -70,25 +78,25 @@ export function OrganizedContent(props: Props) {
<Nav
sections={sections}
currentIndex={currentIndex}
link={props.link}
pageTitle={props.pageTitle}
link={Link}
pageTitle={pageTitle}
setMobileNavOpen={setMobileNavOpen}
/>
</div>
</div>
<div className={styles.content}>
{isReadAll ? (
<section.Content />
children
) : (
<>
<div>
<section>
<h1>{section.title}</h1>
<section.Content />
</div>
{children}
</section>
<Footer
sections={sections}
currentIndex={currentIndex}
link={props.link}
link={Link}
/>
</>
)}
@ -156,7 +164,7 @@ function Nav({
setMobileNavOpen,
}: NavProps) {
return (
<div className={styles.nav}>
<nav className={styles.nav}>
<h1 className={styles.mobileNavTitle}>{pageTitle}</h1>
{sections.map((section, index) => {
const classNames = [styles.navItem];
@ -181,7 +189,7 @@ function Nav({
</Link>
);
})}
</div>
</nav>
);
}
@ -240,26 +248,68 @@ function useDebounce(func: () => void, delay = 300) {
}, [func, delay]);
}
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,
];
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" }) {

View File

@ -0,0 +1,50 @@
.page {
margin-top: calc(60rem / 16);
margin-bottom: calc(40rem / 16);
}
.headerContainer {
display: flex;
flex-direction: row;
align-items: flex-end;
padding-bottom: 1rem;
border-bottom: calc(1rem / 16) solid var(--primary-heading);
}