Skip to content

Commit afda870

Browse files
committed
feat: implement responsive layout and project details drawer
- Added a drawer component for displaying project details on smaller screens. - Implemented responsive layout changes for different screen sizes. - Refactored project list and detail components for better organization. - Updated global styles and added background noise utility. - Added animation to GameOfLife component.
1 parent fc64f69 commit afda870

File tree

12 files changed

+131
-41
lines changed

12 files changed

+131
-41
lines changed

src/app/@aside/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { GameOfLife } from "@/components/gameoflife";
44
import { ProjectDetail } from "@/components/project";
55
import useProject from "@/hooks/use-project";
66

7-
const Page = () => {
7+
const Aside = () => {
88
const { selectedProject } = useProject();
99

1010
return (
11-
<aside className="@lg:w-1/3 h-svh sticky top-0">
11+
<aside className="hidden lg:block w-1/3 h-svh sticky top-0">
1212
{selectedProject ? (
1313
<ProjectDetail project={selectedProject} />
1414
) : (
@@ -18,4 +18,4 @@ const Page = () => {
1818
);
1919
};
2020

21-
export default Page;
21+
export default Aside;

src/app/globals.css

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222
}
2323
}
2424

25-
html {
26-
@apply antialiased bg-background text-primary leading-none font-mono **:border-border;
27-
@apply before:fixed before:top-0 before:left-0 before:size-full before:bg-[url(/noise.webp)] before:bg-repeat before:opacity-40 before:-z-50;
25+
body {
26+
@apply antialiased bg-background text-primary leading-none font-mono **:border-border bg-noise;
2827
}
2928
}
3029

@@ -38,3 +37,7 @@
3837
--font-sans: var(--font-pp-neue-montreal);
3938
--font-mono: var(--font-pp-neue-montreal-mono);
4039
}
40+
41+
@utility bg-noise {
42+
@apply before:fixed before:top-0 before:left-0 before:size-full before:bg-[url(/noise.webp)] before:bg-repeat before:opacity-40 before:-z-50;
43+
}

src/app/layout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ReactNode } from "react";
55
import { ppNeueMontreal, ppNeueMontrealMono } from "../lib/fonts";
66
import "./globals.css";
77
import { Footer } from "@/components/footer";
8+
import Drawer from "@/components/drawer/drawer";
89

910
export const metadata: Metadata = {
1011
title: "Taehoon Lee",
@@ -25,10 +26,11 @@ const Layout = ({ children, aside }: LayoutProps) => {
2526
<body>
2627
<HighlighterProvider>
2728
<ProjectContextProvider>
28-
<div className="container @container min-h-svh mx-auto flex border-x">
29+
<div className="container @container min-h-svh mx-auto flex border-x overflow-x-hidden">
2930
{children}
3031
{aside}
3132
</div>
33+
<Drawer />
3234
<Footer />
3335
</ProjectContextProvider>
3436
</HighlighterProvider>

src/app/page.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { ProjectListItem } from "@/components/project";
2-
import { projects } from "@/lib/data";
1+
import { ProjectList } from "@/components/project";
32

43
const Page = () => {
54
return (
6-
<main className="@lg:w-2/3 @lg:pt-40 border-r space-y-10">
7-
<h1 className="text-7xl mb-5 font-bold tracking-tight px-4 font-sans">
5+
<main className="pt-20 lg:pt-40 basis-full lg:basis-2/3 lg:border-r space-y-10">
6+
<h1 className="text-4xl lg:text-7xl mb-5 font-bold tracking-tight px-4 font-sans">
87
TAEHOON LEE
98
</h1>
109
<p className="px-4 font-sans text-secondary text-lg leading-snug">
@@ -13,12 +12,7 @@ const Page = () => {
1312
building cool CLIs in Go, making websites, or writing helix editor
1413
configs.
1514
</p>
16-
<div className="flex flex-col">
17-
<div className="text-secondary text-sm mb-2 px-4">PROJECTS</div>
18-
{projects.map((p, i) => (
19-
<ProjectListItem key={i} project={p} />
20-
))}
21-
</div>
15+
<ProjectList />
2216
</main>
2317
);
2418
};

src/components/drawer/drawer.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use client";
2+
3+
import useProject from "@/hooks/use-project";
4+
import { ProjectDetail } from "../project";
5+
import { useEffect, useRef, useState } from "react";
6+
import { motion } from "motion/react";
7+
8+
const Drawer = () => {
9+
const { selectedProject } = useProject();
10+
const ref = useRef<HTMLDivElement>(null);
11+
const isOpen = !!selectedProject;
12+
13+
return (
14+
<motion.div
15+
ref={ref}
16+
initial={{
17+
right: "-80svw",
18+
}}
19+
animate={
20+
isOpen
21+
? {
22+
right: 0,
23+
}
24+
: {
25+
right: "-80svw",
26+
}
27+
}
28+
transition={{
29+
duration: 0.3,
30+
ease: "circOut",
31+
}}
32+
className="fixed block lg:hidden top-0 h-svh w-[80svw] bg-background/90 bg-noise border-l backdrop-blur-2xl"
33+
>
34+
{selectedProject && <ProjectDetail project={selectedProject} />}
35+
</motion.div>
36+
);
37+
};
38+
39+
export default Drawer;

src/components/footer/footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const Footer = () => {
44
const year = new Date().getFullYear();
55

66
return (
7-
<footer className="h-20 border-t">
7+
<footer className="h-20 px-4 border-t">
88
<div className="container mx-auto mt-4 font-sans text-secondary flex justify-between">
99
<span>© {year} Taehoon Lee</span>
1010
<div className="*:text-secondary *:hover:text-primary *:transition-all flex gap-4">

src/components/gameoflife/gameoflife.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { memo, useEffect, useRef, useState } from "react";
4+
import { motion } from "motion/react";
45

56
const FPS = 8;
67
const INITIAL_LIFE_CHANCE = 0.15;
@@ -120,9 +121,15 @@ const GameOfLife = memo(() => {
120121
}, [matrix]);
121122

122123
return (
123-
<div ref={containerRef} className="size-full">
124+
<motion.div
125+
ref={containerRef}
126+
initial={{ opacity: 0 }}
127+
animate={{ opacity: 1 }}
128+
transition={{ delay: 0.3, ease: "easeOut" }}
129+
className="size-full"
130+
>
124131
<canvas ref={canvasRef} />
125-
</div>
132+
</motion.div>
126133
);
127134
});
128135
GameOfLife.displayName = "GameOfLife";

src/components/project/detail.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type ProjectDetailProps = {
1010

1111
const ProjectDetail = ({ project }: ProjectDetailProps) => {
1212
return (
13-
<div className="flex flex-col h-full justify-between gap-10 py-40">
13+
<div className="flex flex-col size-full justify-between gap-10 py-20 lg:py-40">
1414
<div className="px-4">
1515
<h2 className="text-xl font-sans mb-1 uppercase">{project.title}</h2>
1616
{project.description.map((d, i) => (

src/components/project/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as ProjectDetail } from "./detail";
22
export { default as ProjectListItem } from "./list-item";
3+
export { default as ProjectList } from "./list";

src/components/project/list-item.tsx

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import useProject from "@/hooks/use-project";
44
import { Project } from "@/lib/data";
5-
import { motion } from "motion/react";
65
import { memo } from "react";
76
import { Highlighter } from "../highlighter";
7+
import { motion, type Variants } from "motion/react";
88

99
type ProjectListItemProps = {
1010
project: Project;
@@ -14,26 +14,42 @@ const ProjectListItem = memo(({ project }: ProjectListItemProps) => {
1414
const { selectedProject, setSelectedProject } = useProject();
1515
const isSelected = selectedProject === project;
1616

17+
const itemVariant: Variants = {
18+
hidden: { opacity: 0 },
19+
show: {
20+
opacity: 1,
21+
transition: {
22+
duration: 0.5,
23+
ease: "easeOut",
24+
},
25+
},
26+
};
27+
28+
const borderVariant: Variants = {
29+
hidden: { width: "0%" },
30+
show: {
31+
width: "100%",
32+
transition: {
33+
duration: 0.5,
34+
ease: "easeOut",
35+
},
36+
},
37+
};
38+
1739
return (
1840
<Highlighter>
1941
<motion.div
20-
animate={
21-
isSelected
22-
? {
23-
background: "var(--color-primary)",
24-
color: "var(--color-background)",
25-
}
26-
: {}
27-
}
28-
transition={{
29-
duration: 0.3,
30-
ease: "circOut",
31-
}}
42+
variants={itemVariant}
3243
onClick={() => setSelectedProject(isSelected ? null : project)}
33-
className="h-18 flex flex-col gap-2 justify-center border-t px-4 cursor-pointer"
44+
data-selected={isSelected}
45+
className="space-y-2 px-4 py-3 overflow-hidden relative cursor-pointer data-[selected=true]:bg-primary data-[selected=true]:text-background transition-all"
3446
>
47+
<motion.div
48+
variants={borderVariant}
49+
className="h-[1px] absolute w-full top-0 left-0 bg-border"
50+
/>
3551
<p className="uppercase">{project.title}</p>
36-
<p className="text-secondary font-sans tracking-wide">
52+
<p className="text-secondary font-sans leading-tight">
3753
{project.description[0]}
3854
</p>
3955
</motion.div>

0 commit comments

Comments
 (0)