fix(story): harden notebook interaction and fallback
This commit is contained in:
1 parent
bb4bf8e7e2
commit
f3ed9da777
12 files changed
+344
-57
No files matched your search
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user