"use client"; import { PROJECTS } from "@/data/content"; import { useStaggerReveal } from "@/hooks/useAnimations"; import React, { useState, useCallback, useRef } from "react"; import ProjectCard from "@/components/ProjectCard"; import { Layout, Responsive, useContainerWidth, horizontalCompactor, DefaultBreakpoints, } from "react-grid-layout"; import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; export default function Projects() { const rowHeight = 100; const visible = useStaggerReveal(PROJECTS.length, 100); const [selectedProject, setSelectedProject] = useState(1); const { width, containerRef, mounted } = useContainerWidth(); const [layouts, setLayouts] = useState>( () => buildLayouts(1, {}) ); const heightCache = useRef>({}); function buildLayouts( activeId: number, heightOverrides: Record ): Record { const isSelected = (id: number) => id === activeId; function h(project: (typeof PROJECTS)[number]) { const override = heightOverrides[project.id.toString()]; if (isSelected(project.id) && override != null) return override; if(!isSelected(project.id)) return 1; return project.images?.length ? 2 : 1; } const selectedProject = PROJECTS.find((p) => isSelected(p.id)); const selectedH = selectedProject ? h(selectedProject) : 1; const nonSelected = PROJECTS.filter((p) => !isSelected(p.id)); function twoCol(project: (typeof PROJECTS)[number]) { if (isSelected(project.id)) { return { i: project.id.toString(), x: 0, y: 0, w: 2, h: h(project) }; } const ni = nonSelected.indexOf(project); return { i: project.id.toString(), x: ni % 2, y: Math.floor(ni / 2) + selectedH, w: 1, h: 3, }; } function singleCol(project: (typeof PROJECTS)[number], colSpan: number) { if (isSelected(project.id)) { return { i: project.id.toString(), x: 0, y: 0, w: colSpan, h: h(project) }; } const ni = nonSelected.indexOf(project); return { i: project.id.toString(), x: 0, y: ni + selectedH, w: colSpan, h: project.images?.length ? 4 : 2, }; } return { lg: PROJECTS.map((p) => twoCol(p)), md: PROJECTS.map((p) => twoCol(p)), sm: PROJECTS.map((p) => twoCol(p)), xs: PROJECTS.map((p) => singleCol(p, 4)), xxs: PROJECTS.map((p) => singleCol(p, 4)), }; } const selectedRef = useRef(selectedProject); selectedRef.current = selectedProject; const pendingUpdate = useRef | null>(null); const updateHeight = useCallback( (sourceId: number, contentRef: HTMLElement | null) => { if (sourceId !== selectedRef.current) return; if (pendingUpdate.current) clearTimeout(pendingUpdate.current); pendingUpdate.current = setTimeout(() => { const newH = contentRef ? Math.ceil(contentRef.getBoundingClientRect().height / rowHeight) : 2; const key = sourceId.toString(); if (heightCache.current[key] === newH) return; heightCache.current[key] = newH; setLayouts((prev) => { const next = { ...prev }; for (const bp in next) { next[bp as DefaultBreakpoints] = next[bp as DefaultBreakpoints].map( (layout) => layout.i === key ? { ...layout, h: newH } : layout ); } return next; }); }, 10); }, [rowHeight] ); function setSelectedHandler(projectId: number) { const id = Number(projectId); setSelectedProject(id); heightCache.current = {}; setLayouts(buildLayouts(id, {})); } return (
{"# projects"}

{"Things I've built"}

{mounted && ( {PROJECTS.map((project, i) => (
))}
)}
); }