@@ -15,8 +15,10 @@ export interface VisualTourProps {
1515 onClose : ( ) => void ;
1616 /** Label of the button to close the tour. */
1717 closeLabel ?: string ;
18- /** The label for the next button. */
18+ /** The label for the ' next' button. */
1919 nextLabel ?: string ;
20+ /** The label for the 'previous' button. */
21+ prevLabel ?: string ;
2022}
2123
2224interface VisualTourStep {
@@ -38,6 +40,7 @@ export const VisualTour = ({
3840 onClose,
3941 closeLabel = "Close" ,
4042 nextLabel = "Next" ,
43+ prevLabel = "Back" ,
4144} : VisualTourProps ) => {
4245 const [ currentStepIndex , setCurrentStepIndex ] = React . useState < number > ( 0 ) ;
4346 const [ currentStepComponent , setCurrentStepComponent ] = React . useState < React . JSX . Element | null > ( null ) ;
@@ -51,45 +54,121 @@ export const VisualTour = ({
5154 return ;
5255 }
5356 const hasNextStep = currentStepIndex + 1 < steps . length ;
57+ const hasPreviousStep = currentStepIndex > 0 ;
5458 // Configure optional highlighting
5559 const element = document . querySelector ( containerElementQuery ) ;
60+ let elementToHighlight : Element | null = null ;
5661 if ( element ) {
5762 // Remove previous element highlight
5863 element . classList . remove ( containerHighlightClass ) ;
5964 document . querySelector ( `.${ highlightElementClass } ` ) ?. classList . remove ( highlightElementClass ) ;
6065 if ( step . highlightElementQuery ) {
61- const elementToHighlight = document . querySelector ( step . highlightElementQuery ) ;
66+ elementToHighlight = document . querySelector ( step . highlightElementQuery ) ;
6267 if ( elementToHighlight ) {
6368 element . classList . add ( containerHighlightClass ) ;
6469 elementToHighlight . classList . add ( highlightElementClass ) ;
6570 }
6671 }
6772 }
68- setCurrentStepComponent (
69- < SimpleDialog
70- title = { step . title + ` ${ currentStepIndex + 1 } / ${ steps . length } ` }
71- isOpen = { true }
72- preventSimpleClosing = { true }
73- onClose = { onClose }
74- actions = { [
75- < Button onClick = { onClose } > { closeLabel } </ Button > ,
76- hasNextStep ? (
77- < Button
78- onClick = { ( ) => {
79- setCurrentStepIndex ( currentStepIndex + 1 ) ;
80- } }
81- >
82- { nextLabel } : { steps [ currentStepIndex + 1 ] . title }
83- </ Button >
84- ) : null ,
85- ] }
86- >
87- { typeof step . content === "string" ? step . content : step . content ( ) }
88- </ SimpleDialog >
89- ) ;
73+ const titleSuffix = ` ${ currentStepIndex + 1 } / ${ steps . length } ` ;
74+ const navigationButtons = [
75+ < Button onClick = { onClose } > { closeLabel } </ Button > ,
76+ hasNextStep ? (
77+ < Button
78+ onClick = { ( ) => {
79+ setCurrentStepIndex ( currentStepIndex + 1 ) ;
80+ } }
81+ >
82+ { nextLabel } : { steps [ currentStepIndex + 1 ] . title }
83+ </ Button >
84+ ) : null ,
85+ hasPreviousStep ? (
86+ < Button
87+ onClick = { ( ) => {
88+ setCurrentStepIndex ( currentStepIndex - 1 ) ;
89+ } }
90+ >
91+ { prevLabel } : { steps [ currentStepIndex - 1 ] . title }
92+ </ Button >
93+ ) : null ,
94+ ] ;
95+ // TODO: What to do if an element should have been highlighted, but none was found?
96+ if ( elementToHighlight ) {
97+ setCurrentStepComponent (
98+ < StepPopover
99+ highlightedElement = { elementToHighlight }
100+ titleSuffix = { titleSuffix }
101+ navigationButtons = { navigationButtons }
102+ step = { step }
103+ onClose = { onClose }
104+ />
105+ ) ;
106+ } else {
107+ setCurrentStepComponent (
108+ < StepModal
109+ titleSuffix = { titleSuffix }
110+ navigationButtons = { navigationButtons }
111+ step = { step }
112+ onClose = { onClose }
113+ />
114+ ) ;
115+ }
90116 } , [ currentStepIndex ] ) ;
91117
92118 return currentStepComponent ;
93119} ;
94120
121+ interface StepModalProps {
122+ step : VisualTourStep ;
123+ // Current step starting with 1
124+ titleSuffix : string ;
125+ // Close the visual tour
126+ onClose : ( ) => void ;
127+ // The navigation buttons
128+ navigationButtons : ( React . JSX . Element | null ) [ ] ;
129+ }
130+
131+ /** Modal that is displayed for a step. */
132+ const StepModal = ( { step, titleSuffix, onClose, navigationButtons } : StepModalProps ) => {
133+ return (
134+ < SimpleDialog
135+ title = { `${ step . title } ${ titleSuffix } ` }
136+ isOpen = { true }
137+ preventSimpleClosing = { true }
138+ onClose = { onClose }
139+ actions = { navigationButtons }
140+ >
141+ { typeof step . content === "string" ? step . content : step . content ( ) }
142+ </ SimpleDialog >
143+ ) ;
144+ } ;
145+
146+ interface StepPopoverProps {
147+ highlightedElement : Element ;
148+ step : VisualTourStep ;
149+ // Current step starting with 1
150+ titleSuffix : string ;
151+ // Close the visual tour
152+ onClose : ( ) => void ;
153+ // The navigation buttons
154+ navigationButtons : ( React . JSX . Element | null ) [ ] ;
155+ }
156+
157+ /** Popover that is displayed and points at the highlighted element. */
158+ const StepPopover = ( { highlightedElement, step, titleSuffix, onClose, navigationButtons } : StepPopoverProps ) => {
159+ // TODO: Show popover on highlighted element
160+ return (
161+ < SimpleDialog
162+ title = { `${ step . title } ${ titleSuffix } ` }
163+ isOpen = { true }
164+ preventSimpleClosing = { true }
165+ onClose = { onClose }
166+ actions = { navigationButtons }
167+ >
168+ { typeof step . content === "string" ? step . content : step . content ( ) }
169+ { highlightedElement . tagName }
170+ </ SimpleDialog >
171+ ) ;
172+ } ;
173+
95174export default VisualTour ;
0 commit comments