Skip to content

Commit bd11a89

Browse files
committed
refactor(CCarousel): migrate to forwardRef, update props, fix animations
1 parent a27b51c commit bd11a89

File tree

6 files changed

+228
-282
lines changed

6 files changed

+228
-282
lines changed

src/components/carousel/CCarousel.tsx

Lines changed: 78 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, RefObject, HTMLAttributes, useState, useEffect, useRef } from 'react'
1+
import React, { forwardRef, HTMLAttributes, useState, useEffect, useRef } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44

@@ -8,132 +8,135 @@ export interface CCarouselProps extends HTMLAttributes<HTMLDivElement> {
88
*/
99
className?: string
1010
/**
11-
* Inner ref of main element. [docs]
11+
* Set 'animate' variable for created context. [docs]
1212
*
13-
* @type RefObject<HTMLDivElement> | {():void}
13+
* @type boolean
1414
*/
15-
innerRef?: RefObject<HTMLDivElement> | { (): void } // TODO: check
15+
animate?: boolean
1616
/**
17-
* index of the active item. [docs]
17+
* The amount of time to delay between automatically cycling an item. If false, carousel will not automatically cycle. [docs]
1818
*
19-
* @type number
19+
* @type boolean | number
2020
*/
21-
activeIndex?: number
21+
interval: boolean | number
2222
/**
23-
* Slide starts on beginning. [docs]
23+
* index of the active item. [docs]
2424
*
2525
* @type number
2626
*/
27-
autoSlide?: number
27+
index?: number
2828
/**
29-
* Set 'animate' variable for created context. [docs]
29+
* On slide change callback. [docs]
3030
*
31-
* @type boolean
31+
* @type (a:number|string|null)=>void
3232
*/
33-
animate?: boolean
33+
onSlideChange?: (a: number | string | null) => void
3434
/**
3535
* On slide change callback. [docs]
3636
*
37-
* @type (a:number|string|null)=>void
37+
* @type 'slide' | 'crossfade'
38+
* @default 'slide'
3839
*/
39-
onSlideChange?: (a: number | string | null) => void
40+
transition?: 'slide' | 'crossfade'
4041
}
4142

4243
interface DataType {
4344
timeout?: null | ReturnType<typeof setTimeout>
4445
}
4546

4647
export interface ContextType {
47-
itemNumber: number
48+
itemsNumber: number
4849
state: [number | null, number, string?]
4950
animating: boolean
5051
animate?: boolean
51-
setItemNumber: (a: number) => void
52+
setItemsNumber: (a: number) => void
5253
setAnimating: (a: boolean) => void
5354
setState: (a: [number | null, number, string?]) => void
5455
}
5556

5657
//
5758

5859
export const Context = React.createContext<ContextType>({
59-
itemNumber: 0,
60+
itemsNumber: 0,
6061
state: [null, 0],
6162
animating: false,
62-
setItemNumber: (_) => {},
63+
setItemsNumber: (_) => {},
6364
setAnimating: (_) => {},
6465
setState: (_) => {},
6566
})
6667

67-
export const CCarousel: FC<CCarouselProps> = ({
68-
className,
69-
children,
70-
innerRef,
71-
autoSlide,
72-
activeIndex = 0,
73-
animate,
74-
onSlideChange,
75-
...rest
76-
}) => {
77-
const [state, setState] = useState<[number | null, number, string?]>([null, activeIndex])
78-
const [itemNumber, setItemNumber] = useState<number>(0)
79-
const [animating, setAnimating] = useState<boolean>(false)
68+
export const CCarousel = forwardRef<HTMLDivElement, CCarouselProps>(
69+
(
70+
{ className, children, index = 0, animate = true, interval = 5000, onSlideChange, transition, ...rest },
71+
ref,
72+
) => {
73+
const [state, setState] = useState<[number | null, number, string?]>([null, index])
74+
const [itemsNumber, setItemsNumber] = useState<number>(0)
75+
const [animating, setAnimating] = useState<boolean>(false)
8076

81-
let data = useRef<DataType>({}).current
77+
let data = useRef<DataType>({}).current
8278

83-
const setNext = () => {
84-
reset()
85-
if (autoSlide) {
86-
data.timeout = setTimeout(() => nextItem(), autoSlide)
79+
const cycle = () => {
80+
pause()
81+
if (typeof interval === 'number') {
82+
data.timeout = setTimeout(() => nextItem(), interval)
83+
}
84+
}
85+
const pause = () => data.timeout && clearTimeout(data.timeout)
86+
const nextItem = () => {
87+
if (typeof state[1] === 'number')
88+
setState([state[1], itemsNumber === state[1] + 1 ? 0 : state[1] + 1, 'next'])
8789
}
88-
}
89-
const reset = () => data.timeout && clearTimeout(data.timeout)
90-
const nextItem = () => {
91-
if (typeof state[1] === 'number')
92-
setState([state[1], itemNumber === state[1] + 1 ? 0 : state[1] + 1, 'next'])
93-
}
9490

95-
useEffect(() => {
96-
setState([state[1], activeIndex])
97-
}, [activeIndex])
91+
useEffect(() => {
92+
setState([state[1], index])
93+
}, [index])
9894

99-
useEffect(() => {
100-
onSlideChange && onSlideChange(state[1])
101-
setNext()
102-
return () => {
103-
reset()
104-
}
105-
}, [state])
95+
useEffect(() => {
96+
onSlideChange && onSlideChange(state[1])
97+
cycle()
98+
return () => {
99+
pause()
100+
}
101+
}, [state])
106102

107-
// render
103+
// render
108104

109-
const classes = classNames('carousel', className)
105+
const _className = classNames(
106+
'carousel slide',
107+
transition === 'crossfade' && 'carousel-fade',
108+
className
109+
)
110110

111-
return (
112-
<div className={classes} onMouseEnter={reset} onMouseLeave={setNext} {...rest} ref={innerRef}>
113-
<Context.Provider
114-
value={{
115-
state,
116-
setState,
117-
animate,
118-
itemNumber,
119-
setItemNumber,
120-
animating,
121-
setAnimating,
122-
}}
123-
>
124-
{children}
125-
</Context.Provider>
126-
</div>
127-
)
128-
}
111+
return (
112+
<div className={_className} onMouseEnter={pause} onMouseLeave={cycle} {...rest} ref={ref}>
113+
<Context.Provider
114+
value={{
115+
state,
116+
setState,
117+
animate,
118+
itemsNumber,
119+
setItemsNumber,
120+
animating,
121+
setAnimating,
122+
}}
123+
>
124+
{children}
125+
</Context.Provider>
126+
</div>
127+
)
128+
},
129+
)
129130

130131
CCarousel.propTypes = {
131-
activeIndex: PropTypes.number,
132132
animate: PropTypes.bool,
133-
autoSlide: PropTypes.number,
134133
children: PropTypes.node,
135134
className: PropTypes.string,
136-
innerRef: PropTypes.any, // TODO: check
135+
index: PropTypes.number,
136+
interval: PropTypes.oneOfType([
137+
PropTypes.bool,
138+
PropTypes.number
139+
]).isRequired,
137140
onSlideChange: PropTypes.func,
138141
}
139142

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, HTMLAttributes, RefObject } from 'react'
1+
import React, { forwardRef, HTMLAttributes, RefObject } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44

@@ -7,26 +7,18 @@ export interface CCarouselCaptionProps extends HTMLAttributes<HTMLDivElement> {
77
* A string of all className you want applied to the base component. [docs]
88
*/
99
className?: string
10-
11-
/**
12-
* Inner ref of main element. [docs]
13-
*
14-
* @type RefObject<HTMLDivElement> | {(): void}
15-
*/
16-
innerRef?: RefObject<HTMLDivElement> | { (): void }
1710
}
1811

19-
export const CCarouselCaption: FC<CCarouselCaptionProps> = ({ className, innerRef, ...rest }) => {
20-
//render
12+
export const CCarouselCaption = forwardRef<HTMLDivElement, CCarouselCaptionProps>(
13+
({ className, ...rest }, ref) => {
14+
const _className = classNames('carousel-caption', className)
2115

22-
const classes = classNames('carousel-caption', className)
23-
24-
return <div className={classes} {...rest} ref={innerRef} />
25-
}
16+
return <div className={_className} {...rest} ref={ref} />
17+
},
18+
)
2619

2720
CCarouselCaption.propTypes = {
2821
className: PropTypes.string,
29-
innerRef: PropTypes.any,
3022
}
3123

3224
CCarouselCaption.displayName = 'CCarouselCaption'
Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
1-
import React, { FC, HTMLAttributes, RefObject, useContext } from 'react'
1+
import React, { forwardRef, HTMLAttributes, RefObject, useContext } from 'react'
22
import PropTypes from 'prop-types'
33
import classNames from 'classnames'
44
import { Context } from './CCarousel'
55

66
type Direction = 'prev' | 'next'
77

8-
export interface CCarouselControlProps extends HTMLAttributes<HTMLElement> {
8+
export interface CCarouselControlProps extends HTMLAttributes<HTMLButtonElement> {
99
/**
1010
* A string of all className you want applied to the base component. [docs]
1111
*/
1212
className?: string
13-
/**
14-
* Inner ref of main element. [docs]
15-
*
16-
* @type RefObject<HTMLDivElement> | {(): void}
17-
*/
18-
innerRef?: RefObject<HTMLAnchorElement> | { (): void }
1913
/**
2014
* Direction. [docs]
2115
*
@@ -24,44 +18,39 @@ export interface CCarouselControlProps extends HTMLAttributes<HTMLElement> {
2418
direction: Direction
2519
}
2620

27-
export const CCarouselControl: FC<CCarouselControlProps> = ({
28-
className,
29-
innerRef,
30-
children,
31-
direction,
32-
...rest
33-
}) => {
34-
const { state, setState, itemNumber, animating } = useContext(Context)
35-
36-
const onClick = (): void => {
37-
if (animating) {
38-
return
21+
export const CCarouselControl = forwardRef<HTMLButtonElement, CCarouselControlProps>(
22+
({ className, children, direction, ...rest }, ref) => {
23+
const { state, setState, itemsNumber, animating } = useContext(Context)
24+
25+
const onClick = (): void => {
26+
if (animating) {
27+
return
28+
}
29+
let newIdx
30+
if (direction === 'next') {
31+
newIdx = itemsNumber === state[1] + 1 ? 0 : state[1] + 1
32+
} else {
33+
newIdx = state[1] === 0 ? itemsNumber - 1 : state[1] - 1
34+
}
35+
setState([state[1], newIdx, direction])
3936
}
40-
let newIdx
41-
if (direction === 'next') {
42-
newIdx = itemNumber === state[1] + 1 ? 0 : state[1] + 1
43-
} else {
44-
newIdx = state[1] === 0 ? itemNumber - 1 : state[1] - 1
45-
}
46-
setState([state[1], newIdx, direction])
47-
}
48-
49-
//render
5037

51-
const anchorClasses = classNames(`carousel-control-${direction}`, className)
38+
const anchorClasses = classNames(`carousel-control-${direction}`, className)
5239

53-
return (
54-
<a className={anchorClasses} {...rest} onClick={onClick} ref={innerRef}>
55-
{children || <span className={`carousel-control-${direction}-icon`} aria-label={direction} />}
56-
</a>
57-
)
58-
}
40+
return (
41+
<button className={anchorClasses} {...rest} onClick={onClick} ref={ref}>
42+
{children || (
43+
<span className={`carousel-control-${direction}-icon`} aria-label={direction} />
44+
)}
45+
</button>
46+
)
47+
},
48+
)
5949

6050
CCarouselControl.propTypes = {
6151
children: PropTypes.node,
6252
className: PropTypes.string,
6353
direction: PropTypes.oneOf<Direction>(['prev', 'next']).isRequired, // TODO: check
64-
innerRef: PropTypes.any,
6554
}
6655

6756
CCarouselControl.displayName = 'CCarouselControl'

0 commit comments

Comments
 (0)