150 lines
4.7 KiB
TypeScript
150 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import { PROJECTS } from "@/data/content";
|
|
import { useStaggerReveal } from "@/hooks/useAnimations";
|
|
import React, { useState, useCallback } from 'react';
|
|
|
|
import ProjectCard from "@/components/ProjectCard";
|
|
|
|
import { Layout, Responsive, useContainerWidth, horizontalCompactor, verticalCompactor, DefaultBreakpoints } from "react-grid-layout";
|
|
|
|
import "react-grid-layout/css/styles.css";
|
|
import "react-resizable/css/styles.css";
|
|
|
|
export default function Projects() {
|
|
|
|
const rowHeight = 250;
|
|
const defaultExpandedHeightUnits = 2;
|
|
const visible = useStaggerReveal(PROJECTS.length, 100);
|
|
const [selectedProject, setSelectedProject] = useState<number>(1);
|
|
|
|
const { width, containerRef, mounted } = useContainerWidth();
|
|
|
|
const [topCellHeight, setTopCellHeight] = useState(defaultExpandedHeightUnits);
|
|
const [layouts, setLayouts] = useState<Record<DefaultBreakpoints, Layout>>(updateLayout(1));
|
|
|
|
|
|
function setExpandedHeight(height: number) {
|
|
const gridUnits = height === 0 ? defaultExpandedHeightUnits : Math.ceil(height / rowHeight);
|
|
setTopCellHeight(gridUnits);
|
|
setLayouts(updateLayout(selectedProject, gridUnits)); // pass both
|
|
}
|
|
|
|
function setSelectedHandler(projectId: number) {
|
|
const id = Number(projectId);
|
|
setSelectedProject(id);
|
|
setLayouts(updateLayout(id, topCellHeight)); // pass both
|
|
}
|
|
|
|
|
|
function updateLayout(activeId: number, cellHeight?: number) : Record<DefaultBreakpoints, Layout> {
|
|
const currentSelected = activeId ?? selectedProject;
|
|
const currentCellHeight = cellHeight ?? topCellHeight;
|
|
|
|
let isSelected = (projectId: number) => projectId === currentSelected;
|
|
return {
|
|
"sm": PROJECTS.map((project, i) => {
|
|
return {
|
|
i: project.id.toString(),
|
|
x: isSelected(project.id) ? 0 : (i % 2) + 1,
|
|
y: isSelected(project.id) ? 0 : Math.floor(i / 2) + (!isSelected(project.id) ? 1 : 0),
|
|
w: isSelected(project.id) ? 2 : 1,
|
|
h: project.images && project.images.length > 0 ? currentCellHeight : 1
|
|
};
|
|
}),
|
|
"xs": PROJECTS.map((project, i) => {
|
|
return {
|
|
i: project.id.toString(),
|
|
x: 0,
|
|
y: i,
|
|
w: 2,
|
|
h: project.images && project.images.length > 0 ? currentCellHeight : 1
|
|
};
|
|
}),
|
|
"xxs": PROJECTS.map((project, i) => {
|
|
return {
|
|
i: project.id.toString(),
|
|
x: 0,
|
|
y: i,
|
|
w: 2,
|
|
h: project.images && project.images.length > 0 ? currentCellHeight : 1
|
|
};
|
|
}),
|
|
"md": PROJECTS.map((project, i) => {
|
|
return {
|
|
i: project.id.toString(),
|
|
x: isSelected(project.id) ? 0 : (i % 2) + 1,
|
|
y: isSelected(project.id) ? 0 : Math.floor(i / 2) + (!isSelected(project.id) ? 1 : 0),
|
|
w: isSelected(project.id) ? 2 : 1,
|
|
h: project.images && project.images.length > 0 ? currentCellHeight : 1
|
|
};
|
|
}),
|
|
"lg": PROJECTS.map((project, i) => {
|
|
return {
|
|
i: project.id.toString(),
|
|
x: isSelected(project.id) ? 0 : (i % 2) + 1,
|
|
y: isSelected(project.id) ? 0 : Math.floor(i / 2) + (!isSelected(project.id) ? 1 : 0),
|
|
w: isSelected(project.id) ? 2 : 1,
|
|
h: project.images && project.images.length > 0 ? currentCellHeight : 1
|
|
};
|
|
}),
|
|
};
|
|
};
|
|
|
|
return (
|
|
<div style={{ padding: "48px 0"}}>
|
|
<div
|
|
style={{
|
|
marginBottom: 8,
|
|
fontFamily: "var(--mono)",
|
|
fontSize: 11,
|
|
color: "var(--muted)",
|
|
letterSpacing: "0.08em",
|
|
textTransform: "uppercase",
|
|
}}
|
|
>
|
|
{"# projects"}
|
|
</div>
|
|
<h2
|
|
style={{
|
|
fontFamily: "var(--sans)",
|
|
fontSize: 28,
|
|
fontWeight: 700,
|
|
color: "var(--fg)",
|
|
margin: "0 0 32px 0",
|
|
letterSpacing: "-0.01em",
|
|
}}
|
|
>
|
|
{"Things I've built"}
|
|
</h2>
|
|
|
|
<div ref={containerRef}>
|
|
{mounted && (<Responsive
|
|
layouts={layouts}
|
|
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
|
cols={{ lg: 2, md: 2, sm: 2, xs: 2, xxs: 2 }}
|
|
width={width}
|
|
compactor={horizontalCompactor}
|
|
rowHeight={rowHeight}
|
|
dragConfig={{enabled:false}}
|
|
>
|
|
{PROJECTS.map((project, i) => (
|
|
<div key={project.id} style={{pointerEvents: "none"}}>
|
|
<ProjectCard
|
|
project={project}
|
|
visible={visible.has(i)}
|
|
selected={selectedProject === project.id}
|
|
setSelected={setSelectedHandler}
|
|
setExpandedHeight={setExpandedHeight}
|
|
/>
|
|
</div>
|
|
|
|
))}
|
|
</Responsive>)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|