11import React from "react"
2+ import { clamp , useInterval } from "utils"
23import { EditorProps , EditorStep } from "../mini-editor"
34import { InnerCode , updateEditorStep } from "./code"
45import { Preview , PresetConfig } from "./preview"
56import { extractPreviewSteps } from "./steps"
67
8+ type ChangeEvent = {
9+ index : number
10+ }
11+
712export function Slideshow ( {
813 children,
914 className,
@@ -30,19 +35,12 @@ export function Slideshow({
3035 hasPreviewSteps ?: boolean
3136 autoFocus ?: boolean
3237 start ?: number
33- onChange ?: Function
38+ onChange ?: ( e : ChangeEvent ) => void
3439 presetConfig ?: PresetConfig
3540 style ?: React . CSSProperties
3641 autoPlay ?: number
3742 loop ?: boolean
3843} ) {
39- const controlsRef = React . useRef ( null )
40-
41- React . useEffect ( ( ) => {
42- // Only set focus on controls input if we have configured to do so
43- autoFocus && controlsRef . current . focus ( )
44- } , [ ] )
45-
4644 const { stepsChildren, previewChildren } =
4745 extractPreviewSteps ( children , hasPreviewSteps )
4846 const withPreview = presetConfig || hasPreviewSteps
@@ -51,103 +49,47 @@ export function Slideshow({
5149 ( child : any ) => child . props ?. children
5250 )
5351
54- const maxSteps = editorSteps . length - 1 ;
52+ const maxSteps = editorSteps . length - 1
5553
56- // Make sure the initial slide is not configured out of bounds
57- const initialSlide = start > maxSteps ? maxSteps : start
58-
59- // As this gets more complex, probably would make more sense to abstract this into a custom hook with methods to modify state versus exposing directly
60- const [ state , setState ] = React . useState ( {
61- stepIndex : initialSlide ,
62- step : editorSteps [ initialSlide ] ,
54+ const [ state , setState ] = React . useState ( ( ) => {
55+ const startIndex = clamp ( start , 0 , maxSteps )
56+ return {
57+ stepIndex : startIndex ,
58+ step : editorSteps [ startIndex ] ,
59+ }
6360 } )
6461
65- // Destructure these values and give them more semantic names for use below
66- const {
67- stepIndex : currentSlideIndex ,
68- step : tab ,
69- } = state ;
62+ const { stepIndex : currentIndex , step : tab } = state
7063
71- const atSlideshowStart = currentSlideIndex === 0 ;
72- const atSlideshowEnd = currentSlideIndex === maxSteps ;
64+ const atSlideshowEnd = currentIndex === maxSteps
7365
74- // Run any time our Slideshow state changes
7566 React . useEffect ( ( ) => {
76- // Return our state object to the Slideshow onChange function
77- onSlideshowChange ( {
78- index : currentSlideIndex
79- } ) ;
80- // We are only calling this effect if the current slide changes.
81- } , [ currentSlideIndex ] ) ;
67+ onSlideshowChange ( { index : currentIndex } )
68+ } , [ currentIndex ] )
8269
8370 function onTabClick ( filename : string ) {
84- const newStep = updateEditorStep (
85- state . step ,
86- filename ,
87- null
88- )
71+ const newStep = updateEditorStep ( tab , filename , null )
8972 setState ( { ...state , step : newStep } )
9073 }
9174
92- function slideNext ( ) {
93- setState ( s => {
94- const stepIndex = Math . min (
95- maxSteps ,
96- s . stepIndex + 1
97- )
98- return {
99- stepIndex,
100- step : editorSteps [ stepIndex ] ,
101- }
102- } )
75+ function setIndex ( newIndex : number ) {
76+ const stepIndex = clamp ( newIndex , 0 , maxSteps )
77+ setState ( { stepIndex, step : editorSteps [ stepIndex ] } )
10378 }
10479
105- function slidePrevious ( ) {
80+ function nextSlide ( ) {
10681 setState ( s => {
107- const stepIndex = Math . max (
108- 0 ,
109- s . stepIndex - 1
110- )
82+ const stepIndex = loop
83+ ? ( s . stepIndex + 1 ) % ( maxSteps + 1 )
84+ : clamp ( s . stepIndex + 1 , 0 , maxSteps )
11185 return {
11286 stepIndex,
11387 step : editorSteps [ stepIndex ] ,
11488 }
11589 } )
11690 }
11791
118- React . useEffect ( ( ) => {
119- // If autoplay is enabled, and we are not at the end of the slides, move to the next slide
120- if ( autoPlay && ! atSlideshowEnd ) {
121- const autoSlide = setTimeout (
122- ( ) => slideNext ( ) ,
123- autoPlay
124- ) ;
125-
126- // Cleanup our timeout if our component unmounts
127- return ( ) => {
128- clearTimeout ( autoSlide ) ;
129- } ;
130- // If we are at the end of the slideshow, and we have configured to loop, start over
131- } else if ( autoPlay && atSlideshowEnd && loop ) {
132- // We still have to use the same timeout function with autoPlay delay or else the last slide will never show because it will instantly change
133- const autoRestart = setTimeout (
134- ( ) => {
135- setState ( {
136- stepIndex : 0 ,
137- step : editorSteps [ 0 ] ,
138- } )
139- } ,
140- autoPlay
141- ) ;
142-
143- // Cleanup our timeout if our component unmounts
144- return ( ) => {
145- clearTimeout ( autoRestart ) ;
146- } ;
147- } else {
148- return null ;
149- }
150- } , [ currentSlideIndex , autoPlay ] ) ;
92+ useInterval ( nextSlide , autoPlay )
15193
15294 return (
15395 < div
@@ -176,48 +118,50 @@ export function Slideshow({
176118 ) : hasPreviewSteps ? (
177119 < Preview
178120 className = "ch-slideshow-preview"
179- { ...previewChildren [ currentSlideIndex ] [ "props" ] }
121+ { ...previewChildren [ currentIndex ] [ "props" ] }
180122 />
181123 ) : null }
182124 </ div >
183125
184126 < div className = "ch-slideshow-notes" >
185127 < div className = "ch-slideshow-range" >
186- < button
187- onClick = { ( ) => slidePrevious ( ) }
188- disabled = { atSlideshowStart }
128+ < button
129+ onClick = { ( ) => setIndex ( currentIndex - 1 ) }
130+ disabled = { currentIndex === 0 }
189131 >
190132 Prev
191133 </ button >
192134 < input
193135 max = { maxSteps }
194136 min = { 0 }
195- ref = { controlsRef }
196137 step = { 1 }
197138 type = "range"
198- value = { currentSlideIndex }
199- onChange = { e =>
200- setState ( {
201- stepIndex : + e . target . value ,
202- step : editorSteps [ + e . target . value ] ,
203- } )
204- }
139+ value = { currentIndex }
140+ onChange = { e => setIndex ( + e . target . value ) }
141+ ref = { useAutoFocusRef ( autoFocus ) }
205142 autoFocus = { autoFocus }
206143 />
207- < button
208- onClick = { ( ) => slideNext ( ) }
144+ < button
145+ onClick = { nextSlide }
209146 disabled = { atSlideshowEnd }
210147 >
211148 Next
212149 </ button >
213150 </ div >
214-
215151 { hasNotes && (
216152 < div className = "ch-slideshow-note" >
217- { stepsChildren [ currentSlideIndex ] }
153+ { stepsChildren [ currentIndex ] }
218154 </ div >
219155 ) }
220156 </ div >
221157 </ div >
222158 )
223159}
160+
161+ function useAutoFocusRef ( autoFocus : boolean ) {
162+ const ref = React . useRef ( null )
163+ React . useEffect ( ( ) => {
164+ autoFocus && ref . current . focus ( )
165+ } , [ ] )
166+ return ref
167+ }
0 commit comments