1
1
import React from "react"
2
+ import { clamp , useInterval } from "utils"
2
3
import { EditorProps , EditorStep } from "../mini-editor"
3
4
import { InnerCode , updateEditorStep } from "./code"
4
5
import { Preview , PresetConfig } from "./preview"
5
6
import { extractPreviewSteps } from "./steps"
6
7
8
+ type ChangeEvent = {
9
+ index : number
10
+ }
11
+
7
12
export function Slideshow ( {
8
13
children,
9
14
className,
@@ -30,19 +35,12 @@ export function Slideshow({
30
35
hasPreviewSteps ?: boolean
31
36
autoFocus ?: boolean
32
37
start ?: number
33
- onChange ?: Function
38
+ onChange ?: ( e : ChangeEvent ) => void
34
39
presetConfig ?: PresetConfig
35
40
style ?: React . CSSProperties
36
41
autoPlay ?: number
37
42
loop ?: boolean
38
43
} ) {
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
-
46
44
const { stepsChildren, previewChildren } =
47
45
extractPreviewSteps ( children , hasPreviewSteps )
48
46
const withPreview = presetConfig || hasPreviewSteps
@@ -51,103 +49,47 @@ export function Slideshow({
51
49
( child : any ) => child . props ?. children
52
50
)
53
51
54
- const maxSteps = editorSteps . length - 1 ;
52
+ const maxSteps = editorSteps . length - 1
55
53
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
+ }
63
60
} )
64
61
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
70
63
71
- const atSlideshowStart = currentSlideIndex === 0 ;
72
- const atSlideshowEnd = currentSlideIndex === maxSteps ;
64
+ const atSlideshowEnd = currentIndex === maxSteps
73
65
74
- // Run any time our Slideshow state changes
75
66
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 ] )
82
69
83
70
function onTabClick ( filename : string ) {
84
- const newStep = updateEditorStep (
85
- state . step ,
86
- filename ,
87
- null
88
- )
71
+ const newStep = updateEditorStep ( tab , filename , null )
89
72
setState ( { ...state , step : newStep } )
90
73
}
91
74
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 ] } )
103
78
}
104
79
105
- function slidePrevious ( ) {
80
+ function nextSlide ( ) {
106
81
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 )
111
85
return {
112
86
stepIndex,
113
87
step : editorSteps [ stepIndex ] ,
114
88
}
115
89
} )
116
90
}
117
91
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 )
151
93
152
94
return (
153
95
< div
@@ -176,48 +118,50 @@ export function Slideshow({
176
118
) : hasPreviewSteps ? (
177
119
< Preview
178
120
className = "ch-slideshow-preview"
179
- { ...previewChildren [ currentSlideIndex ] [ "props" ] }
121
+ { ...previewChildren [ currentIndex ] [ "props" ] }
180
122
/>
181
123
) : null }
182
124
</ div >
183
125
184
126
< div className = "ch-slideshow-notes" >
185
127
< div className = "ch-slideshow-range" >
186
- < button
187
- onClick = { ( ) => slidePrevious ( ) }
188
- disabled = { atSlideshowStart }
128
+ < button
129
+ onClick = { ( ) => setIndex ( currentIndex - 1 ) }
130
+ disabled = { currentIndex === 0 }
189
131
>
190
132
Prev
191
133
</ button >
192
134
< input
193
135
max = { maxSteps }
194
136
min = { 0 }
195
- ref = { controlsRef }
196
137
step = { 1 }
197
138
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 ) }
205
142
autoFocus = { autoFocus }
206
143
/>
207
- < button
208
- onClick = { ( ) => slideNext ( ) }
144
+ < button
145
+ onClick = { nextSlide }
209
146
disabled = { atSlideshowEnd }
210
147
>
211
148
Next
212
149
</ button >
213
150
</ div >
214
-
215
151
{ hasNotes && (
216
152
< div className = "ch-slideshow-note" >
217
- { stepsChildren [ currentSlideIndex ] }
153
+ { stepsChildren [ currentIndex ] }
218
154
</ div >
219
155
) }
220
156
</ div >
221
157
</ div >
222
158
)
223
159
}
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