Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 106 additions & 26 deletions app/components/home/portfolio.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,59 @@
import React from "react";
import Album from '@/app/Media/Photos/Fox Album Cover Alt2.jpg';
import Ript from '@/app/Media/Photos/Ript Portfolio Icon.png';
import Dansbands from '@/app/Media/Photos/dansbands icon.png';
"use client";

import Link from "next/link";
import Image from "next/image";
import React, { useEffect, useRef, useState } from "react";
import { portfolioItems } from "@/app/util/const";

const CARD_GAP = 24;

const getPrimaryHref = (caseStudyUrl?: string) => caseStudyUrl ?? "/portfolio";

const Portfolio = () => {
const trackRef = useRef<HTMLDivElement>(null);
const [canScrollPrev, setCanScrollPrev] = useState(false);
const [canScrollNext, setCanScrollNext] = useState(true);

useEffect(() => {
const track = trackRef.current;

if (!track) {
return;
}

const updateScrollState = () => {
const maxScrollLeft = track.scrollWidth - track.clientWidth;
setCanScrollPrev(track.scrollLeft > 4);
setCanScrollNext(track.scrollLeft < maxScrollLeft - 4);
};

updateScrollState();
track.addEventListener("scroll", updateScrollState, { passive: true });
window.addEventListener("resize", updateScrollState);

return () => {
track.removeEventListener("scroll", updateScrollState);
window.removeEventListener("resize", updateScrollState);
};
}, []);

const scrollCards = (direction: "prev" | "next") => {
const track = trackRef.current;

if (!track) {
return;
}

const firstCard = track.querySelector<HTMLElement>(".portfolio-card");
const cardWidth = firstCard?.getBoundingClientRect().width ?? 320;
const distance = cardWidth + CARD_GAP;

track.scrollBy({
left: direction === "next" ? distance : -distance,
behavior: "smooth",
});
};

return (
<>
<div id="recent-work" className="smooth"></div>
Expand All @@ -17,28 +65,60 @@ const Portfolio = () => {
<h2 className="subtitle">
SELECTED PROJECTS. <a href="/portfolio">{"SEE MORE >"}</a>
</h2>
<div className="row">
<Image
alt="Fox album artwork"
src={Album}
width={300}
height={300}
sizes="(max-width: 767px) 200px, (max-width: 1199px) 200px, 300px"
/>
<Image
alt="Ript project cover"
src={Ript}
width={300}
height={300}
sizes="(max-width: 767px) 200px, (max-width: 1199px) 200px, 300px"
/>
<Image
alt="dansbands project cover"
src={Dansbands}
width={300}
height={300}
sizes="(max-width: 767px) 200px, (max-width: 1199px) 200px, 300px"
/>
<div className="portfolio-carousel-shell">
<div className="portfolio-carousel-copy">
<p>
The homepage now pulls directly from the same portfolio dataset,
so project imagery stays in sync with the full Selected Work page.
</p>
<div className="portfolio-carousel-controls" aria-hidden="true">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove aria-hidden from interactive carousel controls

aria-hidden="true" on the controls container removes its subtree from the accessibility tree while it still contains clickable <button> elements. Screen-reader users won’t be able to discover/use these desktop carousel controls, and focusable elements inside an aria-hidden subtree are an accessibility violation. The container should not be aria-hidden if the buttons are intended to be interactive.

Useful? React with 👍 / 👎.

<button
type="button"
className="portfolio-carousel-button"
onClick={() => scrollCards("prev")}
disabled={!canScrollPrev}
aria-label="Scroll recent work backward"
>
</button>
<button
type="button"
className="portfolio-carousel-button"
onClick={() => scrollCards("next")}
disabled={!canScrollNext}
aria-label="Scroll recent work forward"
>
</button>
</div>
</div>
<div className="portfolio-track" ref={trackRef}>
{portfolioItems.map((item) => (
<article className="portfolio-card" key={item.title}>
<div className="portfolio-card-media">
<Image
alt={`${item.title} project screenshot`}
src={item.image}
fill
sizes="(max-width: 767px) 78vw, (max-width: 1199px) 44vw, 340px"
/>
</div>
<div className="portfolio-card-body">
<div className="portfolio-card-meta">
<span>{item.section === "professional" ? "CASE STUDY" : "PROJECT"}</span>
<span>{item.date}</span>
</div>
<h3>{item.title}</h3>
<p>{item.subtitle}</p>
<div className="portfolio-card-actions">
<Link href={getPrimaryHref(item.caseStudyUrl)}>
{item.caseStudyUrl ? "Open Case Study" : "View Portfolio"}
</Link>
</div>
</div>
</article>
))}
</div>
</div>
</div>
</div>
Expand Down
Loading