Преглед изворни кода

Adjust carousel styles according to new designs (#954)

Rafal Pawlow пре 3 година
родитељ
комит
53b1e5db68

+ 6 - 17
src/components/VideoGallery.tsx

@@ -1,8 +1,8 @@
 import styled from '@emotion/styled'
-import React, { useCallback, useMemo, useState } from 'react'
+import React from 'react'
 
 import { VideoFieldsFragment } from '@/api/queries'
-import { CAROUSEL_ARROW_HEIGHT, Gallery, MIN_VIDEO_TILE_WIDTH } from '@/shared/components'
+import { Gallery } from '@/shared/components'
 import { breakpointsOfGrid } from '@/shared/components/Grid'
 import { sizes } from '@/shared/theme'
 
@@ -45,6 +45,8 @@ const breakpoints = breakpointsOfGrid({
   },
 }))
 
+const MIN_VIDEO_PREVIEW_WIDTH = 281
+
 export const VideoGallery: React.FC<VideoGalleryProps> = ({
   title,
   videos = [],
@@ -54,18 +56,6 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
   onRemoveButtonClick,
   onVideoNotFound,
 }) => {
-  const [coverHeight, setCoverHeight] = useState<number>()
-  const onCoverResize = useCallback((_, imgHeight) => {
-    setCoverHeight(imgHeight)
-  }, [])
-  const arrowPosition = useMemo(() => {
-    if (!coverHeight) {
-      return
-    }
-    const topPx = (coverHeight - CAROUSEL_ARROW_HEIGHT) / 2
-    return topPx
-  }, [coverHeight])
-
   if (!loading && videos?.length === 0) {
     return null
   }
@@ -82,8 +72,8 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
       paddingLeft={sizes(2, true)}
       paddingTop={sizes(2, true)}
       responsive={breakpoints}
-      itemWidth={MIN_VIDEO_TILE_WIDTH}
-      arrowPosition={arrowPosition}
+      itemWidth={MIN_VIDEO_PREVIEW_WIDTH}
+      dotsVisible
     >
       {[...videos, ...placeholderItems]?.map((video, idx) => (
         <StyledVideoTile
@@ -91,7 +81,6 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
           progress={video?.progress}
           key={idx}
           removeButton={video ? removeButton : false}
-          onCoverResize={onCoverResize}
           onClick={createClickHandler(video.id)}
           onNotFound={createNotFoundHandler(video.id)}
           onRemoveButtonClick={createRemoveButtonClickHandler(video.id)}

+ 4 - 2
src/shared/components/Carousel/Carousel.stories.tsx

@@ -1,6 +1,6 @@
 import styled from '@emotion/styled'
 import { Meta, Story } from '@storybook/react'
-import React from 'react'
+import React, { useRef } from 'react'
 
 import { colors } from '@/shared/theme'
 
@@ -12,8 +12,10 @@ export default {
 } as Meta
 
 const Template: Story<CarouselProps> = (args) => {
+  const prevArrowRef = useRef<HTMLButtonElement>(null)
+  const nextArrowRef = useRef<HTMLButtonElement>(null)
   return (
-    <Carousel {...args}>
+    <Carousel {...args} prevArrowRef={prevArrowRef} nextArrowRef={nextArrowRef} dotsVisible>
       {Array.from({ length: 10 }, (_, i) => (
         <CarouselItem key={i}> Carousel Item {i}</CarouselItem>
       ))}

+ 45 - 37
src/shared/components/Carousel/Carousel.style.ts

@@ -1,6 +1,7 @@
 import styled from '@emotion/styled'
 
-import { zIndex } from '../../theme'
+import { colors, sizes, transitions, zIndex } from '@/shared/theme'
+
 import { IconButton } from '../IconButton'
 
 export const CAROUSEL_ARROW_HEIGHT = 48
@@ -9,45 +10,24 @@ export const Container = styled.div`
   position: relative;
 `
 
-type HasDirection = {
-  direction: 'prev' | 'next'
-}
-
 type HasPadding = {
   paddingLeft: number
   paddingTop: number
 }
 
-type ArrowProps = {
-  arrowPosition?: number
-}
-
-export const BackgroundGradient = styled.div<HasDirection & HasPadding>`
-  position: absolute;
-  top: 0;
-  left: ${(props) => (props.direction === 'prev' ? 0 : 'auto')};
-  right: ${(props) => (props.direction === 'next' ? 0 : 'auto')};
-  bottom: 0;
-  margin-left: ${(props) => -props.paddingLeft}px;
-  margin-top: ${(props) => -props.paddingTop}px;
-  width: 10%;
-  z-index: ${zIndex.overlay};
-  background-image: linear-gradient(
-    ${(props) => (props.direction === 'prev' ? 270 : 90)}deg,
-    transparent,
-    var(--gradientColor, transparent)
-  );
-  pointer-events: none;
-`
-
-export const Arrow = styled(IconButton)<ArrowProps>`
-  position: absolute;
-  top: ${({ arrowPosition }) => arrowPosition && `${arrowPosition}px`};
+export const Arrow = styled(IconButton)`
   z-index: ${zIndex.nearOverlay};
   cursor: pointer;
 
   &.disabled {
-    display: none;
+    opacity: 0.5;
+  }
+
+  &.glider-prev,
+  &.glider-next {
+    position: relative;
+    top: 0;
+    padding: ${sizes(2)};
   }
 
   &.glider-prev {
@@ -57,12 +37,6 @@ export const Arrow = styled(IconButton)<ArrowProps>`
   &.glider-next {
     right: 0;
   }
-  + ${BackgroundGradient} {
-    --gradientColor: black;
-  }
-  &.disabled + ${BackgroundGradient} {
-    --gradientColor: transparent;
-  }
 `
 
 export const GliderContainer = styled.div<HasPadding>`
@@ -75,3 +49,37 @@ export const GliderContainer = styled.div<HasPadding>`
 export const Track = styled.div`
   align-items: flex-start;
 `
+
+export const Dots = styled.div`
+  margin-top: ${sizes(13)};
+
+  .glider-dot {
+    background-color: transparent;
+    width: ${sizes(10)};
+    border-radius: 0;
+    padding: ${sizes(1)};
+    margin: 0;
+
+    &::after {
+      content: '';
+      width: 100%;
+      height: ${sizes(1)};
+      display: block;
+      background-color: ${colors.gray[700]};
+      transition: all ${transitions.timings.regular} ${transitions.easing};
+    }
+
+    &:hover:not(.active) {
+      &::after {
+        background-color: ${colors.gray[50]};
+        transform: translateY(-2px);
+      }
+    }
+
+    &.active {
+      &::after {
+        background-color: ${colors.gray[300]};
+      }
+    }
+  }
+`

+ 55 - 39
src/shared/components/Carousel/Carousel.tsx

@@ -1,8 +1,6 @@
-import React, { useRef } from 'react'
+import React, { ComponentPropsWithoutRef, ReactNode, RefObject, forwardRef, useImperativeHandle, useRef } from 'react'
 
-import { SvgGlyphChevronLeft, SvgGlyphChevronRight } from '@/shared/icons'
-
-import { Arrow, BackgroundGradient, Container, GliderContainer, Track } from './Carousel.style'
+import { Container, Dots, GliderContainer, Track } from './Carousel.style'
 
 import { GliderProps, useGlider } from '../Glider'
 
@@ -11,41 +9,59 @@ export type CarouselProps = {
   paddingTop?: number
   className?: string
   arrowPosition?: number
+  dotsVisible?: boolean
 } & GliderProps
+export const Carousel = forwardRef<
+  ReactNode,
+  CarouselProps &
+    ComponentPropsWithoutRef<'div'> & {
+      prevArrowRef: RefObject<HTMLButtonElement>
+      nextArrowRef: RefObject<HTMLButtonElement>
+    }
+>(
+  (
+    {
+      children,
+      paddingLeft = 0,
+      paddingTop = 0,
+      className = '',
+      arrowPosition,
+      slidesToShow = 'auto',
+      dotsVisible,
+      prevArrowRef,
+      nextArrowRef,
+      ...gliderOptions
+    },
+    ref
+  ) => {
+    const dotsRef = useRef<HTMLDivElement>(null)
+    const {
+      ref: gliderRef,
+      getContainerProps,
+      getGliderProps,
+      getTrackProps,
+      getPrevArrowProps,
+      getNextArrowProps,
+      getDotsProps,
+    } = useGlider<HTMLDivElement>({
+      slidesToShow,
+      arrows: { prev: prevArrowRef.current, next: nextArrowRef.current },
+      dots: dotsRef.current,
+      ...gliderOptions,
+    })
 
-export const Carousel: React.FC<CarouselProps> = ({
-  children,
-  paddingLeft = 0,
-  paddingTop = 0,
-  className = '',
-  arrowPosition,
-  slidesToShow = 'auto',
-  ...gliderOptions
-}) => {
-  // TODO: this is the only place in the app that requires refs to buttons. Once we refactor this component, we can remove forwardRef from buttons
-  const nextArrowRef = useRef<HTMLButtonElement>(null)
-  const prevArrowRef = useRef<HTMLButtonElement>(null)
-  const { ref, getContainerProps, getGliderProps, getTrackProps, getPrevArrowProps, getNextArrowProps } = useGlider<
-    HTMLDivElement
-  >({
-    slidesToShow,
-    arrows: { prev: prevArrowRef.current, next: nextArrowRef.current },
-    ...gliderOptions,
-  })
+    useImperativeHandle(ref, () => ({
+      getPrevArrowProps,
+      getNextArrowProps,
+    }))
 
-  return (
-    <Container {...getContainerProps({ className })}>
-      <Arrow {...getPrevArrowProps()} ref={prevArrowRef} arrowPosition={arrowPosition} size="large">
-        <SvgGlyphChevronLeft />
-      </Arrow>
-      <BackgroundGradient direction="prev" paddingLeft={paddingLeft} paddingTop={paddingTop} />
-      <GliderContainer {...getGliderProps()} paddingLeft={paddingLeft} paddingTop={paddingTop} ref={ref}>
-        <Track {...getTrackProps()}>{children}</Track>
-      </GliderContainer>
-      <Arrow {...getNextArrowProps()} ref={nextArrowRef} arrowPosition={arrowPosition} size="large">
-        <SvgGlyphChevronRight />
-      </Arrow>
-      <BackgroundGradient direction="next" paddingLeft={paddingLeft} paddingTop={paddingTop} />
-    </Container>
-  )
-}
+    return (
+      <Container {...getContainerProps({ className })}>
+        <GliderContainer {...getGliderProps()} paddingLeft={paddingLeft} paddingTop={paddingTop} ref={gliderRef}>
+          <Track {...getTrackProps()}>{children}</Track>
+        </GliderContainer>
+        {dotsVisible && <Dots {...getDotsProps()} ref={dotsRef} />}
+      </Container>
+    )
+  }
+)

+ 13 - 4
src/shared/components/Gallery/Gallery.style.ts

@@ -1,6 +1,6 @@
 import styled from '@emotion/styled'
 
-import { sizes, typography } from '../../theme'
+import { colors, sizes, typography } from '../../theme'
 
 export const Container = styled.section`
   display: flex;
@@ -9,11 +9,13 @@ export const Container = styled.section`
 export const HeadingContainer = styled.div`
   display: flex;
   justify-content: space-between;
-  align-items: baseline;
-  margin-bottom: ${sizes(4)};
+  align-items: start;
+  margin-bottom: ${sizes(10)};
+  padding-bottom: ${sizes(4)};
+  border-bottom: 1px solid ${colors.gray[700]};
 
   > h4 {
-    font-size: ${typography.sizes.h5};
+    font-size: ${typography.sizes.h4};
     margin: 0;
   }
 
@@ -22,3 +24,10 @@ export const HeadingContainer = styled.div`
     padding: 0;
   }
 `
+
+export const CarouselArrowsContainer = styled.div`
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  grid-column-gap: ${sizes(4)};
+  margin-left: auto;
+`

+ 32 - 8
src/shared/components/Gallery/Gallery.tsx

@@ -1,6 +1,9 @@
-import React from 'react'
+import React, { ComponentProps, useRef } from 'react'
 
-import { Container, HeadingContainer } from './Gallery.style'
+import { Arrow } from '@/shared/components/Carousel/Carousel.style'
+import { SvgGlyphChevronLeft, SvgGlyphChevronRight } from '@/shared/icons'
+
+import { CarouselArrowsContainer, Container, HeadingContainer } from './Gallery.style'
 
 import { Carousel, CarouselProps } from '../Carousel/Carousel'
 import { Text } from '../Text'
@@ -10,15 +13,36 @@ export type GalleryProps = {
   className?: string
 } & CarouselProps
 
+type ImperativeHandleData = {
+  getPrevArrowProps: () => ComponentProps<typeof Arrow>
+  getNextArrowProps: () => ComponentProps<typeof Arrow>
+}
+
 export const Gallery: React.FC<GalleryProps> = ({ title, className, ...carouselProps }) => {
+  // TODO: this is the only place in the app that requires refs to buttons. Once we refactor this component, we can remove forwardRef from buttons
+  const prevArrowRef = useRef<HTMLButtonElement>(null)
+  const nextArrowRef = useRef<HTMLButtonElement>(null)
+  const carouselRef = useRef<ImperativeHandleData>(null)
   return (
     <Container className={className}>
-      {title && (
-        <HeadingContainer>
-          <Text variant="h5">{title}</Text>
-        </HeadingContainer>
-      )}
-      <Carousel {...carouselProps} />
+      <HeadingContainer>
+        {title && <Text variant="h4">{title}</Text>}
+        <CarouselArrowsContainer>
+          <Arrow {...carouselRef.current?.getPrevArrowProps()} ref={prevArrowRef} size="large" variant="secondary">
+            <SvgGlyphChevronLeft />
+          </Arrow>
+          <Arrow {...carouselRef.current?.getNextArrowProps()} ref={nextArrowRef} size="large" variant="secondary">
+            <SvgGlyphChevronRight />
+          </Arrow>
+        </CarouselArrowsContainer>
+      </HeadingContainer>
+      <Carousel
+        {...carouselProps}
+        prevArrowRef={prevArrowRef}
+        nextArrowRef={nextArrowRef}
+        ref={carouselRef}
+        itemWidth={350}
+      />
     </Container>
   )
 }

+ 2 - 0
src/shared/components/Glider/Glider.tsx

@@ -28,6 +28,7 @@ const getTrackProps = getPropsFor('glider-track')
 const getNextArrowProps = getPropsFor('glider-next')
 const getPrevArrowProps = getPropsFor('glider-prev')
 const getContainerProps = getPropsFor('glider-contain')
+const getDotsProps = getPropsFor('glider-dots')
 
 export function useGlider<T extends HTMLElement>({
   onAdd,
@@ -80,6 +81,7 @@ export function useGlider<T extends HTMLElement>({
     getNextArrowProps,
     getPrevArrowProps,
     getContainerProps,
+    getDotsProps,
   }
 }