Skip to content

Commit a717375

Browse files
committed
Slideshow refactor
1 parent 9d9f3bd commit a717375

File tree

5 files changed

+75
-103
lines changed

5 files changed

+75
-103
lines changed

packages/mdx/dev/content/slideshow-auto.mdx packages/mdx/dev/content/slideshow-autoplay.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This is how to use the `<CH.Sildeshow>` component. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quia! Quidem, quisquam.
44

5-
<CH.Slideshow preset="https://codesandbox.io/s/rfdho" code={{minZoom: 0.5}} autoPlay={1000} loop>
5+
<CH.Slideshow preset="https://codesandbox.io/s/rfdho" code={{minZoom: 0.5}} autoPlay={3000} loop>
66

77
<CH.Code>
88

packages/mdx/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@
8686
"vitest": "^0.5.9"
8787
},
8888
"peerDependencies": {
89-
"react": "^16.8.3 || ^17 || ^18",
90-
"framer-motion": "^6.5.1"
89+
"react": "^16.8.3 || ^17 || ^18"
9190
},
9291
"repository": "code-hike/codehike",
9392
"license": "MIT",
+44-100
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import React from "react"
2+
import { clamp, useInterval } from "utils"
23
import { EditorProps, EditorStep } from "../mini-editor"
34
import { InnerCode, updateEditorStep } from "./code"
45
import { Preview, PresetConfig } from "./preview"
56
import { extractPreviewSteps } from "./steps"
67

8+
type ChangeEvent = {
9+
index: number
10+
}
11+
712
export 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+
}

packages/mdx/src/utils/hooks.ts

+25
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,28 @@ export const useLayoutEffect =
1010
// effect: any,
1111
// deps?: any
1212
// ) => {}
13+
14+
// from https://overreacted.io/making-setinterval-declarative-with-react-hooks/
15+
export function useInterval(
16+
callback: () => void,
17+
delay: number | null
18+
) {
19+
const savedCallback = React.useRef(callback)
20+
21+
React.useEffect(() => {
22+
savedCallback.current = callback
23+
}, [callback])
24+
25+
React.useEffect(() => {
26+
if (!delay && delay !== 0) {
27+
return undefined
28+
}
29+
30+
const id = setInterval(
31+
() => savedCallback.current(),
32+
delay
33+
)
34+
35+
return () => clearInterval(id)
36+
}, [delay])
37+
}

packages/mdx/src/utils/index.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ export * from "./code"
44
export * from "./focus"
55
export * from "./theme"
66
export * from "./hooks"
7+
8+
export function clamp(x: number, min: number, max: number) {
9+
return Math.min(Math.max(x, min), max)
10+
}

0 commit comments

Comments
 (0)