From cf4eb8a51baf8214647ead58826823395fbd258c Mon Sep 17 00:00:00 2001 From: Anthony Li Date: Fri, 29 Dec 2023 21:40:29 -0500 Subject: [PATCH] Add navigation --- app/[slug]/page.tsx | 22 ++++--- app/layout.tsx | 18 ++++-- app/menu.tsx | 116 ++++++++++++++++++++++++++++++++++++ components/Card.tsx | 20 +++++++ components/Prose.tsx | 11 ++++ components/StaffGrid.tsx | 62 +++++++++++++++++++ components/mdx.tsx | 6 ++ content/pages/codestyle.mdx | 6 ++ content/pages/index.mdx | 48 ++++++++++++++- content/pages/resources.mdx | 6 ++ content/pages/syllabus.mdx | 1 + contentlayer.config.ts | 2 + package-lock.json | 10 ++++ package.json | 1 + styles/global.css | 7 ++- tailwind.config.js | 1 + 16 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 app/menu.tsx create mode 100644 components/Card.tsx create mode 100644 components/Prose.tsx create mode 100644 components/StaffGrid.tsx create mode 100644 content/pages/codestyle.mdx create mode 100644 content/pages/resources.mdx diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx index 7a0b0bf..0c87834 100644 --- a/app/[slug]/page.tsx +++ b/app/[slug]/page.tsx @@ -2,6 +2,9 @@ import { allPages } from 'contentlayer/generated' import { notFound } from 'next/navigation' import { useMDXComponent } from 'next-contentlayer/hooks' import { mdxComponents } from '@/components/mdx' +import { Card } from '@/components/Card' +import { Prose } from '@/components/Prose' +import { MenuItemActivator } from '@/app/menu' export async function generateStaticParams() { return allPages.map(page => ({ @@ -14,14 +17,17 @@ export default function Page({ params }: { params: { slug: string } }) { if (!page) notFound() const MDXContent = useMDXComponent(page.body.code) + const content = + + if (page.customLayout) return <> + + {content} + - return
-
-

{page.title}

-
-
-
+ return {page.title}}> + + -
-
+ + } \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 13af3d5..8423557 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,6 @@ import "@/styles/global.css" import "prism-themes/themes/prism-atom-dark.css" +import { Menu, MenuContextProvider } from "./menu" export const metadata = { title: 'Next.js', @@ -14,12 +15,19 @@ export default function RootLayout({ return ( -
-

CIS 1951

-
- {children} + +
+

CIS 1951

+
+
+

CIS 1951

+
+
+
+
{children}
+
-
+
) diff --git a/app/menu.tsx b/app/menu.tsx new file mode 100644 index 0000000..5a27cd4 --- /dev/null +++ b/app/menu.tsx @@ -0,0 +1,116 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" +import { ReactNode, createContext, useContext, useEffect, useState } from "react" + +export type MenuItemProps = { + id: string + title: string + icon: string + href: string +} + +export const menuItems: MenuItemProps[] = [ + { + id: "home", + title: "Home", + icon: "🏠", + href: "/", + }, + { + id: "syllabus", + title: "Syllabus", + icon: "📄", + href: "/syllabus", + }, + { + id: "codestyle", + title: "Style Guide", + icon: "💅", + href: "/codestyle", + }, + { + id: "resources", + title: "Resources", + icon: "🧑‍💻", + href: "/resources", + }, +] + +export type MenuCoordinator = { + activeItem: string | null + setActiveItem: (id: string | null) => void +} + +const MenuContext = createContext(null) + +export function MenuContextProvider({ children }: { children: ReactNode }) { + const pathname = usePathname() + const [activeState, setActiveState] = useState<{ item: string | null, pathname: string }>({ item: null, pathname }) + + useEffect(() => { + if (activeState.pathname !== pathname) { + setActiveState({ item: null, pathname }) + } + }, [pathname]) + + return + {children} + +} + +export function useMenuContext(): MenuCoordinator { + const context = useContext(MenuContext) + if (!context) throw new Error("useMenuContext must be called inside a MenuContextProvider") + + return context +} + +export type MenuItemActivatorProps = { + item: string | null +} + +export function MenuItemActivator({ item }: MenuItemActivatorProps) { + const context = useMenuContext() + useEffect(() => { + context.setActiveItem(item) + }, []) + + return null +} + +function MenuItem({ id, title, icon, href }: MenuItemProps) { + const context = useMenuContext() + const isActive = id === context.activeItem + + let className = "block px-3 py-2 rounded-xl group relative bg-opacity-0" + if (isActive) { + className += " bg-gradient-to-br from-cyan-700 to-purple-700 text-white" + } else { + className += " transition-[background-color] active:bg-neutral-200 dark:active:bg-neutral-700 md:dark:active:bg-neutral-800" + } + + let containerClassName = "flex gap-3 transition-[margin-left] justify-center text-center md:text-left" + if (!isActive) containerClassName += " md:group-hover:ml-1" + + return +
+
{icon}
+
{title}
+
+
+
+ +} + +export function Menu() { + return
+ {menuItems.map(item => )} +
+} \ No newline at end of file diff --git a/components/Card.tsx b/components/Card.tsx new file mode 100644 index 0000000..c9abace --- /dev/null +++ b/components/Card.tsx @@ -0,0 +1,20 @@ +import { ReactNode } from "react" + +export type CardProps = { + title?: ReactNode + children: ReactNode + margin?: boolean +} + +export function Card({ title, children, margin = false }: CardProps) { + let className = "rounded-xl p-4 bg-neutral-50 border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" + if (margin) className += " mb-4" + + return
+ {title &&
+ {title} +
+
} + {children} +
+} \ No newline at end of file diff --git a/components/Prose.tsx b/components/Prose.tsx new file mode 100644 index 0000000..83d2406 --- /dev/null +++ b/components/Prose.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from "react" + +export type ProseProps = { + children: ReactNode +} + +export function Prose({ children }: ProseProps) { + return
+ {children} +
+} \ No newline at end of file diff --git a/components/StaffGrid.tsx b/components/StaffGrid.tsx new file mode 100644 index 0000000..a7e4299 --- /dev/null +++ b/components/StaffGrid.tsx @@ -0,0 +1,62 @@ +export type StaffMemberProps = { + name: string + section?: "501" | "502" + flavorText?: string + pennkey: string + school: "sas" | "seas" | "wharton" // Sorry, forgot what Penn Medicine has + pronouns: string + github?: string + website?: string + avatar?: string +} + +function StaffMember({ + name, + flavorText, + section, + pennkey, + school, + pronouns, + github, + website, + avatar, +}: StaffMemberProps) { + const links = [ + github && { + text: "GitHub", + href: `https://github.com/${github}`, + }, + website && { + text: "Website", + href: website, + } + ].filter(link => link) + + return
+
+ {avatar && } +
+
+

+ {name} + {section && ({section})} +

+ {flavorText &&
{flavorText}
} +
Email: {pennkey}@{school}
+
Pronouns: {pronouns}
+ {links.length > 0 &&
+ {links.map(({ text, href }, index) => {text})} +
} +
+
+} + +export type StaffGridProps = { + members: StaffMemberProps[] +} + +export function StaffGrid({ members }: StaffGridProps) { + return
+ {members.map((member, index) => )} +
+} \ No newline at end of file diff --git a/components/mdx.tsx b/components/mdx.tsx index c373d2c..960351e 100644 --- a/components/mdx.tsx +++ b/components/mdx.tsx @@ -1,5 +1,8 @@ import { MDXComponents } from "mdx/types"; import Link from "next/link"; +import { Card } from "./Card"; +import { Prose } from "./Prose"; +import { StaffGrid } from "./StaffGrid"; export const mdxComponents: MDXComponents = { a: ({ href, ...props }) => { @@ -12,4 +15,7 @@ export const mdxComponents: MDXComponents = { return } }, + Card: Card, + Prose: Prose, + StaffGrid: StaffGrid, } \ No newline at end of file diff --git a/content/pages/codestyle.mdx b/content/pages/codestyle.mdx new file mode 100644 index 0000000..9e4dfa9 --- /dev/null +++ b/content/pages/codestyle.mdx @@ -0,0 +1,6 @@ +--- +title: Code Style Guide +activeMenuItem: codestyle +--- + +Make it readable \ No newline at end of file diff --git a/content/pages/index.mdx b/content/pages/index.mdx index 9e302b3..2437128 100644 --- a/content/pages/index.mdx +++ b/content/pages/index.mdx @@ -1,7 +1,51 @@ --- title: Home +activeMenuItem: home +customLayout: true --- -Hello, world! +Welcome} margin> + + *\[insert course description here\]* + + -[Syllabus](/~cis1951/syllabus) \ No newline at end of file +Staff} margin> +

Instructors

+ + +

TAs

+ +
\ No newline at end of file diff --git a/content/pages/resources.mdx b/content/pages/resources.mdx new file mode 100644 index 0000000..fb7819e --- /dev/null +++ b/content/pages/resources.mdx @@ -0,0 +1,6 @@ +--- +title: Resources +activeMenuItem: resources +--- + +They exist \ No newline at end of file diff --git a/content/pages/syllabus.mdx b/content/pages/syllabus.mdx index 1bd18e2..cf3a16c 100644 --- a/content/pages/syllabus.mdx +++ b/content/pages/syllabus.mdx @@ -1,5 +1,6 @@ --- title: Syllabus +activeMenuItem: syllabus --- ## Grading diff --git a/contentlayer.config.ts b/contentlayer.config.ts index a66ad1c..99d9aee 100644 --- a/contentlayer.config.ts +++ b/contentlayer.config.ts @@ -7,6 +7,8 @@ export const Page = defineDocumentType(() => ({ contentType: 'mdx', fields: { title: { type: 'string', required: true }, + activeMenuItem: { type: 'string', required: false }, + customLayout: { type: 'boolean', required: false }, }, computedFields: { slug: { diff --git a/package-lock.json b/package-lock.json index 39c582e..1f5fad9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@next/mdx": "^14.0.4", "@types/mdx": "^2.0.10", "contentlayer": "^0.3.4", + "date-fns": "^3.0.6", "next": "^14.0.4", "next-contentlayer": "^0.3.4", "prism-themes": "^1.9.0" @@ -3521,6 +3522,15 @@ "node": ">= 12" } }, + "node_modules/date-fns": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.6.tgz", + "integrity": "sha512-W+G99rycpKMMF2/YD064b2lE7jJGUe+EjOES7Q8BIGY8sbNdbgcs9XFTZwvzc9Jx1f3k7LB7gZaZa7f8Agzljg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 96a966e..f8c4ce8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@next/mdx": "^14.0.4", "@types/mdx": "^2.0.10", "contentlayer": "^0.3.4", + "date-fns": "^3.0.6", "next": "^14.0.4", "next-contentlayer": "^0.3.4", "prism-themes": "^1.9.0" diff --git a/styles/global.css b/styles/global.css index 6e06e04..d89cfeb 100644 --- a/styles/global.css +++ b/styles/global.css @@ -7,6 +7,9 @@ body { @apply dark:bg-black dark:text-white; } -.card { - @apply rounded-xl p-4 bg-neutral-50 border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800; +.prose a, .link { + @apply text-cyan-500 dark:text-cyan-400 underline; + @apply decoration-cyan-500/50 dark:decoration-cyan-400/50; + @apply hover:decoration-cyan-500 dark:hover:decoration-cyan-400; + @apply hover:bg-cyan-500/20 dark:hover:bg-cyan-400/20; } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index e54ef14..9d4e8b8 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,6 +2,7 @@ module.exports = { content: [ "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./content/**/*.mdx", "./components/**/*.{js,ts,jsx,tsx,mdx}", ], theme: {