Browse Source

Style Arrows and Allow Multiple Carousels on the same page

Francesco Baccetti 4 years ago
parent
commit
daf010aea5

+ 1 - 1
src/components/VideoGallery.tsx

@@ -28,7 +28,7 @@ const VideoGallery: React.FC<VideoGalleryProps> = ({ title, action, videos, load
         ? Array.from({ length: PLACEHOLDERS_COUNT }).map((_, idx) => (
             <StyledVideoPreviewBase key={`video-placeholder-${idx}`} />
           ))
-        : videos!.map((video, idx) => (
+        : videos!.map((video) => (
             <StyledVideoPreview
               id={video.id}
               channelId={video.channel.id}

+ 47 - 0
src/shared/components/Carousel/Carousel.style.ts

@@ -0,0 +1,47 @@
+import styled from '@emotion/styled'
+import Glider from 'react-glider'
+
+import Icon from '../Icon'
+import { colors } from '../../theme'
+
+export const Container = styled.div<{ trackPadding: string }>`
+  .glider-prev,
+  .glider-next {
+    position: absolute;
+
+    display: grid;
+    place-items: center;
+    color: ${colors.white};
+    background-color: ${colors.blue[500]};
+    border: unset;
+    width: 48px;
+    height: 48px;
+    transition: none;
+    :hover {
+      color: ${colors.white};
+      background-color: ${colors.blue[700]};
+    }
+    :active {
+      background-color: ${colors.blue[900]};
+    }
+  }
+  .glider-prev.disabled,
+  .glider-next.disabled {
+    opacity: 0;
+  }
+  .glider-prev {
+    left: 0;
+  }
+  .glider-next {
+    right: 0;
+  }
+
+  .glider-track {
+    padding: ${(props) => props.trackPadding};
+  }
+`
+
+export const StyledGlider = styled(Glider)`
+  scrollbar-width: none;
+`
+export const Arrow = styled(Icon)``

+ 58 - 40
src/shared/components/Carousel/Carousel.tsx

@@ -1,52 +1,70 @@
-import React from 'react'
-import styled from '@emotion/styled'
-import Glider, { GliderProps, BreakPoint } from 'react-glider'
-import 'glider-js/glider.min.css'
+import React, { useState, useEffect, useRef } from 'react'
+import { GliderProps, BreakPoint, GliderMethods } from 'react-glider'
+
+import { Container, StyledGlider, Arrow } from './Carousel.style'
 
-import Icon from '../Icon'
+import 'glider-js/glider.min.css'
 
-export type CarouselProps = {
+type CarouselProps = {
   trackPadding?: string
 } & GliderProps
 
-const Track: React.FC<any> = ({ className = '', ...props }) => (
+type TrackProps = {
+  className?: string
+  padding?: string
+}
+const Track: React.FC<TrackProps> = ({ className = '', ...props }) => (
   <div className={`glider-track ${className}`} {...props} />
 )
 
-const defaultBreakpoints: BreakPoint[] = [
-  {
-    breakpoint: 400,
-    settings: {
-      slidesToShow: 1,
-      slidesToScroll: 1,
-      duration: 0.25,
-    },
-  },
-  {
-    breakpoint: 775,
-    settings: {
-      slidesToShow: 4,
-      slidesToScroll: 'auto',
-      duration: 0.25,
-    },
-  },
-] as BreakPoint[]
-
-const StyledTrack = styled(Track)<{ padding: string }>`
-  padding: ${(props) => props.padding};
-`
-const LeftIcon = <Icon name="chevron-left" />
-const RightIcon = <Icon name="chevron-right" />
-const Carousel: React.FC<CarouselProps> = ({
-  children,
-  trackPadding = '0',
-  responsive = defaultBreakpoints,
-  ...gliderProps
-}) => {
+const RightArrow = <Arrow name="chevron-right" />
+const LeftArrow = <Arrow name="chevron-left" />
+
+const Carousel: React.FC<CarouselProps> = ({ children, trackPadding = '0', className, ...gliderProps }) => {
+  // Using any because the GliderMethods type only has methods and I need the full instance
+  const gliderRef = React.useRef<any>()
+  const [arrows, setArrows] = React.useState<
+    { prev: string | HTMLButtonElement; next: string | HTMLButtonElement } | undefined
+  >(undefined)
+
+  React.useLayoutEffect(() => {
+    if (gliderRef.current) {
+      const glider = gliderRef.current.ele
+      const prevArrow = glider.previousSibling
+      const nextArrow = glider.nextSibling
+      const INSTANCE_KEY = Math.round(Math.random() * 1000)
+      prevArrow.classList.add(`glider-${INSTANCE_KEY}-prev`)
+      nextArrow.classList.add(`glider-${INSTANCE_KEY}-next`)
+      setArrows({ prev: prevArrow, next: nextArrow })
+    }
+  }, [])
+
+  // 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
+      container.insertBefore(prevArrow, gliderRef.current.ele)
+      container.appendChild(nextArrow)
+    }
+  }, [arrows])
+
   return (
-    <Glider skipTrack hasArrows iconLeft={LeftIcon} iconRight={RightIcon} responsive={responsive} {...gliderProps}>
-      <StyledTrack padding={trackPadding}>{children}</StyledTrack>
-    </Glider>
+    <Container trackPadding={trackPadding} className={className}>
+      <StyledGlider
+        addTrack
+        skipTrack
+        hasArrows
+        draggable
+        ref={gliderRef}
+        iconLeft={LeftArrow}
+        iconRight={RightArrow}
+        arrows={arrows as { prev: string; next: string }}
+        {...gliderProps}
+      >
+        <Track padding={trackPadding}>{children}</Track>
+      </StyledGlider>
+    </Container>
   )
 }
 export default Carousel

+ 0 - 2
src/shared/components/Carousel/index.ts

@@ -1,5 +1,3 @@
-import type { CarouselProps } from './Carousel'
 import Carousel from './Carousel'
 
 export default Carousel
-export type { CarouselProps }

+ 22 - 1
src/shared/components/Gallery/Gallery.style.ts

@@ -1,4 +1,6 @@
-import { spacing } from '../../theme'
+import styled from '@emotion/styled'
+import { spacing, typography } from '../../theme'
+
 import { makeStyles, StyleFn } from '../../utils'
 
 const container: StyleFn = () => ({
@@ -6,6 +8,25 @@ const container: StyleFn = () => ({
   flexDirection: 'column',
 })
 
+export const Container = styled.section`
+  display: flex;
+  flex-direction: column;
+`
+export const HeadingContainer = styled.div`
+  display: flex;
+  justify-content: space-between;
+  align-items: baseline;
+  margin-bottom: ${spacing.m};
+
+  > h4 {
+    font-size: ${typography.sizes.h5};
+    margin: 0;
+  }
+  > button {
+    font-size: ${typography.sizes.subtitle2};
+    padding: 0;
+  }
+`
 const headingContainer: StyleFn = () => ({
   display: 'flex',
   justifyContent: 'space-between',

+ 10 - 12
src/shared/components/Gallery/Gallery.tsx

@@ -1,30 +1,28 @@
 import React from 'react'
-import { SerializedStyles } from '@emotion/core'
-import { useCSS } from './Gallery.style'
+import { Container, HeadingContainer } from './Gallery.style'
 import Button from '../Button'
-import Carousel, { CarouselProps } from '../Carousel'
+import Carousel from '../Carousel'
 
 type GalleryProps = {
   title?: string
   action?: string
   onClick?: () => void
-  containerCss?: SerializedStyles
-} & CarouselProps
+  className?: string
+} & React.ComponentProps<typeof Carousel>
 
-const Gallery: React.FC<GalleryProps> = ({ title, action = '', containerCss, onClick, ...props }) => {
-  const styles = useCSS()
+const Gallery: React.FC<GalleryProps> = ({ title, action = '', className, onClick, ...carouselProps }) => {
   return (
-    <section css={[styles.container, containerCss]}>
-      <div css={styles.headingContainer}>
+    <Container className={className}>
+      <HeadingContainer>
         {title && <h4>{title}</h4>}
         {action && (
           <Button variant="tertiary" onClick={onClick}>
             {action}
           </Button>
         )}
-      </div>
-      <Carousel {...props} />
-    </section>
+      </HeadingContainer>
+      <Carousel {...carouselProps} />
+    </Container>
   )
 }