@startupjs-ui/carousel 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
7
+
8
+ **Note:** Version bump only for package @startupjs-ui/carousel
9
+
10
+
11
+
12
+
13
+
14
+ ## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
15
+
16
+
17
+ ### Features
18
+
19
+ * add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
20
+ * **carousel:** refactor Carousel component ([03b066b](https://github.com/startupjs/startupjs-ui/commit/03b066b0343985cce5eb4e023f160b1f267d4110))
package/README.mdx ADDED
@@ -0,0 +1,544 @@
1
+ import { useRef, useState } from 'react'
2
+ import { Text } from 'react-native'
3
+ import Carousel, { _PropsJsonSchema as CarouselPropsJsonSchema } from './index'
4
+ import Div from '@startupjs-ui/div'
5
+ import Span from '@startupjs-ui/span'
6
+ import { Sandbox } from '@startupjs-ui/docs'
7
+
8
+ # Carousel
9
+
10
+ ```js
11
+ import { Carousel } from 'startupjs-ui'
12
+ ```
13
+
14
+ ## Simple example
15
+
16
+ All you need to use is to transfer children in Carousel
17
+ Such an example of a carousel, used on instagram or vk for stories, children have no clear boundaries, such a carousel should be adaptive for all extensions and devices
18
+
19
+ ```jsx example noscroll
20
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
21
+ const caseStyle = {
22
+ height: 180,
23
+ width: 200,
24
+ paddingLeft: 4,
25
+ paddingRight: 4
26
+ }
27
+ const itemStyle = { width: '100%', height: '100%' }
28
+
29
+ return (
30
+ <Carousel style={carouselStyle}>
31
+ <Div style={caseStyle}>
32
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
33
+ </Div>
34
+ <Div style={caseStyle}>
35
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
36
+ </Div>
37
+ <Div style={caseStyle}>
38
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
39
+ </Div>
40
+ <Div style={caseStyle}>
41
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
42
+ </Div>
43
+ </Carousel>
44
+ )
45
+ ```
46
+
47
+ You can also set different widths for children, the component knows how to handle this case
48
+
49
+ ```jsx example noscroll
50
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
51
+ const caseStyle = {
52
+ height: 180,
53
+ width: 200,
54
+ paddingLeft: 4,
55
+ paddingRight: 4
56
+ }
57
+ const itemStyle = { width: '100%', height: '100%' }
58
+
59
+ return (
60
+ <Carousel style={carouselStyle}>
61
+ <Div style={{ ...caseStyle, width: 120 }}>
62
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
63
+ </Div>
64
+ <Div style={{ ...caseStyle, width: 200 }}>
65
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
66
+ </Div>
67
+ <Div style={{ ...caseStyle, width: 240 }}>
68
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
69
+ </Div>
70
+ <Div style={{ ...caseStyle, width: 150 }}>
71
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
72
+ </Div>
73
+ <Div style={{ ...caseStyle, width: 240 }}>
74
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
75
+ </Div>
76
+ <Div style={{ ...caseStyle, width: 150 }}>
77
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
78
+ </Div>
79
+ </Carousel>
80
+ )
81
+ ```
82
+
83
+ ## Adaptability
84
+
85
+ If you need clear boundaries
86
+ There is a property for this - isResponsive (by default - false)
87
+ In conjunction with this property, each children needs to minWidth and maxWidth
88
+ The component itself will calculate how many blocks need to be displayed for a specific resolution
89
+ For example, when minWidth 100 and maxWidth 500, on the desktop extension there will be 2 blocks, on mobile 1
90
+
91
+ ```jsx example noscroll
92
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
93
+ const caseStyle = {
94
+ height: 180,
95
+ paddingLeft: 4,
96
+ paddingRight: 4,
97
+ minWidth: 100,
98
+ maxWidth: 500
99
+ }
100
+ const itemStyle = { width: '100%', height: '100%' }
101
+
102
+ return (
103
+ <Carousel style={carouselStyle} isResponsive={true}>
104
+ <Div style={caseStyle}>
105
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
106
+ </Div>
107
+ <Div style={caseStyle}>
108
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
109
+ </Div>
110
+ <Div style={caseStyle}>
111
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
112
+ </Div>
113
+ <Div style={caseStyle}>
114
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
115
+ </Div>
116
+ </Carousel>
117
+ )
118
+ ```
119
+
120
+ To use 1 visible block, children minWidth to be 100%
121
+
122
+ ```jsx example noscroll
123
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
124
+ const caseStyle = {
125
+ height: 180,
126
+ paddingLeft: 4,
127
+ paddingRight: 4,
128
+ minWidth: '100%'
129
+ }
130
+ const itemStyle = { width: '100%', height: '100%' }
131
+
132
+ return (
133
+ <Carousel style={carouselStyle} isResponsive={true}>
134
+ <Div style={caseStyle}>
135
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
136
+ </Div>
137
+ <Div style={caseStyle}>
138
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
139
+ </Div>
140
+ <Div style={caseStyle}>
141
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
142
+ </Div>
143
+ <Div style={caseStyle}>
144
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
145
+ </Div>
146
+ </Carousel>
147
+ )
148
+ ```
149
+
150
+ ## Infinite scrolling
151
+
152
+ Set by isEndless property (default is false)
153
+ Without isResponsive property, infinite scrolling works only on arrow clicks
154
+
155
+ ```jsx example noscroll
156
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
157
+ const caseStyle = {
158
+ height: 180,
159
+ paddingLeft: 4,
160
+ paddingRight: 4,
161
+ minWidth: 100,
162
+ maxWidth: 500
163
+ }
164
+ const itemStyle = { width: '100%', height: '100%' }
165
+
166
+ return (
167
+ <Carousel style={carouselStyle} isEndless={true}>
168
+ <Div style={{ ...caseStyle, width: 400 }}>
169
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
170
+ </Div>
171
+ <Div style={{ ...caseStyle, width: 260 }}>
172
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
173
+ </Div>
174
+ <Div style={{ ...caseStyle, width: 320 }}>
175
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
176
+ </Div>
177
+ <Div style={{ ...caseStyle, width: 200 }}>
178
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
179
+ </Div>
180
+ <Div style={{ ...caseStyle, width: 140 }}>
181
+ <Div style={{ ...itemStyle, backgroundColor: 'green' }} />
182
+ </Div>
183
+ <Div style={{ ...caseStyle, width: 180 }}>
184
+ <Div style={{ ...itemStyle, backgroundColor: 'black' }} />
185
+ </Div>
186
+ </Carousel>
187
+ )
188
+ ```
189
+
190
+ With isResponsive property, works also through swipe
191
+
192
+ ```jsx example noscroll
193
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
194
+ const caseStyle = {
195
+ height: 180,
196
+ paddingLeft: 4,
197
+ paddingRight: 4,
198
+ minWidth: 100,
199
+ maxWidth: 500
200
+ }
201
+ const itemStyle = { width: '100%', height: '100%' }
202
+
203
+ return (
204
+ <Carousel
205
+ style={carouselStyle}
206
+ isResponsive={true}
207
+ isEndless={true}
208
+ hasDots={true}
209
+ >
210
+ <Div style={caseStyle}>
211
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
212
+ </Div>
213
+ <Div style={caseStyle}>
214
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
215
+ </Div>
216
+ <Div style={caseStyle}>
217
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
218
+ </Div>
219
+ <Div style={caseStyle}>
220
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
221
+ </Div>
222
+ </Carousel>
223
+ )
224
+ ```
225
+
226
+ ## Swipe
227
+
228
+ The ability to scroll with the mouse or finger is set by isSwipe
229
+ The default is true, and you can always turn it off:
230
+
231
+ ```jsx example noscroll
232
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
233
+ const caseStyle = { height: 180, paddingLeft: 4, paddingRight: 4 }
234
+ const itemStyle = { width: '100%', height: '100%' }
235
+
236
+ return (
237
+ <Carousel style={carouselStyle} isSwipe={false}>
238
+ <Div style={{ ...caseStyle, width: 400 }}>
239
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
240
+ </Div>
241
+ <Div style={{ ...caseStyle, width: 260 }}>
242
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
243
+ </Div>
244
+ <Div style={{ ...caseStyle, width: 320 }}>
245
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
246
+ </Div>
247
+ <Div style={{ ...caseStyle, width: 200 }}>
248
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
249
+ </Div>
250
+ </Carousel>
251
+ )
252
+ ```
253
+
254
+ ## Display arrows and dots
255
+
256
+ Set by properties hasArrows (default is true) and hasDots (default is false)
257
+ Without isResponsive - hasDots always false
258
+
259
+ ```jsx example noscroll
260
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
261
+ const caseStyle = { minWidth: '100%', height: 180, paddingLeft: 4, paddingRight: 4 }
262
+ const itemStyle = { width: '100%', height: '100%' }
263
+
264
+ return (
265
+ <Carousel
266
+ style={carouselStyle}
267
+ hasArrows={false}
268
+ hasDots={true}
269
+ isResponsive={true}
270
+ >
271
+ <Div style={caseStyle}>
272
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
273
+ </Div>
274
+ <Div style={caseStyle}>
275
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
276
+ </Div>
277
+ <Div style={caseStyle}>
278
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
279
+ </Div>
280
+ <Div style={caseStyle}>
281
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
282
+ </Div>
283
+ </Carousel>
284
+ )
285
+ ```
286
+
287
+ ## Auto scroll
288
+
289
+ Set by isLoop property (default is false)
290
+
291
+ ```jsx example noscroll
292
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
293
+ const caseStyle = { minWidth: '100%', height: 180, paddingLeft: 4, paddingRight: 4 }
294
+ const itemStyle = { width: '100%', height: '100%' }
295
+
296
+ return (
297
+ <Carousel
298
+ style={carouselStyle}
299
+ hasDots={true}
300
+ isLoop={true}
301
+ isEndless={true}
302
+ isResponsive={true}
303
+ >
304
+ <Div style={caseStyle}>
305
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
306
+ </Div>
307
+ <Div style={caseStyle}>
308
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
309
+ </Div>
310
+ <Div style={caseStyle}>
311
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
312
+ </Div>
313
+ <Div style={caseStyle}>
314
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
315
+ </Div>
316
+ </Carousel>
317
+ )
318
+ ```
319
+
320
+ ## Start value
321
+
322
+ Set by startIndex (Number) property
323
+
324
+ ```jsx example noscroll
325
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
326
+ const caseStyle = { minWidth: '100%', height: 180, paddingLeft: 4, paddingRight: 4 }
327
+ const itemStyle = { width: '100%', height: '100%' }
328
+
329
+ return (
330
+ <Carousel
331
+ style={carouselStyle}
332
+ hasDots={true}
333
+ isResponsive={true}
334
+ startIndex={2}
335
+ >
336
+ <Div style={caseStyle}>
337
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
338
+ </Div>
339
+ <Div style={caseStyle}>
340
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
341
+ </Div>
342
+ <Div style={caseStyle}>
343
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
344
+ </Div>
345
+ <Div style={caseStyle}>
346
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
347
+ </Div>
348
+ <Div style={caseStyle}>
349
+ <Div style={{ ...itemStyle, backgroundColor: 'green' }} />
350
+ </Div>
351
+ <Div style={caseStyle}>
352
+ <Div style={{ ...itemStyle, backgroundColor: 'black' }} />
353
+ </Div>
354
+ </Carousel>
355
+ )
356
+ ```
357
+
358
+ ## Styling arrows
359
+
360
+ Set with arrowBackStyle and arrowNextStyle
361
+
362
+ ```jsx example noscroll
363
+ const carouselStyle = { width: '80%', alignSelf: 'center' }
364
+ const caseStyle = { width: 200, height: 180, paddingLeft: 4, paddingRight: 4 }
365
+ const itemStyle = { width: '100%', height: '100%' }
366
+ const arrowBackStyle = {
367
+ left: -50,
368
+ backgroundColor: 'transparent',
369
+ border: '1px solid #333333',
370
+ color: '#333333'
371
+ }
372
+ const arrowNextStyle = {
373
+ right: -50,
374
+ backgroundColor: 'transparent',
375
+ border: '1px solid #333333',
376
+ color: '#333333'
377
+ }
378
+
379
+ return (
380
+ <Carousel
381
+ style={carouselStyle}
382
+ arrowBackStyle={arrowBackStyle}
383
+ arrowNextStyle={arrowNextStyle}
384
+ >
385
+ <Div style={caseStyle}>
386
+ <Div style={{ ...itemStyle, backgroundColor: 'blue' }} />
387
+ </Div>
388
+ <Div style={caseStyle}>
389
+ <Div style={{ ...itemStyle, backgroundColor: 'red' }} />
390
+ </Div>
391
+ <Div style={caseStyle}>
392
+ <Div style={{ ...itemStyle, backgroundColor: 'yellow' }} />
393
+ </Div>
394
+ <Div style={caseStyle}>
395
+ <Div style={{ ...itemStyle, backgroundColor: 'gray' }} />
396
+ </Div>
397
+ <Div style={caseStyle}>
398
+ <Div style={{ ...itemStyle, backgroundColor: 'green' }} />
399
+ </Div>
400
+ <Div style={caseStyle}>
401
+ <Div style={{ ...itemStyle, backgroundColor: 'black' }} />
402
+ </Div>
403
+ </Carousel>
404
+ )
405
+ ```
406
+
407
+ If you need full customization of arrows or points, can be transferred ref, it will contain functions to switch: toBack, toNext, toIndex
408
+
409
+ ```jsx example noscroll
410
+ const ref = useRef()
411
+ const [slide, setSlide] = useState(0)
412
+ const carouselStyle = { width: '100%', marginLeft: 4, marginRight: 4 }
413
+ const caseStyle = {
414
+ minWidth: '100%',
415
+ width: 200,
416
+ height: 180,
417
+ paddingLeft: 4,
418
+ paddingRight: 4
419
+ }
420
+ const itemStyle = { width: '100%', height: '100%' }
421
+ const arrowStyle = {
422
+ backgroundColor: '#333',
423
+ alignItems: 'center',
424
+ justifyContent: 'center',
425
+ height: 20,
426
+ width: 20,
427
+ paddingBottom: 2
428
+ }
429
+
430
+ const dotStyle = {
431
+ backgroundColor: '#333',
432
+ width: 10,
433
+ height: 10,
434
+ border: '2px solid #333',
435
+ marginLeft: 2,
436
+ marginRight: 2,
437
+ borderRadius: 30
438
+ }
439
+
440
+ const data = [
441
+ { backgroundColor: 'blue' },
442
+ { backgroundColor: 'red' },
443
+ { backgroundColor: 'yellow' },
444
+ { backgroundColor: 'gray' },
445
+ { backgroundColor: 'green' },
446
+ { backgroundColor: 'black' }
447
+ ]
448
+
449
+ return (
450
+ <Div style={{ overflow: 'hidden' }}>
451
+ <Div row style={{ marginBottom: 4 }}>
452
+ <Div style={arrowStyle} onPress={()=> ref.current.toBack()}>
453
+ <Text style={{ color: '#fff' }}>{'<'}</Text>
454
+ </Div>
455
+ <Div style={arrowStyle} onPress={()=> ref.current.toNext()}>
456
+ <Text style={{ color: '#fff' }}>{'>'}</Text>
457
+ </Div>
458
+ </Div>
459
+ <Carousel
460
+ ref={ref}
461
+ style={{ marginLeft: -4, marginRight: -4 }}
462
+ hasArrows={false}
463
+ isResponsive={true}
464
+ onChange={v => setSlide(v)}
465
+ >
466
+ {
467
+ data.map((item, index) => (
468
+ <Div key={index} style={caseStyle}>
469
+ <Div style={{ ...itemStyle, ...item }} />
470
+ </Div>
471
+ ))
472
+ }
473
+ </Carousel>
474
+ <Div row style={{ alignSelf: 'center', marginTop: 8 }}>
475
+ {
476
+ data.map((item, index) => (
477
+ <Div
478
+ key={index}
479
+ style={{
480
+ ...dotStyle,
481
+ ...(slide === index ? { backgroundColor: '#fff' } : {})
482
+ }}
483
+ onPress={()=> ref.current.toIndex(index)}
484
+ />
485
+ ))
486
+ }
487
+ </Div>
488
+ </Div>
489
+ )
490
+ ```
491
+
492
+ ## Vertical mode
493
+
494
+ ```jsx example
495
+ const carouselStyle = { marginLeft: 4, marginRight: 4 }
496
+ const caseStyle = {
497
+ height: 56,
498
+ width: 72,
499
+ paddingLeft: 4,
500
+ paddingRight: 4
501
+ }
502
+
503
+ const itemStyle = {
504
+ width: '100%',
505
+ height: '100%',
506
+ justifyContent: 'center',
507
+ alignItems: 'center'
508
+ }
509
+
510
+ const textStyle = { color: '#fff' }
511
+
512
+ const data = new Array(24).fill(true).map(Number)
513
+
514
+ return (
515
+ <Carousel
516
+ style={{ height: 240, width: 72 }}
517
+ variant='vertical'
518
+ isEndless
519
+ hasArrows
520
+ >
521
+ {
522
+ data.map((item, index) => (
523
+ <Div key={index} style={caseStyle}>
524
+ <Div style={itemStyle}>
525
+ <Span style={textStyle}>{index + 1}</Span>
526
+ </Div>
527
+ </Div>
528
+ ))
529
+ }
530
+ </Carousel>
531
+ )
532
+ ```
533
+
534
+ ## Sandbox
535
+
536
+ <Sandbox
537
+ Component={Carousel}
538
+ propsJsonSchema={CarouselPropsJsonSchema}
539
+ props={{
540
+ isResponsive: true,
541
+ hasDots: true,
542
+ style: { height: 180, width: '100%' }
543
+ }}
544
+ />
@@ -0,0 +1,67 @@
1
+ .wrapper
2
+ flex-grow 1
3
+ flex-shrink 1
4
+ flex-direction column
5
+
6
+ .carousel
7
+ flex-grow 1
8
+ flex-shrink 1
9
+ flex-direction row
10
+
11
+ &.vertical
12
+ flex-direction column
13
+
14
+ .arrow
15
+ background-color var(--Carousel-arrowWrapperBg)
16
+ align-self center
17
+ cursor pointer
18
+ position absolute
19
+ user-select none
20
+ z-index 999
21
+ border-radius 1u
22
+ padding .5u
23
+
24
+ &.vertical
25
+ position relative
26
+
27
+ .arrowNext
28
+ right 3u
29
+
30
+ &.vertical
31
+ right 0
32
+
33
+ .arrowBack
34
+ left 3u
35
+
36
+ &.vertical
37
+ left 0
38
+
39
+ .root
40
+ overflow hidden
41
+ flex-direction row
42
+ flex-grow 1
43
+ flex-shrink 1
44
+
45
+ &.vertical
46
+ flex-direction column
47
+
48
+ .case
49
+ flex-direction row
50
+
51
+ &.vertical
52
+ flex-direction column
53
+
54
+ .dots
55
+ align-self center
56
+ margin 2u 0
57
+
58
+ .dot
59
+ height 1u
60
+ width 2u
61
+ background-color var(--color-bg-main-subtle)
62
+ border-radius .3u
63
+ margin 0 .4u
64
+ cursor pointer
65
+
66
+ .dotActive
67
+ background-color var(--color-bg-primary)
package/index.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ /* eslint-disable */
2
+ // DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
3
+
4
+ import React, { type Ref } from 'react';
5
+ import { type StyleProp, type ViewStyle } from 'react-native';
6
+ import './index.cssx.styl';
7
+ export declare const _PropsJsonSchema: {};
8
+ export interface CarouselProps {
9
+ /** Custom styles applied to the outer wrapper */
10
+ style?: StyleProp<ViewStyle>;
11
+ /** Styles for the back arrow container and icon */
12
+ arrowBackStyle?: StyleProp<any>;
13
+ /** Styles for the next arrow container and icon */
14
+ arrowNextStyle?: StyleProp<any>;
15
+ /** Initial active slide index @default 0 */
16
+ startIndex?: number;
17
+ /** Direction of movement @default 'horizontal' */
18
+ variant?: 'horizontal' | 'vertical';
19
+ /** Enable swipe gestures @default true */
20
+ isSwipe?: boolean;
21
+ /** Auto-scroll slides in a loop @default false */
22
+ isLoop?: boolean;
23
+ /** Enable endless carousel mode @default false */
24
+ isEndless?: boolean;
25
+ /** Adjust slide sizes based on available space @default false */
26
+ isResponsive?: boolean;
27
+ /** Show navigation arrows @default true */
28
+ hasArrows?: boolean;
29
+ /** Show navigation dots (responsive only) @default false */
30
+ hasDots?: boolean;
31
+ /** Animation duration in ms @default 300 */
32
+ duration?: number;
33
+ /** Slides to render inside the carousel */
34
+ children?: any[];
35
+ /** Callback fired when active slide changes */
36
+ onChange?: (index: number) => void;
37
+ /** Ref exposing active child and navigation helpers */
38
+ ref?: Ref<any>;
39
+ }
40
+ declare const _default: React.ComponentType<CarouselProps>;
41
+ export default _default;
package/index.tsx ADDED
@@ -0,0 +1,622 @@
1
+ import React, {
2
+ useImperativeHandle,
3
+ useEffect,
4
+ useState,
5
+ useMemo,
6
+ useRef,
7
+ type ReactNode,
8
+ type Ref
9
+ } from 'react'
10
+ import {
11
+ Animated,
12
+ PanResponder,
13
+ StyleSheet,
14
+ View,
15
+ type StyleProp,
16
+ type ViewStyle
17
+ } from 'react-native'
18
+ import { pug, observer } from 'startupjs'
19
+ import { themed } from '@startupjs-ui/core'
20
+ import Div from '@startupjs-ui/div'
21
+ import Icon from '@startupjs-ui/icon'
22
+ import { faAngleLeft } from '@fortawesome/free-solid-svg-icons/faAngleLeft'
23
+ import { faAngleRight } from '@fortawesome/free-solid-svg-icons/faAngleRight'
24
+ import { faAngleUp } from '@fortawesome/free-solid-svg-icons/faAngleUp'
25
+ import { faAngleDown } from '@fortawesome/free-solid-svg-icons/faAngleDown'
26
+ import './index.cssx.styl'
27
+
28
+ /* eslint-disable react-hooks/exhaustive-deps */
29
+ export const _PropsJsonSchema = {/* CarouselProps */}
30
+
31
+ export interface CarouselProps {
32
+ /** Custom styles applied to the outer wrapper */
33
+ style?: StyleProp<ViewStyle>
34
+ /** Styles for the back arrow container and icon */
35
+ arrowBackStyle?: StyleProp<any>
36
+ /** Styles for the next arrow container and icon */
37
+ arrowNextStyle?: StyleProp<any>
38
+ /** Initial active slide index @default 0 */
39
+ startIndex?: number
40
+ /** Direction of movement @default 'horizontal' */
41
+ variant?: 'horizontal' | 'vertical'
42
+ /** Enable swipe gestures @default true */
43
+ isSwipe?: boolean
44
+ /** Auto-scroll slides in a loop @default false */
45
+ isLoop?: boolean
46
+ /** Enable endless carousel mode @default false */
47
+ isEndless?: boolean
48
+ /** Adjust slide sizes based on available space @default false */
49
+ isResponsive?: boolean
50
+ /** Show navigation arrows @default true */
51
+ hasArrows?: boolean
52
+ /** Show navigation dots (responsive only) @default false */
53
+ hasDots?: boolean
54
+ /** Animation duration in ms @default 300 */
55
+ duration?: number
56
+ /** Slides to render inside the carousel */
57
+ children?: any[]
58
+ /** Callback fired when active slide changes */
59
+ onChange?: (index: number) => void
60
+ /** Ref exposing active child and navigation helpers */
61
+ ref?: Ref<any>
62
+ }
63
+
64
+ function Carousel ({
65
+ style,
66
+ arrowBackStyle = {},
67
+ arrowNextStyle = {},
68
+ startIndex = 0,
69
+ variant = 'horizontal',
70
+ isSwipe = true,
71
+ isLoop = false,
72
+ isEndless = false,
73
+ isResponsive = false,
74
+ hasArrows = true,
75
+ hasDots = false,
76
+ duration = 300,
77
+ children = [],
78
+ onChange,
79
+ ref
80
+ }: CarouselProps): ReactNode {
81
+ arrowBackStyle = StyleSheet.flatten(arrowBackStyle)
82
+ arrowNextStyle = StyleSheet.flatten(arrowNextStyle)
83
+ const backArrowColor = arrowBackStyle?.color || '#eeeeee'
84
+ const nextArrowColor = arrowNextStyle?.color || '#eeeeee'
85
+
86
+ const refRoot = useRef<any>(null)
87
+ const refCase = useRef<any>(null)
88
+ const refTimeout = useRef<any>(null)
89
+ const childrenInfo = useRef<any[]>([])
90
+ const coardName = variant === 'horizontal' ? 'x' : 'y'
91
+ const sideName = variant === 'horizontal' ? 'width' : 'height'
92
+
93
+ const childrenRefs = useMemo(() => {
94
+ const _refs: Record<number, any> = {}
95
+ children.forEach((c, i) => {
96
+ _refs[i] = React.createRef()
97
+ })
98
+ return _refs
99
+ }, [children])
100
+
101
+ const validChildren = useMemo(() => {
102
+ return getValidChildren({ children, isEndless, isResponsive, childrenRefs })
103
+ }, [children, childrenRefs, isEndless, isResponsive])
104
+
105
+ const [activeIndex, setActiveIndex] = useState(startIndex)
106
+ const [rootInfo, setRootInfo] = useState<Record<string, number>>({})
107
+ const [caseInfo, setCaseInfo] = useState<Record<string, number>>({})
108
+ const [animateTranslate, setAnimateTranslate] = useState(new Animated.Value(0))
109
+ const [caseStyle, setCaseStyle] = useState<Record<string, any>>({})
110
+
111
+ const [startDrag, setStartDrag] = useState<any>(null)
112
+ const [endDrag, setEndDrag] = useState<any>(null)
113
+ const [isRender, setIsRender] = useState(false)
114
+ const [isAnimate, setIsAnimate] = useState(false)
115
+
116
+ // eslint-disable-next-line react-hooks/exhaustive-deps
117
+ useImperativeHandle(ref, () => {
118
+ return new Proxy(refRoot.current, {
119
+ get (target: any, prop: any) {
120
+ const actualChildIndex = activeIndex % children.length
121
+ const activeRef = childrenRefs[actualChildIndex].current
122
+ if (prop === 'getChildByIndex') return getChildByIndex
123
+ if (prop === 'activeChild') return activeRef
124
+ if (prop === 'element') return refRoot.current
125
+ if (prop === 'toBack') return onBack
126
+ if (prop === 'toNext') return onNext
127
+ if (prop === 'toIndex') return toIndex
128
+ if (target?.[prop]) return target[prop]
129
+ return activeRef?.[prop]
130
+ }
131
+ })
132
+ }, [
133
+ activeIndex,
134
+ rootInfo,
135
+ caseInfo,
136
+ isAnimate,
137
+ isRender
138
+ ])
139
+
140
+ // eslint-disable-next-line react-hooks/exhaustive-deps
141
+ useEffect(() => {
142
+ if (isRender && isLoop && !isAnimate) {
143
+ refTimeout.current = setTimeout(onNext, 3000)
144
+ } else {
145
+ clearTimeout(refTimeout.current)
146
+ }
147
+ }, [isRender, isLoop, isAnimate, activeIndex])
148
+
149
+ function getChildByIndex (index: number) {
150
+ return childrenRefs[index].current
151
+ }
152
+
153
+ function onLayoutChild ({ nativeEvent }: any, index: number) {
154
+ childrenInfo.current[index] = {
155
+ width: Math.round(nativeEvent.layout.width),
156
+ height: Math.round(nativeEvent.layout.height),
157
+ x: Math.round(nativeEvent.layout.x),
158
+ y: Math.round(nativeEvent.layout.y)
159
+ }
160
+
161
+ if (childrenInfo.current.length === validChildren.length && !isRender) {
162
+ const isChildNotExists = childrenInfo.current.includes(undefined)
163
+ if (isChildNotExists) return
164
+
165
+ if (isEndless && isResponsive) {
166
+ setActiveIndex(children.length + startIndex)
167
+
168
+ const coard = childrenInfo.current[children.length + startIndex] &&
169
+ -childrenInfo.current[children.length + startIndex][coardName]
170
+ animateTranslate.setValue(coard || 0)
171
+ } else {
172
+ setActiveIndex(startIndex)
173
+
174
+ const coard = childrenInfo.current[startIndex] &&
175
+ -childrenInfo.current[startIndex][coardName]
176
+ animateTranslate.setValue(coard || 0)
177
+ }
178
+
179
+ setIsRender(true)
180
+ }
181
+ }
182
+
183
+ function toIndex (index: number) {
184
+ setIsAnimate(true)
185
+
186
+ if (isEndless && isResponsive) {
187
+ index = index + children.length
188
+ }
189
+
190
+ let activeElement: any = childrenInfo.current[index]
191
+ activeElement.index = index
192
+
193
+ let toValue = -activeElement[coardName]
194
+ if (caseInfo[sideName] - activeElement[coardName] < rootInfo[sideName]) {
195
+ activeElement = getClosest({
196
+ childrenInfo: childrenInfo.current,
197
+ newPosition: caseInfo[sideName] - rootInfo[sideName],
198
+ coardName,
199
+ sideName
200
+ })
201
+ toValue = rootInfo[sideName] - caseInfo[sideName]
202
+ }
203
+
204
+ setActiveIndex(activeElement.index)
205
+ onChange && onChange(activeElement.index % children.length)
206
+ Animated.timing(animateTranslate, { toValue, duration, useNativeDriver: false }).start(() => {
207
+ setIsAnimate(false)
208
+ })
209
+ }
210
+
211
+ function onBack () {
212
+ if ((activeIndex === 0 &&
213
+ (animateTranslate as any)._value === 0 &&
214
+ !isEndless) || isAnimate) return
215
+
216
+ setIsAnimate(true)
217
+
218
+ let activeElement: any = childrenInfo.current[activeIndex - 1]
219
+ if (isResponsive) {
220
+ activeElement.index = activeIndex - 1
221
+ } else {
222
+ const newPosition = childrenInfo.current[activeIndex][coardName] +
223
+ childrenInfo.current[activeIndex][sideName] - rootInfo[sideName]
224
+
225
+ activeElement = getClosest({
226
+ childrenInfo: childrenInfo.current,
227
+ coardName,
228
+ sideName,
229
+ newPosition
230
+ })
231
+ }
232
+
233
+ let toValue = -activeElement[coardName]
234
+
235
+ if (isEndless && !isResponsive && (activeIndex === 0)) {
236
+ activeElement = getClosest({
237
+ childrenInfo: childrenInfo.current,
238
+ newPosition: caseInfo[sideName] - rootInfo[sideName],
239
+ coardName,
240
+ sideName
241
+ })
242
+ toValue = rootInfo[sideName] - caseInfo[sideName]
243
+ }
244
+
245
+ if (isEndless && isResponsive && (activeIndex - 1 < children.length)) {
246
+ const index = activeElement.index * 2 + 1
247
+ setActiveIndex(index)
248
+ onChange && onChange(index % children.length)
249
+ } else {
250
+ setActiveIndex(activeElement.index)
251
+ onChange && onChange(activeElement.index % children.length)
252
+ }
253
+
254
+ Animated.timing(animateTranslate, { toValue, duration, useNativeDriver: false }).start(() => {
255
+ if (isEndless && isResponsive && activeIndex - 1 < children.length) {
256
+ animateTranslate.setValue(-childrenInfo.current[activeElement.index * 2 + 1][coardName])
257
+ }
258
+
259
+ setIsAnimate(false)
260
+ })
261
+ }
262
+
263
+ function onNext () {
264
+ if (isAnimate) return
265
+ if (!childrenInfo.current[activeIndex + 1] && !isEndless) return
266
+ if ((caseInfo[sideName] - childrenInfo.current[activeIndex][coardName] <= rootInfo[sideName]) && !isEndless) {
267
+ return
268
+ }
269
+
270
+ setIsAnimate(true)
271
+
272
+ let activeElement: any = childrenInfo.current[activeIndex + 1]
273
+ if (isResponsive) {
274
+ activeElement.index = activeIndex + 1
275
+ } else {
276
+ if (childrenInfo.current[activeIndex][coardName] + childrenInfo.current[activeIndex][sideName] > rootInfo[sideName]) {
277
+ activeElement.index = activeIndex + 1
278
+ } else {
279
+ activeElement = getClosest({
280
+ childrenInfo: childrenInfo.current,
281
+ newPosition: childrenInfo.current[activeIndex][coardName] + rootInfo[sideName],
282
+ coardName,
283
+ sideName
284
+ })
285
+ }
286
+ }
287
+
288
+ let toValue = -activeElement[coardName]
289
+ if (caseInfo[sideName] - activeElement[coardName] < rootInfo[sideName]) {
290
+ activeElement = getClosest({
291
+ childrenInfo: childrenInfo.current,
292
+ newPosition: caseInfo[sideName] - rootInfo[sideName],
293
+ coardName,
294
+ sideName
295
+ })
296
+ toValue = -(caseInfo[sideName] - rootInfo[sideName])
297
+ }
298
+
299
+ if (isEndless && !isResponsive && ((animateTranslate as any)._value === -(caseInfo[sideName] - rootInfo[sideName]))) {
300
+ activeElement = childrenInfo.current[0]
301
+ activeElement.index = 0
302
+ toValue = activeElement[coardName]
303
+ }
304
+
305
+ if (isEndless && isResponsive && activeIndex + 1 >= (children.length * 2)) {
306
+ setActiveIndex(children.length)
307
+ onChange && onChange(0)
308
+ } else {
309
+ setActiveIndex(activeElement.index)
310
+ onChange && onChange(activeElement.index % children.length)
311
+ }
312
+
313
+ Animated.timing(animateTranslate, { toValue, duration, useNativeDriver: false }).start(() => {
314
+ if (isEndless && isResponsive && activeIndex + 1 >= (children.length * 2)) {
315
+ animateTranslate.setValue(-childrenInfo.current[children.length][coardName])
316
+ }
317
+
318
+ setIsAnimate(false)
319
+ })
320
+ }
321
+
322
+ // eslint-disable-next-line react-hooks/exhaustive-deps
323
+ useEffect(() => {
324
+ if (!endDrag) return
325
+ if (startDrag === endDrag) return
326
+ setIsAnimate(true)
327
+
328
+ let _endDrag = endDrag
329
+ if (-endDrag > caseInfo[sideName]) {
330
+ _endDrag = -caseInfo[sideName]
331
+ }
332
+
333
+ let activeElement: any = getClosest({
334
+ childrenInfo: childrenInfo.current,
335
+ newPosition: -_endDrag,
336
+ coardName,
337
+ sideName
338
+ })
339
+ const side = (startDrag > _endDrag) ? 'next' : 'back'
340
+
341
+ if (activeElement.index === activeIndex) {
342
+ if (side === 'next') {
343
+ if (!childrenInfo.current[activeIndex + 1]) {
344
+ activeElement = childrenInfo.current[activeIndex]
345
+ } else {
346
+ activeElement = childrenInfo.current[activeIndex + 1]
347
+ activeElement.index = activeIndex + 1
348
+ }
349
+ } else if (activeIndex - 1 >= 0) {
350
+ activeElement = childrenInfo.current[activeIndex - 1]
351
+ activeElement.index = activeIndex - 1
352
+ }
353
+ }
354
+
355
+ let toValue = -activeElement[coardName]
356
+ if (caseInfo[sideName] - activeElement[coardName] < rootInfo[sideName]) {
357
+ activeElement = getClosest({
358
+ childrenInfo: childrenInfo.current,
359
+ newPosition: caseInfo[sideName] - rootInfo[sideName],
360
+ coardName,
361
+ sideName
362
+ })
363
+ toValue = -(caseInfo[sideName] - rootInfo[sideName])
364
+ }
365
+
366
+ if (side === 'back' && isEndless && isResponsive && (activeIndex - 1 < children.length)) {
367
+ const index = activeElement.index * 2 + 1
368
+ setActiveIndex(index)
369
+ onChange && onChange(index % children.length)
370
+ } else if (side === 'next' && isEndless && isResponsive && (activeIndex + 1 >= (children.length * 2))) {
371
+ setActiveIndex(children.length)
372
+ onChange && onChange(0)
373
+ } else {
374
+ setActiveIndex(activeElement.index)
375
+ onChange && onChange(activeElement.index % children.length)
376
+ }
377
+
378
+ Animated.timing(animateTranslate, { toValue, duration, useNativeDriver: false }).start(() => {
379
+ if (side === 'back' && isEndless && isResponsive && activeIndex - 1 < children.length) {
380
+ animateTranslate.setValue(-childrenInfo.current[activeElement.index * 2 + 1][coardName])
381
+ }
382
+
383
+ if (side === 'next' && isEndless && isResponsive && (activeIndex + 1 >= (children.length * 2))) {
384
+ animateTranslate.setValue(-childrenInfo.current[children.length][coardName])
385
+ }
386
+
387
+ setIsAnimate(false)
388
+ })
389
+ }, [endDrag])
390
+
391
+ const panResponder = useMemo(() => {
392
+ if (isSwipe && !isAnimate) {
393
+ return PanResponder.create({
394
+ onStartShouldSetPanResponder: () => true,
395
+ onMoveShouldSetPanResponderCapture: (_, gestureState) => {
396
+ if (!(gestureState.dx === 0 && gestureState.dy === 0)) {
397
+ setCaseStyle({ pointerEvents: 'box-only' })
398
+ return true
399
+ } else {
400
+ return false
401
+ }
402
+ },
403
+ onPanResponderTerminationRequest: () => false,
404
+ onShouldBlockNativeResponder: () => false,
405
+ onPanResponderGrant: e => {
406
+ setAnimateTranslate(animateTranslate => {
407
+ setStartDrag((animateTranslate as any)._value)
408
+ return animateTranslate
409
+ })
410
+ },
411
+ onPanResponderMove: (e, gesture) => {
412
+ const delta = (gesture as any)['d' + coardName]
413
+ setStartDrag((startDrag: any) => {
414
+ animateTranslate.setValue(startDrag + delta)
415
+ return startDrag
416
+ })
417
+ },
418
+ onPanResponderEnd: () => {
419
+ setAnimateTranslate(animateTranslate => {
420
+ setEndDrag((animateTranslate as any)._value)
421
+ setCaseStyle({})
422
+ return animateTranslate
423
+ })
424
+ }
425
+ })
426
+ }
427
+
428
+ return { panHandlers: {} }
429
+ }, [animateTranslate, coardName, isAnimate, isSwipe])
430
+
431
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
432
+ const renderChildren = React.Children.toArray(validChildren).map((child: any, index, arr) => {
433
+ const _style = StyleSheet.flatten([child.props.style])
434
+
435
+ const sideCapitalLetter = sideName[0].toUpperCase() + sideName.slice(1)
436
+
437
+ if (isResponsive) {
438
+ if ((!_style['min' + sideCapitalLetter] || !_style['max' + sideCapitalLetter]) && _style['min' + sideCapitalLetter] !== '100%') {
439
+ console.error('isResponsive need minWidth and maxWidth in children')
440
+ return null
441
+ }
442
+
443
+ if (!rootInfo[sideName]) return null
444
+
445
+ let step = 1
446
+ let _side
447
+
448
+ if (_style['min' + sideCapitalLetter] === '100%') {
449
+ _style['min' + sideCapitalLetter] = rootInfo[sideName]
450
+ _style['max' + sideCapitalLetter] = rootInfo[sideName]
451
+ _side = rootInfo[sideName]
452
+ step = -1
453
+ }
454
+
455
+ while (step !== -1) {
456
+ _side = rootInfo[sideName] / step
457
+
458
+ if (isNaN(_side)) break
459
+ if (step > 10) {
460
+ console.error('no valid minSide/maxSide')
461
+ break
462
+ }
463
+ if ((_side >= _style['min' + sideCapitalLetter]) && (_side <= _style['max' + sideCapitalLetter])) {
464
+ break
465
+ }
466
+
467
+ step++
468
+ }
469
+
470
+ if (_side && !isNaN(_side)) _style[sideName] = _side
471
+ }
472
+
473
+ child = React.cloneElement(child, { style: _style })
474
+
475
+ return pug`
476
+ View(
477
+ key=index
478
+ onLayout=e=> onLayoutChild(e, index)
479
+ )= child
480
+ `
481
+ })
482
+
483
+ function onLayoutRoot ({ nativeEvent }: any) {
484
+ setRootInfo({
485
+ width: Math.round(nativeEvent.layout.width),
486
+ height: Math.round(nativeEvent.layout.height)
487
+ })
488
+ }
489
+
490
+ function onLayoutCase ({ nativeEvent }: any) {
491
+ setCaseInfo({
492
+ width: Math.round(nativeEvent.layout.width),
493
+ height: Math.round(nativeEvent.layout.height)
494
+ })
495
+ }
496
+
497
+ const dots = getDotsArray({
498
+ children,
499
+ childrenInfo: childrenInfo.current,
500
+ coardName,
501
+ sideName,
502
+ rootInfo,
503
+ caseInfo,
504
+ isRender,
505
+ isEndless,
506
+ isResponsive
507
+ })
508
+
509
+ return pug`
510
+ Div.wrapper(style=[style, { opacity: isRender ? 1 : 0 }])
511
+ Div.carousel(styleName=variant)
512
+ if hasArrows
513
+ Div.arrow.arrowBack(
514
+ style=arrowBackStyle
515
+ styleName=variant
516
+ onPress=onBack
517
+ )
518
+ Icon.icon(
519
+ style={ color: backArrowColor }
520
+ icon=variant === 'vertical' ? faAngleUp : faAngleLeft
521
+ size='xxl'
522
+ )
523
+ View.root(
524
+ ref=refRoot
525
+ styleName=variant
526
+ onLayout=onLayoutRoot
527
+ )
528
+ Animated.View.case(
529
+ ref=refCase
530
+ ...panResponder.panHandlers
531
+ style=[
532
+ {
533
+ transform: [{
534
+ ['translate' + coardName.toUpperCase()]: animateTranslate
535
+ }]
536
+ },
537
+ caseStyle
538
+ ]
539
+ styleName=variant
540
+ onLayout=onLayoutCase
541
+ )= renderChildren
542
+ if hasArrows
543
+ Div.arrow.arrowNext(
544
+ style=arrowNextStyle
545
+ styleName=variant
546
+ onPress=onNext
547
+ )
548
+ Icon.icon(
549
+ style={ color: nextArrowColor }
550
+ icon=variant === 'vertical' ? faAngleDown : faAngleRight
551
+ size='xxl'
552
+ )
553
+
554
+ if hasDots && isResponsive
555
+ Div.dots(row)
556
+ each _, index in dots
557
+ Div.dot(
558
+ key=index
559
+ onPress=()=> toIndex(index)
560
+ styleName={ dotActive: activeIndex === (isEndless ? (index + children.length) : index) }
561
+ )
562
+ `
563
+ }
564
+
565
+ function getClosest ({
566
+ childrenInfo,
567
+ newPosition,
568
+ coardName,
569
+ sideName
570
+ }: any) {
571
+ let activeElement: any = {}
572
+
573
+ for (let index = 0; index < childrenInfo.length; index++) {
574
+ const item = childrenInfo[index]
575
+
576
+ if (newPosition < item[coardName] && index === 0) {
577
+ activeElement = { ...item, index }
578
+ break
579
+ }
580
+
581
+ if (newPosition >= item[coardName] && newPosition <= (item[coardName] + item[sideName])) {
582
+ activeElement = { ...item, index }
583
+ }
584
+ }
585
+
586
+ return activeElement
587
+ }
588
+
589
+ function getDotsArray ({
590
+ children,
591
+ childrenInfo,
592
+ coardName,
593
+ sideName,
594
+ rootInfo,
595
+ caseInfo,
596
+ isRender,
597
+ isEndless,
598
+ isResponsive
599
+ }: any) {
600
+ if (!isRender) return []
601
+ if (isEndless && isResponsive) return childrenInfo.slice(children.length * 2)
602
+
603
+ return childrenInfo.filter((item: any) => {
604
+ if (!item) return false
605
+ return Math.round(item[coardName]) <= (caseInfo[sideName] - rootInfo[sideName])
606
+ })
607
+ }
608
+
609
+ function getValidChildren ({ children, isEndless, isResponsive, childrenRefs }: any) {
610
+ const childrenWithRefs = React.Children.toArray(children)
611
+ .map((child: any, index: number) => {
612
+ const childRef = childrenRefs[index]
613
+ return React.cloneElement(child, { ref: childRef })
614
+ })
615
+ if (isEndless && isResponsive) {
616
+ return [...children, ...childrenWithRefs, ...children]
617
+ }
618
+
619
+ return childrenWithRefs
620
+ }
621
+
622
+ export default observer(themed('Carousel', Carousel))
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@startupjs-ui/carousel",
3
+ "version": "0.1.3",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "main": "index.tsx",
8
+ "types": "index.d.ts",
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@startupjs-ui/core": "^0.1.3",
12
+ "@startupjs-ui/div": "^0.1.3",
13
+ "@startupjs-ui/icon": "^0.1.3"
14
+ },
15
+ "peerDependencies": {
16
+ "react": "*",
17
+ "react-native": "*",
18
+ "startupjs": "*"
19
+ },
20
+ "gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
21
+ }