|
@@ -1,94 +1,69 @@
|
|
|
-import React, { useState } from 'react'
|
|
|
-import { SerializedStyles } from '@emotion/core'
|
|
|
-import { animated, useSpring } from 'react-spring'
|
|
|
-import useResizeObserver from 'use-resize-observer'
|
|
|
-import { CarouselStyleProps, useCSS } from './Carousel.style'
|
|
|
-import NavButton from '../NavButton'
|
|
|
+import React, { useState, useLayoutEffect, useRef } from 'react'
|
|
|
+import { GliderProps, GliderMethods } from 'react-glider'
|
|
|
|
|
|
-export type CarouselProps = {
|
|
|
- containerCss?: SerializedStyles
|
|
|
- leftControlCss?: SerializedStyles
|
|
|
- rightControlCss?: SerializedStyles
|
|
|
- disableControls?: boolean
|
|
|
- onScroll?: (direction: 'left' | 'right') => void
|
|
|
-} & CarouselStyleProps
|
|
|
+import { Container, StyledGlider, Arrow } from './Carousel.style'
|
|
|
|
|
|
-const Carousel: React.FC<CarouselProps> = ({
|
|
|
- children,
|
|
|
- containerCss,
|
|
|
- leftControlCss,
|
|
|
- rightControlCss,
|
|
|
- disableControls = false,
|
|
|
- onScroll = () => {},
|
|
|
-}) => {
|
|
|
- const [scroll, setScroll] = useSpring(() => ({
|
|
|
- transform: `translateX(0px)`,
|
|
|
- }))
|
|
|
- const [carouselOffset, setCarouselOffset] = useState(0)
|
|
|
- const { width: containerWidth = 0, ref: containerRef } = useResizeObserver<HTMLDivElement>()
|
|
|
- const { width: childrenWidth = 0, ref: childrenContainerRef } = useResizeObserver<HTMLDivElement>()
|
|
|
+import 'glider-js/glider.min.css'
|
|
|
|
|
|
- const styles = useCSS({})
|
|
|
+type CarouselProps = {
|
|
|
+ trackPadding?: string
|
|
|
+} & GliderProps
|
|
|
|
|
|
- const maxScrollOffset = childrenWidth - containerWidth
|
|
|
+type TrackProps = {
|
|
|
+ className?: string
|
|
|
+ padding?: string
|
|
|
+}
|
|
|
+const Track: React.FC<TrackProps> = ({ className = '', ...props }) => (
|
|
|
+ <div className={`glider-track ${className}`} {...props} />
|
|
|
+)
|
|
|
+
|
|
|
+const RightArrow = <Arrow name="chevron-right" />
|
|
|
+const LeftArrow = <Arrow name="chevron-left" />
|
|
|
|
|
|
- const showLeftControl = !disableControls && carouselOffset > 0
|
|
|
- const showRightControl = !disableControls && carouselOffset < maxScrollOffset
|
|
|
+const Carousel: React.FC<CarouselProps> = ({ children, trackPadding = '0', className, ...gliderProps }) => {
|
|
|
+ // The GliderMethods type only has methods and I need the full instance
|
|
|
+ const gliderRef = useRef<GliderMethods & { ele: HTMLDivElement }>()
|
|
|
+ const [arrows, setArrows] = useState<{ prev: HTMLButtonElement; next: HTMLButtonElement } | undefined>(undefined)
|
|
|
|
|
|
- const handleScroll = (direction: 'left' | 'right') => {
|
|
|
- if (containerWidth == null) {
|
|
|
- return
|
|
|
+ useLayoutEffect(() => {
|
|
|
+ if (gliderRef.current) {
|
|
|
+ const glider = gliderRef.current.ele
|
|
|
+ const prevArrow = glider.previousSibling as HTMLButtonElement
|
|
|
+ const nextArrow = glider.nextSibling as HTMLButtonElement
|
|
|
+
|
|
|
+ setArrows({ prev: prevArrow, next: nextArrow })
|
|
|
}
|
|
|
- let scrollAmount
|
|
|
- switch (direction) {
|
|
|
- case 'left': {
|
|
|
- // Prevent overscroll on the left
|
|
|
- const newOffset = carouselOffset - containerWidth
|
|
|
- scrollAmount = newOffset < 0 ? 0 : newOffset
|
|
|
- onScroll('left')
|
|
|
- break
|
|
|
- }
|
|
|
- case 'right': {
|
|
|
- // Prevent overscroll on the right
|
|
|
- const newOffset = carouselOffset + containerWidth
|
|
|
- scrollAmount = newOffset > maxScrollOffset ? maxScrollOffset : newOffset
|
|
|
- onScroll('right')
|
|
|
- break
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ // This is needed because react-glider will render arrows only if the arrows option is undefined, so arrows won't display if you pass an object to StyledGlider
|
|
|
+ React.useLayoutEffect(() => {
|
|
|
+ if (gliderRef.current && arrows) {
|
|
|
+ const { prev: prevArrow, next: nextArrow } = arrows
|
|
|
+ const container = gliderRef.current.ele.parentElement
|
|
|
+ if (container) {
|
|
|
+ container.insertBefore(prevArrow, gliderRef.current.ele)
|
|
|
+ container.appendChild(nextArrow)
|
|
|
}
|
|
|
}
|
|
|
- setCarouselOffset(scrollAmount)
|
|
|
- setScroll({
|
|
|
- transform: `translateX(-${scrollAmount}px)`,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if (!Array.isArray(children)) {
|
|
|
- return <>{children}</>
|
|
|
- }
|
|
|
+ }, [arrows])
|
|
|
|
|
|
return (
|
|
|
- <div css={[styles.container, containerCss]}>
|
|
|
- <div css={styles.outerItemsContainer} ref={containerRef}>
|
|
|
- <animated.div css={styles.innerItemsContainer} style={scroll}>
|
|
|
- <div css={styles.innerItemsContainer} ref={childrenContainerRef}>
|
|
|
- {children.map((element, idx) => (
|
|
|
- <React.Fragment key={`Carousel-${idx}`}>{element}</React.Fragment>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- </animated.div>
|
|
|
- </div>
|
|
|
- {showLeftControl && (
|
|
|
- <NavButton outerCss={[styles.navLeft, leftControlCss]} direction="left" onClick={() => handleScroll('left')} />
|
|
|
- )}
|
|
|
- {showRightControl && (
|
|
|
- <NavButton
|
|
|
- outerCss={[styles.navRight, rightControlCss]}
|
|
|
- direction="right"
|
|
|
- onClick={() => handleScroll('right')}
|
|
|
- />
|
|
|
- )}
|
|
|
- </div>
|
|
|
+ <Container trackPadding={trackPadding} className={className}>
|
|
|
+ <StyledGlider
|
|
|
+ addTrack
|
|
|
+ skipTrack
|
|
|
+ hasArrows
|
|
|
+ draggable
|
|
|
+ ref={gliderRef as React.RefObject<GliderMethods>}
|
|
|
+ iconLeft={LeftArrow}
|
|
|
+ iconRight={RightArrow}
|
|
|
+ // Akward conversion needed until this is resolved: https://github.com/hipstersmoothie/react-glider/issues/36
|
|
|
+ arrows={(arrows as unknown) as { prev: string; next: string }}
|
|
|
+ {...gliderProps}
|
|
|
+ >
|
|
|
+ <Track padding={trackPadding}>{children}</Track>
|
|
|
+ </StyledGlider>
|
|
|
+ </Container>
|
|
|
)
|
|
|
}
|
|
|
-
|
|
|
export default Carousel
|