Add guided onboarding tour for first toolbox creation
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s

This commit is contained in:
Shinuwa 2026-08-14 13:20:47 +02:00
parent 3007aa1389
commit 4f73e0068b
20 changed files with 470 additions and 20 deletions

View file

@ -1,5 +1,6 @@
// Rôle : affiche les pages toolbox, le panneau latéral et leurs contrôles.
import { useEffect, useRef, useState } from "react";
import { GuidedTooltipTour } from "../../components/GuidedTooltipTour.jsx";
import { Icon } from "../../components/Icon.jsx";
import { ImportButton } from "../../components/ImportButton.jsx";
import { StorageQuota } from "../../components/StorageQuota.jsx";
@ -169,10 +170,18 @@ export function ToolboxPage(props) {
return <ToolboxView {...props} toolbox={toolbox} toolboxGame={getToolboxGame(toolbox)} embedded={false} />;
}
function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, storageUsage, getModuleData, updateToolbox, updateModuleData, addImageFiles }) {
function shouldShowTutorial(toolbox, pendingTutorialToolboxId) {
return toolbox?.tutorial === true || pendingTutorialToolboxId === toolbox?.id;
}
function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, storageUsage, getModuleData, updateToolbox, updateModuleData, addImageFiles, pendingTutorialToolboxId = "", setPendingTutorialToolboxId }) {
const toolboxId = toolbox.id;
const toolboxTutorial = toolbox.tutorial;
const moduleColumns = embedded || Number(toolbox.moduleColumns) === 1 ? 1 : 2;
const toolboxGameCover = getGameCardCover(toolboxGame);
const moduleText = siteContent.toolboxes.modules;
const [showTutorial, setShowTutorial] = useState(() => shouldShowTutorial(toolbox, pendingTutorialToolboxId));
const currentToolboxIdRef = useRef(toolboxId);
const moduleContext = {
getModuleData,
moduleText,
@ -202,6 +211,15 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
refresh: () => {}
};
useEffect(() => {
if (currentToolboxIdRef.current === toolboxId) {
if (shouldShowTutorial(toolbox, pendingTutorialToolboxId)) setShowTutorial(true);
return;
}
currentToolboxIdRef.current = toolboxId;
setShowTutorial(shouldShowTutorial(toolbox, pendingTutorialToolboxId));
}, [pendingTutorialToolboxId, toolbox, toolboxId, toolboxTutorial]);
function addModule(type) {
if (!type) return;
const module = { id: uid("mod"), type };
@ -231,10 +249,10 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
<div>
<p className="eyebrow">Toolbox</p>
<div className="toolbox-title-row">
{!embedded && !toolboxGame && <ToolboxIconPicker toolbox={toolbox} onChange={(icon) => updateToolbox({ ...toolbox, icon })} />}
{!embedded && !toolboxGame && <ToolboxIconPicker toolbox={toolbox} onChange={(icon) => updateToolbox({ ...toolbox, icon })} tutorialTarget="icon" />}
{!embedded && toolboxGameCover && <ToolboxGameIcon game={toolboxGame} />}
{embedded ? <h1>{toolbox.name}</h1> : (
<EditableTitle className="toolbox-title" value={toolbox.name} fallback="Nouvelle toolbox" onSave={(name) => name !== toolbox.name && updateToolbox({ ...toolbox, name })} />
<EditableTitle className="toolbox-title" value={toolbox.name} fallback="Nouvelle toolbox" tutorialTarget="title" onSave={(name) => name !== toolbox.name && updateToolbox({ ...toolbox, name })} />
)}
</div>
{toolboxGame && (
@ -252,6 +270,25 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
<AddToolControls onAdd={addModule} />
</div>
</section>
{!embedded && showTutorial && (
<GuidedTooltipTour
steps={siteContent.toolboxes.tutorial?.items || []}
labels={{
dismiss: siteContent.toolboxes.tutorial?.dismissLabel,
next: siteContent.toolboxes.tutorial?.nextLabel,
done: siteContent.toolboxes.tutorial?.doneLabel
}}
targetAttribute="data-toolbox-tutorial-target"
onDismiss={() => {
if (pendingTutorialToolboxId === toolbox.id) setPendingTutorialToolboxId?.("");
setShowTutorial(false);
}}
onStarted={() => {
if (pendingTutorialToolboxId === toolbox.id) setPendingTutorialToolboxId?.("");
if (toolbox.tutorial === true) updateToolbox({ ...toolbox, tutorial: false });
}}
/>
)}
{!embedded && (
<section className="modules-toolbar" aria-label="Options d'affichage des outils">
<div className="layout-switch" role="group" aria-label="Mode d'affichage des outils">
@ -359,7 +396,7 @@ export function ToolboxDrawer({ gameId, game, siteContent, toolboxes, links, act
);
}
function EditableTitle({ value, fallback, onSave, className = "module-title" }) {
function EditableTitle({ value, fallback, onSave, className = "module-title", tutorialTarget = "" }) {
const inlineEdit = useInlineEdit({
value,
onCommit: (title) => onSave(title || fallback),
@ -370,6 +407,7 @@ function EditableTitle({ value, fallback, onSave, className = "module-title" })
<h1
{...inlineEdit.getContentEditableProps({
className,
"data-toolbox-tutorial-target": tutorialTarget || undefined,
title: "Cliquer pour renommer"
})}
>{value}</h1>