commit
f3d9a08a73
@ -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) |
||||
} |
@ -1,7 +1,71 @@ |
||||
.bubble { |
||||
.container { |
||||
position: relative; |
||||
padding: calc(36rem / 16) 0; |
||||
overflow-x: clip; |
||||
} |
||||
|
||||
.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; |
||||
} |
||||
|
||||
.bubble:nth-child(odd) { |
||||
.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); |
||||
} |
||||
} |
||||
|
@ -1,13 +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> |
||||
); |
||||
} |
||||
|
@ -0,0 +1,6 @@ |
||||
.code { |
||||
padding: 0 calc(4rem / 16); |
||||
background: var(--code-background); |
||||
border-radius: calc(5rem / 16); |
||||
word-wrap: break-word; |
||||
} |
@ -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(" ")} />; |
||||
} |
@ -1,21 +1,24 @@ |
||||
import React from "react"; |
||||
|
||||
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> |
||||
); |
||||
} |
||||
|
@ -0,0 +1,7 @@ |
||||
.line { |
||||
display: block; |
||||
margin: calc(34rem / 16) 0; |
||||
height: calc(1rem / 16); |
||||
border: none; |
||||
background-color: var(--primary-heading); |
||||
} |
@ -0,0 +1,7 @@ |
||||
import React from "react"; |
||||
|
||||
import styles from "./HorizontalLine.module.css"; |
||||
|
||||
export function HorizontalLine() { |
||||
return <hr className={styles.line} />; |
||||
} |
@ -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); |
||||
} |
||||
|
||||
.header { |
||||
line-height: 1; |
||||
color: var(--primary-heading); |
||||
font-size: calc(48rem / 16); |
||||
margin: 0 0 0 calc(36rem / 16); |
||||
text-align: center; |
||||
} |
||||
|
||||
.imageRight { |
||||
flex-direction: row-reverse; |
||||
} |
||||
|
||||
.imageRight .header { |
||||
text-align: left; |
||||
margin-left: 0; |
||||
} |
||||
|
||||
@media only screen and (max-width: calc(768rem / 16)) { |
||||
.headerContainer { |
||||
flex-direction: column; |
||||
align-items: center; |
||||
border: none; |
||||
} |
||||
|
||||
.header { |
||||
font-size: calc(24rem / 16); |
||||
margin: 1.5rem 0 0 0; |
||||
} |
||||
|
||||
.headerImage { |
||||
width: calc(100rem / 16); |
||||
} |
||||
|
||||
.description { |
||||
display: none; |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
import React, { ReactNode } from "react"; |
||||
|
||||
import { Image } from "@/components/Image"; |
||||
|
||||
import styles from "./Header.module.css"; |
||||
|
||||
export interface Props { |
||||
title: string; |
||||
image: string; |
||||
children: ReactNode; |
||||
description?: string; |
||||
imagePosition?: "left" | "right"; |
||||
} |
||||
|
||||
export function Header({ |
||||
title, |
||||
image, |
||||
children, |
||||
description, |
||||
imagePosition, |
||||
}: Props) { |
||||
return ( |
||||
<main className={styles.page}> |
||||
<header |
||||
className={`${styles.headerContainer} ${ |
||||
imagePosition === "right" ? styles.imageRight : "" |
||||
}`}
|
||||
> |
||||
<Image src={image} className={styles.headerImage} /> |
||||
<div> |
||||
<h1 className={styles.header}>{title}</h1> |
||||
{description && <p className={styles.description}>{description}</p>} |
||||
</div> |
||||
</header> |
||||
{children} |
||||
</main> |
||||
); |
||||
} |
@ -0,0 +1,84 @@ |
||||
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote"; |
||||
import React, { ComponentType } from "react"; |
||||
|
||||
import { |
||||
createLink, |
||||
createReadAllSection, |
||||
LinkProps, |
||||
OrganizedContent, |
||||
} from "@/components/OrganizedContent"; |
||||
|
||||
import { GetShapesConfig } from "../ShapesBackground"; |
||||
|
||||
import { Header } from "./Header"; |
||||
|
||||
export interface SerializedSection { |
||||
section: { |
||||
id: string; |
||||
title: string; |
||||
}; |
||||
content: MDXRemoteSerializeResult; |
||||
} |
||||
|
||||
export interface Props { |
||||
sections: SerializedSection[]; |
||||
} |
||||
|
||||
export interface Options { |
||||
pagePath: string; |
||||
title: string; |
||||
image: string; |
||||
getShapesConfig?: GetShapesConfig; |
||||
imagePosition?: "left" | "right"; |
||||
link?: ComponentType<LinkProps>; |
||||
description?: string; |
||||
} |
||||
|
||||
export function createReadAllPage({ |
||||
title, |
||||
image, |
||||
pagePath, |
||||
getShapesConfig, |
||||
link, |
||||
description, |
||||
imagePosition, |
||||
}: Options) { |
||||
const Link = link ?? createLink(pagePath); |
||||
|
||||
function Page({ sections }: Props) { |
||||
const readAllSection = createReadAllSection( |
||||
sections.map(({ section, content }) => ({ |
||||
section, |
||||
Content() { |
||||
return <MDXRemote {...content} />; |
||||
}, |
||||
})), |
||||
true |
||||
); |
||||
|
||||
return ( |
||||
<Header |
||||
title={title} |
||||
image={image} |
||||
description={description} |
||||
imagePosition={imagePosition} |
||||
> |
||||
<OrganizedContent |
||||
id={readAllSection.section.id} |
||||
sections={[ |
||||
readAllSection.section, |
||||
...sections.map(({ section }) => section), |
||||
]} |
||||
pageTitle={title} |
||||
link={Link} |
||||
> |
||||
<readAllSection.Content /> |
||||
</OrganizedContent> |
||||
</Header> |
||||
); |
||||
} |
||||
|
||||
Page.getShapesConfig = getShapesConfig; |
||||
|
||||
return Page; |
||||
} |
@ -0,0 +1,54 @@ |
||||
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote"; |
||||
import React from "react"; |
||||
|
||||
import { createLink, OrganizedContent } from "@/components/OrganizedContent"; |
||||
|
||||
import { Header } from "./Header"; |
||||
import { Options } from "./ReadAll"; |
||||
|
||||
interface Section { |
||||
id: string; |
||||
title: string; |
||||
} |
||||
|
||||
export interface Props { |
||||
content: MDXRemoteSerializeResult; |
||||
sections: Section[]; |
||||
current: number; |
||||
} |
||||
|
||||
export function createSectionPage({ |
||||
title, |
||||
image, |
||||
pagePath, |
||||
getShapesConfig, |
||||
link, |
||||
description, |
||||
imagePosition, |
||||
}: Options) { |
||||
const Link = link ?? createLink(pagePath); |
||||
|
||||
function Page(this: void, { content, sections, current }: Props) { |
||||
return ( |
||||
<Header |
||||
title={title} |
||||
image={image} |
||||
description={description} |
||||
imagePosition={imagePosition} |
||||
> |
||||
<OrganizedContent |
||||
sections={sections} |
||||
id={sections[current].id} |
||||
pageTitle={title} |
||||
link={Link} |
||||
> |
||||
<MDXRemote {...content} /> |
||||
</OrganizedContent> |
||||
</Header> |
||||
); |
||||
} |
||||
|
||||
Page.getShapesConfig = getShapesConfig; |
||||
|
||||
return Page; |
||||
} |
@ -0,0 +1,69 @@ |
||||
import { ParsedUrlQuery } from "querystring"; |
||||
|
||||
import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next"; |
||||
|
||||
import { createReadAllSection } from "@/components/OrganizedContent"; |
||||
import { |
||||
getSectionNamesForPage, |
||||
getSectionsForPage, |
||||
} from "@/lib/organized-content"; |
||||
|
||||
import { Props as ReadAllProps } from "./ReadAll"; |
||||
import { Props as SectionProps } from "./Section"; |
||||
|
||||
export function createReadAllGetStaticProps(pagePath: string) { |
||||
return (async () => { |
||||
const sections = await getSectionsForPage(pagePath); |
||||
|
||||
return { |
||||
props: { |
||||
sections: sections.map(({ name: id, data: { title, content } }) => ({ |
||||
section: { |
||||
id, |
||||
title, |
||||
}, |
||||
content, |
||||
})), |
||||
}, |
||||
}; |
||||
}) as GetStaticProps<ReadAllProps>; |
||||
} |
||||
|
||||
interface SectionParams extends ParsedUrlQuery { |
||||
section: string; |
||||
} |
||||
|
||||
export function createSectionGetStaticProps(pagePath: string) { |
||||
return async function getStaticProps( |
||||
context: GetStaticPropsContext<SectionParams> |
||||
) { |
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentName = context.params!.section; |
||||
const fullSections = await getSectionsForPage(pagePath); |
||||
const current = fullSections.findIndex(({ name }) => name === currentName); |
||||
|
||||
const sections = fullSections.map(({ name, data: { title } }) => ({ |
||||
id: name, |
||||
title, |
||||
})); |
||||
|
||||
return { |
||||
props: { |
||||
content: fullSections[current].data.content, |
||||
current: current + 1, |
||||
sections: [createReadAllSection(sections, false), ...sections], |
||||
}, |
||||
}; |
||||
} as GetStaticProps<SectionProps, SectionParams>; |
||||
} |
||||
|
||||
export function createSectionGetStaticPaths(pagePath: string) { |
||||
return async function getStaticPaths() { |
||||
const names = await getSectionNamesForPage(pagePath); |
||||
|
||||
return { |
||||
paths: names.map((section) => ({ params: { section } })), |
||||
fallback: false, |
||||
}; |
||||
} as GetStaticPaths<SectionParams>; |
||||
} |
@ -0,0 +1,6 @@ |
||||
.pre { |
||||
padding: calc(10rem / 16); |
||||
background: var(--code-background); |
||||
overflow-x: auto; |
||||
border-radius: calc(5rem / 16); |
||||
} |
@ -0,0 +1,9 @@ |
||||
import React, { HTMLAttributes } from "react"; |
||||
|
||||
import styles from "./Pre.module.css"; |
||||
|
||||
export function Pre(props: HTMLAttributes<HTMLPreElement>) { |
||||
const classes = [styles.pre, props.className ?? ""]; |
||||
|
||||
return <pre {...props} className={classes.join(" ")} />; |
||||