-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Onboarding: Guided tour checklist #32795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d581a3b
4c9e929
e662368
b0fbf3c
0ff85f0
5110838
e7ca5c1
b91e701
3b384b4
f473e21
69cc7ba
ce5ebc0
4cd3631
bd55f1e
c89bce5
02c9aca
d685d12
489113d
e23d3f9
b7e60c5
46081e5
b92f735
2e65137
6c633c4
91a3519
770baf7
84440e2
36e688a
385066d
7c51785
1a917a3
36df52d
69eac58
5d1b37e
9f8bf34
7db2e15
0e109db
4289e0e
000cae7
076adee
c5cc465
0f5f1a6
894f311
f5f0ad8
dcfe2fc
49492f0
2a2eb6c
b82881f
4c0104f
8030fc3
230577e
405b934
1eea810
8e471bf
c0eb34b
94c5c62
5f1bd57
5ba33eb
9d28bb0
cd97967
10917ad
589f1f0
624bfc6
5845f57
1411869
70914fe
af5996a
571eb37
da9c7b8
8a27b33
2adfb83
870ab94
d6e6f09
85b9fa1
8c5c660
02917c4
073778c
bc7ac39
9aef937
e311a22
1f79889
1c8bb66
056a80f
f0dfab7
d9df904
2fd404c
bb87ae0
7591d75
88afdc1
64c5d96
c2eaf69
c051458
760764f
e49e0ba
f367ae4
990d21e
eb45f76
3cac5c1
e6456e2
bc804e3
aad4c79
48c0efb
a38f6f0
d8293cb
6327559
f329817
858d2a1
7217660
d8e826e
2977758
aecbc3b
8990d19
9dcb0da
8189bb1
ab25430
5a83618
31255a1
05d50a0
e54c910
4af5c3a
d7e0a57
32ee70d
c75648e
0ffd1f7
659e860
a3b63a3
90a716c
5fa9a01
b51a36d
2e80f98
505e7ec
df04cb7
48a1bc8
2eda64c
b9437f2
81cf1f2
5be3a1f
b753270
e00e1ee
46cc469
1defe4c
fb4f186
331d34d
35fa041
f0ac445
8f91857
50b98b6
135a375
72e49a8
68ef860
2e876cc
703de3e
21e2d4a
f4de5b2
baa9d8b
87b19f3
ec29889
2df3d62
cba209c
a585c45
15cc514
5d7f60a
941600c
4e8d31d
499a8a9
d88ac22
839b6a7
0baca2b
3a02ac2
7eaa90f
e0ea357
b29a44a
75f3867
9b509a7
6122ed5
1760149
499b583
cbbeb47
4caa1e1
d6f26a6
44543da
50bba49
d0cb3b7
131ecbb
27a8514
4baf5db
679e4a6
dc8603e
c141c98
dec8cfc
c71d37f
26b351c
89faea2
c5f7c39
bff2d46
a08e8d0
0f548b3
6925f08
1cfaf81
2d2f717
cfccdc6
08bdbd0
a37edcb
8a9fa97
5d02fb8
d79a162
d42319a
bf693a3
5f29a50
a65a429
2ff8d29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
|
|
||
| import { fn } from 'storybook/test'; | ||
|
|
||
| import { Button } from './Button'; | ||
|
|
||
| const meta = { | ||
| title: 'Example/Button', | ||
| component: Button, | ||
| parameters: { | ||
| layout: 'centered', | ||
| parameters: { | ||
| chromatic: { | ||
| disableSnapshot: true, | ||
| }, | ||
| }, | ||
| }, | ||
| tags: ['autodocs'], | ||
| argTypes: { | ||
| backgroundColor: { control: 'color' }, | ||
| }, | ||
| args: { onClick: fn() }, | ||
| } satisfies Meta<typeof Button>; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| export const Primary: Story = { | ||
| args: { | ||
| primary: true, | ||
| label: 'Button', | ||
| }, | ||
| }; | ||
|
|
||
| export const Secondary: Story = { | ||
| args: { | ||
| label: 'Button', | ||
| }, | ||
| }; | ||
|
|
||
| export const Large: Story = { | ||
| args: { | ||
| size: 'large', | ||
| label: 'Button', | ||
| }, | ||
| }; | ||
|
|
||
| export const Small: Story = { | ||
| args: { | ||
| size: 'small', | ||
| label: 'Button', | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import React from 'react'; | ||
|
|
||
| import './button.css'; | ||
|
|
||
| export interface ButtonProps { | ||
| /** Is this the principal call to action on the page? */ | ||
| primary?: boolean; | ||
| /** What background color to use */ | ||
| backgroundColor?: string; | ||
| /** How large should the button be? */ | ||
| size?: 'small' | 'medium' | 'large'; | ||
| /** Button contents */ | ||
| label: string; | ||
| /** Optional click handler */ | ||
| onClick?: () => void; | ||
| } | ||
|
|
||
| /** Primary UI component for user interaction */ | ||
| export const Button = ({ | ||
| primary = false, | ||
| size = 'medium', | ||
| backgroundColor, | ||
| label, | ||
| ...props | ||
| }: ButtonProps) => { | ||
| const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; | ||
| return ( | ||
| <button | ||
| type="button" | ||
| className={['storybook-button', `storybook-button--${size}`, mode].join(' ')} | ||
| style={{ backgroundColor }} | ||
| {...props} | ||
| > | ||
| {label} | ||
| </button> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| .storybook-button { | ||
| display: inline-block; | ||
| cursor: pointer; | ||
| border: 0; | ||
| border-radius: 3em; | ||
| font-weight: 700; | ||
| line-height: 1; | ||
| font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; | ||
| } | ||
| .storybook-button--primary { | ||
| background-color: #555ab9; | ||
| color: white; | ||
| } | ||
| .storybook-button--secondary { | ||
| box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; | ||
| background-color: transparent; | ||
| color: #333; | ||
| } | ||
| .storybook-button--small { | ||
| padding: 10px 16px; | ||
| font-size: 12px; | ||
| } | ||
| .storybook-button--medium { | ||
| padding: 11px 20px; | ||
| font-size: 14px; | ||
| } | ||
| .storybook-button--large { | ||
| padding: 12px 24px; | ||
| font-size: 16px; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,15 +3,14 @@ import React, { useCallback, useEffect, useState } from 'react'; | |
| import { SyntaxHighlighter } from 'storybook/internal/components'; | ||
| import { SAVE_STORY_RESPONSE } from 'storybook/internal/core-events'; | ||
|
|
||
| import type { Step } from 'react-joyride'; | ||
| import { type API } from 'storybook/manager-api'; | ||
| import { ThemeProvider, convert, styled, themes } from 'storybook/theming'; | ||
|
|
||
| import { HighlightElement } from '../../../core/src/manager/components/TourGuide/HighlightElement'; | ||
| import { TourGuide } from '../../../core/src/manager/components/TourGuide/TourGuide'; | ||
ghengeveld marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import { Confetti } from './components/Confetti/Confetti'; | ||
| import { HighlightElement } from './components/HighlightElement/HighlightElement'; | ||
| import type { STORYBOOK_ADDON_ONBOARDING_STEPS } from './constants'; | ||
| import { ADDON_CONTROLS_ID, STORYBOOK_ADDON_ONBOARDING_CHANNEL } from './constants'; | ||
| import { GuidedTour } from './features/GuidedTour/GuidedTour'; | ||
| import { ADDON_CONTROLS_ID, ADDON_ONBOARDING_CHANNEL } from './constants'; | ||
| import { IntentSurvey } from './features/IntentSurvey/IntentSurvey'; | ||
| import { SplashScreen } from './features/SplashScreen/SplashScreen'; | ||
|
|
||
|
|
@@ -44,30 +43,14 @@ const CodeWrapper = styled.div(({ theme }) => ({ | |
| const theme = convert(); | ||
|
|
||
| export type StepKey = (typeof STORYBOOK_ADDON_ONBOARDING_STEPS)[number]; | ||
| export type StepDefinition = { | ||
| key: StepKey; | ||
| hideNextButton?: boolean; | ||
| onNextButtonClick?: () => void; | ||
| } & Partial< | ||
| Pick< | ||
| // Unfortunately we can't use ts-expect-error here for some reason | ||
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| // @ts-ignore Ignore circular reference | ||
| Step, | ||
| | 'content' | ||
| | 'disableBeacon' | ||
| | 'disableOverlay' | ||
| | 'floaterProps' | ||
| | 'offset' | ||
| | 'placement' | ||
| | 'spotlightClicks' | ||
| | 'styles' | ||
| | 'target' | ||
| | 'title' | ||
| > | ||
| >; | ||
|
|
||
| export default function Onboarding({ api }: { api: API }) { | ||
| export default function Onboarding({ | ||
| api, | ||
| hasCompletedSurvey, | ||
| }: { | ||
| api: API; | ||
| hasCompletedSurvey: boolean; | ||
| }) { | ||
| const [enabled, setEnabled] = useState(true); | ||
| const [showConfetti, setShowConfetti] = useState(false); | ||
| const [step, setStep] = useState<StepKey>('1:Intro'); | ||
|
|
@@ -98,33 +81,36 @@ export default function Onboarding({ api }: { api: API }) { | |
| [api] | ||
| ); | ||
|
|
||
| const disableOnboarding = useCallback(() => { | ||
| // remove onboarding query parameter from current url | ||
| const url = new URL(window.location.href); | ||
| // @ts-expect-error (not strict) | ||
| const path = decodeURIComponent(url.searchParams.get('path')); | ||
| url.search = `?path=${path}&onboarding=false`; | ||
| history.replaceState({}, '', url.href); | ||
| api.setQueryParams({ onboarding: 'false' }); | ||
| setEnabled(false); | ||
| }, [api, setEnabled]); | ||
| const disableOnboarding = useCallback( | ||
| (dismissedStep?: StepKey) => { | ||
| if (dismissedStep) { | ||
| api.emit(ADDON_ONBOARDING_CHANNEL, { | ||
| dismissedStep, | ||
| type: 'dismiss', | ||
| userAgent, | ||
| }); | ||
| } | ||
| // remove onboarding query parameter from current url | ||
| const url = new URL(window.location.href); | ||
| url.searchParams.set('onboarding', 'false'); | ||
| history.replaceState({}, '', url.href); | ||
| api.setQueryParams({ onboarding: 'false' }); | ||
| setEnabled(false); | ||
| }, | ||
| [api, setEnabled, userAgent] | ||
| ); | ||
|
|
||
| const completeOnboarding = useCallback( | ||
| const completeSurvey = useCallback( | ||
| (answers: Record<string, unknown>) => { | ||
| api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, { | ||
| step: '7:FinishedOnboarding' satisfies StepKey, | ||
| type: 'telemetry', | ||
| userAgent, | ||
| }); | ||
| api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, { | ||
| api.emit(ADDON_ONBOARDING_CHANNEL, { | ||
| answers, | ||
| type: 'survey', | ||
| userAgent, | ||
| }); | ||
| setStep('7:FinishedOnboarding'); | ||
| selectStory('configure-your-project--docs'); | ||
| disableOnboarding(); | ||
| }, | ||
| [api, selectStory, disableOnboarding, userAgent] | ||
| [api, selectStory, userAgent] | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
|
|
@@ -148,6 +134,10 @@ export default function Onboarding({ api }: { api: API }) { | |
|
|
||
| useEffect(() => { | ||
| setStep((current) => { | ||
| if (hasCompletedSurvey && current === '6:IntentSurvey') { | ||
| return '7:FinishedOnboarding'; | ||
| } | ||
|
|
||
| if ( | ||
| ['1:Intro', '5:StoryCreated', '6:IntentSurvey', '7:FinishedOnboarding'].includes(current) | ||
| ) { | ||
|
|
@@ -162,12 +152,13 @@ export default function Onboarding({ api }: { api: API }) { | |
| return '3:SaveFromControls'; | ||
| } | ||
|
|
||
| if (primaryControl) { | ||
| if (primaryControl || current === '2:Controls') { | ||
| return '2:Controls'; | ||
| } | ||
|
|
||
| return '1:Intro'; | ||
| }); | ||
| }, [createNewStoryForm, primaryControl, saveFromControls]); | ||
| }, [hasCompletedSurvey, createNewStoryForm, primaryControl, saveFromControls]); | ||
|
|
||
| useEffect(() => { | ||
| return api.on(SAVE_STORY_RESPONSE, ({ payload, success }) => { | ||
|
|
@@ -183,7 +174,7 @@ export default function Onboarding({ api }: { api: API }) { | |
| }, [api]); | ||
|
|
||
| useEffect( | ||
| () => api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, { step, type: 'telemetry', userAgent }), | ||
| () => api.emit(ADDON_ONBOARDING_CHANNEL, { step, type: 'telemetry', userAgent }), | ||
| [api, step, userAgent] | ||
| ); | ||
|
|
||
|
|
@@ -196,7 +187,7 @@ export default function Onboarding({ api }: { api: API }) { | |
| const snippet = source?.slice(startIndex).trim(); | ||
| const startingLineNumber = source?.slice(0, startIndex).split('\n').length; | ||
|
|
||
| const steps: StepDefinition[] = [ | ||
| const controlsTour = [ | ||
| { | ||
| key: '2:Controls', | ||
| target: '#control-primary', | ||
|
|
@@ -213,7 +204,7 @@ export default function Onboarding({ api }: { api: API }) { | |
| disableBeacon: true, | ||
| disableOverlay: true, | ||
| spotlightClicks: true, | ||
| onNextButtonClick: () => { | ||
| onNext: () => { | ||
| const input = document.querySelector('#control-primary') as HTMLInputElement; | ||
| input.click(); | ||
| }, | ||
|
|
@@ -234,7 +225,7 @@ export default function Onboarding({ api }: { api: API }) { | |
| disableBeacon: true, | ||
| disableOverlay: true, | ||
| spotlightClicks: true, | ||
| onNextButtonClick: () => { | ||
| onNext: () => { | ||
| const button = document.querySelector( | ||
| 'button[aria-label="Create new story with these settings"]' | ||
| ) as HTMLButtonElement; | ||
|
|
@@ -280,21 +271,59 @@ export default function Onboarding({ api }: { api: API }) { | |
| }, | ||
| }, | ||
| }, | ||
| ] as const; | ||
| ]; | ||
|
|
||
| const checklistTour = [ | ||
| { | ||
| key: '7:FinishedOnboarding', | ||
| target: '#storybook-checklist-module', | ||
| title: 'Continue at your own pace using the guide', | ||
| content: ( | ||
| <> | ||
| Nice! You've got the essentials. You can continue at your own pace using the guide to | ||
| discover more of Storybook's capabilities. | ||
| <HighlightElement targetSelector="#storybook-checklist-module" pulsating /> | ||
| </> | ||
| ), | ||
| offset: 0, | ||
| placement: 'right-start', | ||
| disableBeacon: true, | ||
| disableOverlay: true, | ||
| styles: { | ||
| tooltip: { | ||
| width: 350, | ||
| }, | ||
| }, | ||
| }, | ||
| ]; | ||
|
Comment on lines
+276
to
+298
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify the target element exists when this tour step is shown. The checklist tour targets Run the following script to verify the checklist module is referenced in related components: 🏁 Script executed: #!/bin/bash
# Description: Verify that the storybook-checklist-module element is defined in the codebase.
# Search for the checklist module ID definition or usage
rg -n "storybook-checklist-module" --type=tsx --type=ts --type=jsx --type=js -C3Length of output: 27 🏁 Script executed: #!/bin/bash
# Search for storybook-checklist-module reference without type restrictions
rg -n "storybook-checklist-module" -C3Length of output: 1068 🏁 Script executed: #!/bin/bash
# Search for checklist module related definitions
rg -n "checklist" -i --type=ts --type=js -C2 | head -50Length of output: 3468 🏁 Script executed: #!/bin/bash
# Look for element IDs being set or assigned
rg -n 'id=.*checklist|id: .*checklist' -i -C2Length of output: 50538 🏁 Script executed: #!/bin/bash
# Search ChecklistModule for the specific ID
rg -n "storybook-checklist-module" code/core/src/manager/components/sidebar/ -C5Length of output: 0 🏁 Script executed: #!/bin/bash
# Look at ChecklistModule.tsx for its root element ID
rg -n "id=" code/core/src/manager/components/sidebar/ChecklistModule.tsx -C2Length of output: 493 🏁 Script executed: #!/bin/bash
# Check if there's a wrapper or parent with id attribute in ChecklistModule
cat code/core/src/manager/components/sidebar/ChecklistModule.tsx | head -250Length of output: 7168 🏁 Script executed: #!/bin/bash
# Look for where ChecklistModule is rendered and if ID is set there
rg -n "ChecklistModule" code/addons/onboarding/src/ -A3 -B3Length of output: 0 🏁 Script executed: #!/bin/bash
# Check if the tour step with FinishedOnboarding key is actually used/activated
rg -n "FinishedOnboarding\|7:FinishedOnboarding" code/addons/onboarding/src/ -C3Length of output: 0 🏁 Script executed: #!/bin/bash
# Search for where the root checklist element/module might get the ID
rg -n "storybook-checklist" code/core/src/manager/components/sidebar/ChecklistModule.tsx -C5Length of output: 0 🏁 Script executed: #!/bin/bash
# Search entire codebase for the ID pattern used in a return/render statement
rg -n "id=['\"]storybook-checklist-module" -A2 -B2Length of output: 0 🏁 Script executed: #!/bin/bash
# Look at the full Onboarding.tsx file to see how tour steps are used
cat code/addons/onboarding/src/Onboarding.tsx | grep -n -A10 -B10 "checklistTour"Length of output: 1607 🏁 Script executed: #!/bin/bash
# Check if there's any element rendering at this location
rg -n "ChecklistModule\|checklist.*Module" code/core/src/manager/ -l | head -10Length of output: 0 🏁 Script executed: #!/bin/bash
# Find where ChecklistModule component is imported and rendered
rg -n "ChecklistModule" code/core/src/manager/ -A3 -B3 -lLength of output: 182 🏁 Script executed: #!/bin/bash
# Look for sidebar components that render the checklist
fd "sidebar" code/core/src/manager/components/ --type f -name "*.tsx" -exec grep -l "Checklist" {} \;Length of output: 230 🏁 Script executed: #!/bin/bash
# Search for any element with id containing checklist
rg -n 'id\s*=\s*["\'].*checklist' code/core/src/manager/components/sidebar/ -C2Length of output: 68 🏁 Script executed: #!/bin/bash
# Examine Sidebar.tsx where ChecklistModule is imported
cat code/core/src/manager/components/sidebar/Sidebar.tsx | grep -n "ChecklistModule" -A5 -B5Length of output: 990 🏁 Script executed: #!/bin/bash
# Look for the root container/div of ChecklistModule in Sidebar
rg -n "ChecklistModule" code/core/src/manager/components/sidebar/Sidebar.tsx -C10Length of output: 1790 🏁 Script executed: #!/bin/bash
# Search for ID assignments in Sidebar
cat code/core/src/manager/components/sidebar/Sidebar.tsx | grep -n "id="Length of output: 0 🏁 Script executed: #!/bin/bash
# Check the full context around ChecklistModule rendering in Sidebar
cat code/core/src/manager/components/sidebar/Sidebar.tsx | sed -n '150,185p'Length of output: 1197 🏁 Script executed: #!/bin/bash
# Final comprehensive search: look for any div/element with this exact ID anywhere
rg "storybook-checklist-module" code/ --type-listLength of output: 389 🏁 Script executed: #!/bin/bash
# Check if ID might be set via props or data attributes
rg -n "data-testid.*checklist\|id.*=.*checklist" code/core/src/manager/components/sidebar/ChecklistModule.tsxLength of output: 0 Add id="storybook-checklist-module" to the ChecklistModule's root element. The tour targets Add the ID to ChecklistModule's root container (the CollapsibleWithMargin wrapper) so the tour can properly locate and highlight the element when the FinishedOnboarding step is shown. 🤖 Prompt for AI Agents |
||
|
|
||
| return ( | ||
| <ThemeProvider theme={theme}> | ||
| {showConfetti && <Confetti />} | ||
| {step === '1:Intro' ? ( | ||
| <SplashScreen onDismiss={() => setStep('2:Controls')} /> | ||
| ) : step === '6:IntentSurvey' ? ( | ||
| <IntentSurvey onComplete={completeOnboarding} onDismiss={disableOnboarding} /> | ||
| <IntentSurvey | ||
| onComplete={completeSurvey} | ||
| onDismiss={() => disableOnboarding('6:IntentSurvey')} | ||
| /> | ||
| ) : step === '7:FinishedOnboarding' ? ( | ||
| <TourGuide | ||
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| // @ts-ignore Circular reference in Step type | ||
| step={step} | ||
| steps={checklistTour} | ||
| onComplete={() => disableOnboarding()} | ||
| onDismiss={() => disableOnboarding(step)} | ||
| /> | ||
| ) : ( | ||
| <GuidedTour | ||
| <TourGuide | ||
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| // @ts-ignore Circular reference in Step type | ||
| step={step} | ||
| steps={steps} | ||
| onClose={disableOnboarding} | ||
| onComplete={() => setStep('6:IntentSurvey')} | ||
| steps={controlsTour} | ||
| onComplete={() => setStep(hasCompletedSurvey ? '7:FinishedOnboarding' : '6:IntentSurvey')} | ||
| onDismiss={() => disableOnboarding(step)} | ||
| /> | ||
| )} | ||
| </ThemeProvider> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.