1
- import { css } from '@emotion/react'
2
- import React , { PureComponent } from 'react'
3
1
import * as theme from '../styles/theme'
2
+
3
+ import React , { PureComponent } from 'react'
4
+
4
5
import Button from './Button'
5
6
import SVGIcon from './SVGIcon'
7
+ import { css } from '@emotion/react'
6
8
7
9
interface Props {
8
10
children : React . ReactNode
9
- keys : string [ ]
10
- maxWidth : number
11
+ keys ? : string [ ]
12
+ // Set to 0 to stop
11
13
interval ?: number
14
+ maxWidth : number
15
+ numSlides : number
16
+ showDesktopButtons ?: boolean
12
17
}
13
18
14
19
interface State {
@@ -98,10 +103,6 @@ export default class Carousel extends PureComponent<Props, State> {
98
103
this . addListeners ( )
99
104
}
100
105
101
- getNumSlides ( ) {
102
- return this . slides . current ?. childElementCount || 1
103
- }
104
-
105
106
getCurrentWidth ( ) {
106
107
if ( ! this . container . current ) {
107
108
return 0
@@ -111,10 +112,10 @@ export default class Carousel extends PureComponent<Props, State> {
111
112
112
113
constrainX ( x : number ) {
113
114
const width = this . getCurrentWidth ( )
115
+ const { numSlides } = this . props
114
116
const { index } = this . state
115
- const len = this . getNumSlides ( )
116
117
return Math . max (
117
- - width * ( index < len - 1 ? index + 1 : index ) ,
118
+ - width * ( index < numSlides - 1 ? index + 1 : index ) ,
118
119
Math . min ( - width * ( index > 0 ? index - 1 : index ) , x )
119
120
)
120
121
}
@@ -151,67 +152,84 @@ export default class Carousel extends PureComponent<Props, State> {
151
152
}
152
153
153
154
next = ( ) => {
155
+ const { numSlides } = this . props
154
156
const { index } = this . state
155
- const len = this . getNumSlides ( )
156
157
this . setState ( {
157
- index : ( index + 1 ) % len ,
158
+ index : ( index + 1 ) % numSlides ,
158
159
moving : false
159
160
} )
160
161
}
161
162
162
163
prev = ( ) => {
164
+ const { numSlides } = this . props
163
165
const { index } = this . state
164
- const len = this . getNumSlides ( )
165
166
this . setState ( {
166
- index : ( index + len - 1 ) % len ,
167
+ index : ( index + numSlides - 1 ) % numSlides ,
167
168
moving : false
168
169
} )
169
170
}
170
171
171
172
start ( ) {
172
- this . timeout = setInterval ( this . next , this . props . interval )
173
+ const { interval } = this . props
174
+ if ( interval ! > 0 ) {
175
+ this . timeout = setInterval ( this . next , interval )
176
+ }
173
177
}
174
178
175
179
stop ( ) {
176
180
clearTimeout ( this . timeout ! )
177
181
}
178
182
179
183
render ( ) {
180
- const { children, keys, maxWidth } = this . props
184
+ const {
185
+ children,
186
+ keys = [ ] ,
187
+ maxWidth,
188
+ numSlides,
189
+ showDesktopButtons
190
+ } = this . props
181
191
const { index, moving, x } = this . state
192
+ const buttonStyle = [ styles . moveButton ]
193
+ const arrowStyle = [ styles . arrow ]
194
+ if ( showDesktopButtons ) {
195
+ buttonStyle . push ( styles . desktopButton )
196
+ arrowStyle . push ( styles . desktopArrow )
197
+ }
182
198
183
199
return (
184
- < div ref = { this . container } style = { { maxWidth } } >
185
- < div css = { styles . buttons } >
186
- { keys . map ( ( key , i ) => (
187
- < Button
188
- key = { key }
189
- transparent = { i !== index }
190
- extraCss = { [
191
- styles . button ,
192
- css `
193
- ${ theme . DEFAULT_MEDIA_QUERY } {
194
- : not (: nth-of-type (${ index + 1 } )) {
195
- display : none;
200
+ < div css = { styles . carousel } ref = { this . container } style = { { maxWidth } } >
201
+ { ! ! keys . length && (
202
+ < div css = { styles . buttons } >
203
+ { keys . map ( ( key , i ) => (
204
+ < Button
205
+ key = { key }
206
+ transparent = { i !== index }
207
+ extraCss = { [
208
+ styles . button ,
209
+ css `
210
+ ${ theme . DEFAULT_MEDIA_QUERY } {
211
+ : not (: nth-of-type (${ index + 1 } )) {
212
+ display : none;
213
+ }
196
214
}
215
+ `
216
+ ] }
217
+ onClick = { ( ) => {
218
+ this . stop ( )
219
+ if (
220
+ window . matchMedia ( `(min-width:${ theme . DEFAULT_WIDTH } px)` )
221
+ . matches
222
+ ) {
223
+ this . setState ( { index : i } )
224
+ } else {
225
+ this . next ( )
197
226
}
198
- `
199
- ] }
200
- onClick = { ( ) => {
201
- this . stop ( )
202
- if (
203
- window . matchMedia ( `(min-width:${ theme . DEFAULT_WIDTH } px)` )
204
- . matches
205
- ) {
206
- this . setState ( { index : i } )
207
- } else {
208
- this . next ( )
209
- }
210
- } } >
211
- { key }
212
- </ Button >
213
- ) ) }
214
- </ div >
227
+ } } >
228
+ { key }
229
+ </ Button >
230
+ ) ) }
231
+ </ div >
232
+ ) }
215
233
< div css = { styles . content } >
216
234
< div css = { styles . slidesWrap } >
217
235
< div
@@ -223,32 +241,34 @@ export default class Carousel extends PureComponent<Props, State> {
223
241
style = { {
224
242
transform : moving
225
243
? `translateX(${ x } px)`
226
- : `translateX(-${ index * 25 } %)` ,
227
- width : `${ keys . length * 100 } %`
244
+ : `translateX(-${ index * ( 100 / numSlides ) } %)` ,
245
+ width : `${ numSlides * 100 } %`
228
246
} } >
229
247
{ children }
230
248
</ div >
231
249
</ div >
232
250
< Button
233
- link
251
+ link = { ! showDesktopButtons }
234
252
onClick = { ( ) => {
235
253
this . stop ( )
236
254
this . prev ( )
237
255
} }
238
- extraCss = { [ styles . moveButton , styles . prevButton ] } >
256
+ className = "prev-button"
257
+ extraCss = { buttonStyle } >
239
258
< SVGIcon
240
- icon = "#arrow-down "
241
- extraCss = { [ styles . arrow , styles . backArrow ] }
259
+ icon = "#arrow-forward "
260
+ extraCss = { arrowStyle . concat ( styles . backArrow ) }
242
261
/>
243
262
</ Button >
244
263
< Button
245
- link
264
+ link = { ! showDesktopButtons }
246
265
onClick = { ( ) => {
247
266
this . stop ( )
248
267
this . next ( )
249
268
} }
250
- extraCss = { [ styles . moveButton , styles . nextButton ] } >
251
- < SVGIcon icon = "#arrow-down" extraCss = { [ styles . arrow ] } />
269
+ className = "next-button"
270
+ extraCss = { buttonStyle } >
271
+ < SVGIcon icon = "#arrow-forward" extraCss = { arrowStyle } />
252
272
</ Button >
253
273
</ div >
254
274
</ div >
@@ -257,6 +277,10 @@ export default class Carousel extends PureComponent<Props, State> {
257
277
}
258
278
259
279
const styles = {
280
+ carousel : css `
281
+ width : 100% ;
282
+ cursor : default;
283
+ ` ,
260
284
buttons : css `
261
285
display : flex;
262
286
flex-direction : row;
@@ -291,25 +315,50 @@ const styles = {
291
315
` ,
292
316
moveButton : css `
293
317
position : absolute;
294
- top : 50% ;
295
- transform : translateY (-50% );
318
+ top : -92px ;
319
+
320
+ & .prev-button {
321
+ left : 0 ;
322
+ }
323
+
324
+ & .next-button {
325
+ right : 0 ;
326
+ }
296
327
297
328
${ theme . MIN_DEFAULT_MEDIA_QUERY } {
298
329
display : none;
299
330
}
300
331
` ,
301
- prevButton : css `
302
- left : -20px ;
303
- ` ,
304
- nextButton : css `
305
- right : -20px ;
332
+ desktopButton : css `
333
+ width : 40px ;
334
+ height : 40px ;
335
+ border-radius : 50% ;
336
+ top : 50% ;
337
+ transform : translateY (-50% );
338
+
339
+ ${ theme . MIN_DEFAULT_MEDIA_QUERY } {
340
+ display : flex;
341
+ width : 60px ;
342
+ height : 60px ;
343
+
344
+ & .prev-button {
345
+ left : -100px ;
346
+ }
347
+
348
+ & .next-button {
349
+ right : -100px ;
350
+ }
351
+ }
306
352
` ,
307
353
arrow : css `
308
- width : 14px ;
309
- height : 15px ;
310
- transform : rotateZ (-90deg );
354
+ width : 32px ;
355
+ height : 32px ;
356
+ fill : ${ theme . primary } ;
357
+ ` ,
358
+ desktopArrow : css `
359
+ fill : white;
311
360
` ,
312
361
backArrow : css `
313
- transform : rotateZ ( 90 deg );
362
+ transform : rotateY ( 180 deg );
314
363
`
315
364
}
0 commit comments