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

+62 -2
View File
@@ -1,6 +1,7 @@
"use client";
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import type { NotebookProject } from "./notebookProjects";
import type { NotebookFrame } from "./notebookTimeline";
@@ -12,6 +13,7 @@ export type NotebookBrowserProps = {
"cursorVisible" | "cursorX" | "cursorY" | "cursorClicking"
>;
onSelectTab: (index: number) => void;
interactive?: boolean;
};
function clampIndex(index: number, length: number): number {
@@ -28,10 +30,35 @@ export default function NotebookBrowser({
activeIndex,
frame,
onSelectTab,
interactive = true,
}: NotebookBrowserProps) {
const selectedIndex = clampIndex(activeIndex, projects.length);
const selectedProject =
selectedIndex >= 0 ? projects[selectedIndex] : undefined;
const previousIndexRef = useRef(selectedIndex);
const [outgoingIndex, setOutgoingIndex] = useState<number | null>(null);
useEffect(() => {
const previousIndex = previousIndexRef.current;
if (previousIndex === selectedIndex) return undefined;
previousIndexRef.current = selectedIndex;
setOutgoingIndex(previousIndex >= 0 ? previousIndex : null);
const timeout = window.setTimeout(() => setOutgoingIndex(null), 180);
return () => window.clearTimeout(timeout);
}, [selectedIndex]);
const outgoingProject =
outgoingIndex !== null ? projects[outgoingIndex] : undefined;
const nextProject =
selectedIndex >= 0 && projects.length > 1
? projects[(selectedIndex + 1) % projects.length]
: undefined;
const interactionProps = interactive
? {}
: {
"aria-hidden": true,
inert: true,
};
function handleTabKeyDown(
event: React.KeyboardEvent<HTMLButtonElement>,
@@ -52,12 +79,16 @@ export default function NotebookBrowser({
if (nextIndex === null) return;
event.preventDefault();
event.currentTarget.parentElement
?.querySelectorAll<HTMLButtonElement>('[role="tab"]')
[nextIndex]?.focus();
onSelectTab(nextIndex);
}
if (!selectedProject) {
return (
<div
{...interactionProps}
className="notebook-browser"
data-testid="notebook-browser"
data-empty
@@ -71,7 +102,11 @@ export default function NotebookBrowser({
const panelId = `notebook-panel-${selectedId}`;
return (
<div className="notebook-browser" data-testid="notebook-browser">
<div
{...interactionProps}
className="notebook-browser"
data-testid="notebook-browser"
>
<div className="notebook-browser-bar" aria-hidden="true">
<span className="notebook-browser-dot" />
<span className="notebook-browser-dot" />
@@ -89,6 +124,9 @@ export default function NotebookBrowser({
{String(projects.length).padStart(2, "0")}
</span>
</div>
<output className="sr-only" aria-live="polite">
Projeto ativo: {selectedProject.title}
</output>
<div
className="notebook-tablist"
@@ -145,14 +183,36 @@ export default function NotebookBrowser({
</div>
<div className="notebook-screenshot-wrap">
{outgoingProject ? (
<Image
alt=""
aria-hidden="true"
className="notebook-screenshot notebook-screenshot-outgoing"
fill
sizes="(min-width: 681px) 60vw, 100vw"
src={outgoingProject.screenshotSrc}
unoptimized
/>
) : null}
<Image
alt={`Captura de tela de ${selectedProject.title}`}
className="notebook-screenshot"
className="notebook-screenshot notebook-screenshot-current"
fill
sizes="(min-width: 681px) 60vw, 100vw"
src={selectedProject.screenshotSrc}
unoptimized
/>
{nextProject ? (
<Image
alt=""
aria-hidden="true"
className="notebook-screenshot-preload"
fill
sizes="1px"
src={nextProject.screenshotSrc}
unoptimized
/>
) : null}
{frame.cursorVisible ? (
<span
aria-hidden="true"