100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import { type RefObject, useEffect, useRef, useState } from "react";
|
|
import type { PortfolioProjectView } from "@/lib/portfolio-data";
|
|
import { secondaryButtonDark } from "./button-styles";
|
|
import HoverPrefetchLink from "./HoverPrefetchLink";
|
|
import { Section } from "./Section";
|
|
import { ProjectReel } from "./showcase/ProjectReel";
|
|
import { ShowcaseProvider } from "./showcase/ShowcaseProvider";
|
|
|
|
/**
|
|
* Quanto "Ver os cases" anda para a direita para ficar na coluna da legenda e dos botões da vitrine. Na vitrine fixa
|
|
* (a partir de 1024 px) o conjunto de aparelhos, com a legenda e os botões no canto esquerdo dele, fica centrado no
|
|
* palco, e esse canto muda com a altura da janela (o palco mede a si mesmo): só medindo dá para alinhar o link a ele.
|
|
* Sem vitrine fixa (celular, movimento reduzido, janela baixa) não há conjunto e o link fica no começo da coluna.
|
|
*/
|
|
function useReelColumnOffset(exitRef: RefObject<HTMLElement | null>) {
|
|
const [offset, setOffset] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const exit = exitRef.current;
|
|
const section = exit?.closest("section");
|
|
if (!exit || !section) return;
|
|
let observedSet: Element | null = null;
|
|
|
|
const refresh = () => {
|
|
const set = section.querySelector(".ds-reel-set");
|
|
if (set !== observedSet) {
|
|
if (observedSet) resizes?.unobserve(observedSet);
|
|
if (set) resizes?.observe(set);
|
|
observedSet = set;
|
|
}
|
|
// o padding do próprio bloco não muda o `left` dele: a medida é só a distância até o canto do conjunto
|
|
setOffset(
|
|
set
|
|
? Math.max(
|
|
0,
|
|
Math.round(
|
|
set.getBoundingClientRect().left -
|
|
exit.getBoundingClientRect().left,
|
|
),
|
|
)
|
|
: 0,
|
|
);
|
|
};
|
|
const resizes =
|
|
typeof ResizeObserver === "undefined"
|
|
? undefined
|
|
: new ResizeObserver(refresh);
|
|
|
|
refresh();
|
|
resizes?.observe(section);
|
|
// a vitrine fixa chega depois (pacote sob demanda) e sai quando a janela deixa de comportá-la
|
|
const mutations = new MutationObserver(refresh);
|
|
mutations.observe(section, { childList: true, subtree: true });
|
|
return () => {
|
|
resizes?.disconnect();
|
|
mutations.disconnect();
|
|
};
|
|
}, [exitRef]);
|
|
|
|
return offset;
|
|
}
|
|
|
|
/** Vitrine única da home: recebe só projetos de cliente (o filtro vem de app/page.tsx). */
|
|
export default function Projects({
|
|
projects,
|
|
}: {
|
|
projects: PortfolioProjectView[];
|
|
}) {
|
|
const exitRef = useRef<HTMLDivElement>(null);
|
|
const offset = useReelColumnOffset(exitRef);
|
|
|
|
return (
|
|
<Section
|
|
id="projects"
|
|
eyebrow="Projetos"
|
|
title="Veja o que já saiu do papel."
|
|
description="Projetos feitos para empresas. Abra um case para entender o problema, a solução e as decisões por trás da interface."
|
|
dark
|
|
disableChildrenAnimation
|
|
>
|
|
<ShowcaseProvider projects={projects}>
|
|
<ProjectReel />
|
|
</ShowcaseProvider>
|
|
|
|
<div
|
|
ref={exitRef}
|
|
className="mt-sp-8"
|
|
data-reel-stagger="left"
|
|
style={offset ? { paddingLeft: offset } : undefined}
|
|
>
|
|
<HoverPrefetchLink href="/cases" className={secondaryButtonDark}>
|
|
Ver os cases
|
|
</HoverPrefetchLink>
|
|
</div>
|
|
</Section>
|
|
);
|
|
}
|