fix(story): harden notebook interaction and fallback

This commit is contained in:
Evandro Fonseca Junior committed 2026-08-31 01:28:43 -03:00
1 parent bb4bf8e7e2
commit f3ed9da777
12 files changed
+344 -57

No files matched your search

+44 -3
View File
@@ -1,6 +1,7 @@
"use client";
import dynamic from "next/dynamic";
import type { CSSProperties } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { PortfolioProject } from "@/lib/portfolio-data";
import NotebookFallback from "./NotebookFallback";
@@ -30,6 +31,14 @@ export function canUseNotebook3D(view: CapabilityWindow): boolean {
);
}
export function getNotebookRunwayVh(tabCount: number): number {
const count = Math.max(
1,
Number.isFinite(tabCount) ? Math.floor(tabCount) : 1,
);
return 360 + count * 40;
}
export function getNotebookSeekProgress(
index: number,
tabCount: number,
@@ -58,7 +67,28 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
const [isVisible, setIsVisible] = useState(true);
const [activeIndex, setActiveIndex] = useState(activeIndexRef.current);
useEffect(() => {
setEngine(notebookProjects.length > 0 && canUseNotebook3D(window));
const updateEngine = () => {
setEngine(notebookProjects.length > 0 && canUseNotebook3D(window));
};
updateEngine();
if (typeof window.matchMedia !== "function") return undefined;
const mediaQueries = [
window.matchMedia("(prefers-reduced-motion: reduce)"),
window.matchMedia("(pointer: coarse)"),
window.matchMedia("(hover: hover)"),
];
window.addEventListener("resize", updateEngine, { passive: true });
mediaQueries.forEach((mediaQuery) => {
mediaQuery.addEventListener("change", updateEngine);
});
return () => {
window.removeEventListener("resize", updateEngine);
mediaQueries.forEach((mediaQuery) => {
mediaQuery.removeEventListener("change", updateEngine);
});
};
}, [notebookProjects.length]);
useEffect(() => {
@@ -127,6 +157,10 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
};
}, [engine, notebookProjects.length]);
const handleStageError = useCallback(() => {
setEngine(false);
}, []);
const seekToTab = useCallback(
(index: number) => {
const section = sectionRef.current;
@@ -161,7 +195,7 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
if (!engine) {
return (
<section
aria-label="Projetos em produção"
aria-label="Projetos em produção no notebook"
className="notebook-story notebook-story-static"
data-testid="notebook-story"
id="story"
@@ -183,12 +217,18 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
return (
<section
aria-label="Apresentação em scroll contínuo"
aria-label="Projetos em produção no notebook"
className="notebook-story notebook-story-runway"
data-notebook-engine="3d"
data-notebook-runway-vh={getNotebookRunwayVh(notebookProjects.length)}
data-testid="notebook-story"
id="story"
ref={sectionRef}
style={
{
"--notebook-runway": `${getNotebookRunwayVh(notebookProjects.length)}vh`,
} as CSSProperties
}
>
<div className="notebook-story-sticky">
<div className="notebook-story-grid">
@@ -208,6 +248,7 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
activeIndex={activeIndex}
isVisible={isVisible}
onSelectTab={handleSelectTab}
onStageError={handleStageError}
progress={progressRef}
projects={notebookProjects}
/>