From ed492c1874014e8a29bacab322fc5890a72c5f28 Mon Sep 17 00:00:00 2001 From: gbanyan Date: Mon, 22 Dec 2025 21:33:26 +0800 Subject: [PATCH] Add guided tour feature with Driver.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Install driver.js package for lightweight product tours - Create GuidedTour component with bilingual tour steps (Chinese/English) - Create TourPromptModal to ask users if they want a tour after welcome - Add data-tour attributes to Toolbar, FilePanel, PedigreeCanvas, PropertyPanel - Tour covers: file operations, adding persons, canvas usage, relationships, editing properties, and exporting Tour flow: 1. First visit: Welcome Modal → Tour Prompt Modal → Start tour or skip 2. Subsequent visits: No modals shown 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 7 + package.json | 1 + src/components/App/App.tsx | 29 ++++ src/components/FilePanel/FilePanel.tsx | 4 +- src/components/GuidedTour/tourSteps.ts | 150 ++++++++++++++++++ src/components/GuidedTour/useGuidedTour.ts | 47 ++++++ .../PedigreeCanvas/PedigreeCanvas.tsx | 2 +- .../PropertyPanel/PropertyPanel.tsx | 2 +- src/components/Toolbar/Toolbar.tsx | 4 +- .../TourPromptModal.module.css | 91 +++++++++++ .../TourPromptModal/TourPromptModal.tsx | 52 ++++++ 11 files changed, 383 insertions(+), 6 deletions(-) create mode 100644 src/components/GuidedTour/tourSteps.ts create mode 100644 src/components/GuidedTour/useGuidedTour.ts create mode 100644 src/components/TourPromptModal/TourPromptModal.module.css create mode 100644 src/components/TourPromptModal/TourPromptModal.tsx diff --git a/package-lock.json b/package-lock.json index 5b6dfa2..176bf91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "d3": "^7.9.0", + "driver.js": "^1.4.0", "html-to-image": "^1.11.13", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -2672,6 +2673,12 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/driver.js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.4.0.tgz", + "integrity": "sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", diff --git a/package.json b/package.json index 831eb55..cda3bb2 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "d3": "^7.9.0", + "driver.js": "^1.4.0", "html-to-image": "^1.11.13", "react": "^19.2.0", "react-dom": "^19.2.0", diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 0fa1453..9347269 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -11,10 +11,13 @@ import { PropertyPanel } from '../PropertyPanel/PropertyPanel'; import { RelationshipPanel } from '../RelationshipPanel/RelationshipPanel'; import { FilePanel } from '../FilePanel/FilePanel'; import { WelcomeModal } from '../WelcomeModal/WelcomeModal'; +import { TourPromptModal } from '../TourPromptModal/TourPromptModal'; +import { useGuidedTour } from '../GuidedTour/useGuidedTour'; import { usePedigreeStore, useTemporalStore } from '@/store/pedigreeStore'; import styles from './App.module.css'; const WELCOME_DISMISSED_KEY = 'pedigree-draw-welcome-dismissed'; +const TOUR_COMPLETED_KEY = 'pedigree-draw-tour-completed'; export function App() { const { @@ -26,13 +29,36 @@ export function App() { } = usePedigreeStore(); const temporal = useTemporalStore(); + // Guided tour hook + const { startTour } = useGuidedTour(); + // Welcome modal state - check localStorage on init const [showWelcome, setShowWelcome] = useState(() => { return localStorage.getItem(WELCOME_DISMISSED_KEY) !== 'true'; }); + // Tour prompt modal state - show after welcome if tour not completed + const [showTourPrompt, setShowTourPrompt] = useState(false); + const handleCloseWelcome = () => { setShowWelcome(false); + // Show tour prompt if tour hasn't been completed yet + if (localStorage.getItem(TOUR_COMPLETED_KEY) !== 'true') { + setShowTourPrompt(true); + } + }; + + const handleStartTour = () => { + setShowTourPrompt(false); + // Small delay to let modal close before tour starts + setTimeout(() => { + startTour(); + }, 100); + }; + + const handleSkipTour = () => { + setShowTourPrompt(false); + localStorage.setItem(TOUR_COMPLETED_KEY, 'true'); }; // Keyboard shortcuts @@ -99,6 +125,9 @@ export function App() { {showWelcome && } + {showTourPrompt && ( + + )} ); } diff --git a/src/components/FilePanel/FilePanel.tsx b/src/components/FilePanel/FilePanel.tsx index f55d04a..99a841b 100644 --- a/src/components/FilePanel/FilePanel.tsx +++ b/src/components/FilePanel/FilePanel.tsx @@ -125,7 +125,7 @@ export function FilePanel() { }, [createNewPedigree]); return ( -
+
File Operations
@@ -163,7 +163,7 @@ export function FilePanel() {
Export
-
+
{Math.round(zoomLevel * 100)}% diff --git a/src/components/PropertyPanel/PropertyPanel.tsx b/src/components/PropertyPanel/PropertyPanel.tsx index 2e65bce..87446aa 100644 --- a/src/components/PropertyPanel/PropertyPanel.tsx +++ b/src/components/PropertyPanel/PropertyPanel.tsx @@ -63,7 +63,7 @@ export function PropertyPanel() { }; return ( -
+
Properties {selectedPerson.id} diff --git a/src/components/Toolbar/Toolbar.tsx b/src/components/Toolbar/Toolbar.tsx index c466a3e..90c3d80 100644 --- a/src/components/Toolbar/Toolbar.tsx +++ b/src/components/Toolbar/Toolbar.tsx @@ -163,7 +163,7 @@ export function Toolbar() {
-
+
+ +
+
+
+ ); +}