Browse Source

Align Carousel Arrows With Middle Of Preview Cover

Francesco Baccetti 4 years ago
parent
commit
1c3f569814

+ 29 - 9
src/components/VideoGallery.tsx

@@ -1,13 +1,11 @@
-import React from 'react'
-
+import React, { useState, useCallback, useMemo } from 'react'
+import { css } from '@emotion/core'
 import styled from '@emotion/styled'
 
 import { breakpointsOfGrid, Gallery, MIN_VIDEO_PREVIEW_WIDTH, VideoPreviewBase } from '@/shared/components'
 import VideoPreview from './VideoPreviewWithNavigation'
 import { VideoFields } from '@/api/queries/__generated__/VideoFields'
-
-import { sizes, spacing } from '@/shared/theme'
-import { css } from '@emotion/core'
+import { sizes } from '@/shared/theme'
 
 type VideoGalleryProps = {
   title?: string
@@ -16,8 +14,8 @@ type VideoGalleryProps = {
 }
 
 const PLACEHOLDERS_COUNT = 12
-
-const trackPadding = `${spacing.xs} 0 0 0`
+const CAROUSEL_ARROW_HEIGHT = 48
+const trackPadding = `${sizes.b2}px 0 0 0`
 
 // This is needed since Gliderjs and the Grid have different resizing policies
 const breakpoints = breakpointsOfGrid({
@@ -33,15 +31,36 @@ const breakpoints = breakpointsOfGrid({
 }))
 
 const VideoGallery: React.FC<VideoGalleryProps> = ({ title, videos, loading }) => {
+  const [imgHeight, setImgHeight] = useState<number>()
+  const imgRef = useCallback((img: HTMLImageElement | null) => {
+    setImgHeight(img?.clientHeight)
+  }, [])
+
+  const arrowPosition = useMemo(() => {
+    if (!imgHeight) {
+      return
+    }
+    const topPx = (imgHeight - CAROUSEL_ARROW_HEIGHT) / 2 + sizes.b2
+    return css`
+      top: ${topPx}px;
+    `
+  }, [imgHeight])
+
   const displayPlaceholders = loading || !videos
 
   return (
-    <Gallery title={title} trackPadding={trackPadding} responsive={breakpoints} itemWidth={MIN_VIDEO_PREVIEW_WIDTH}>
+    <Gallery
+      title={title}
+      trackPadding={trackPadding}
+      responsive={breakpoints}
+      itemWidth={MIN_VIDEO_PREVIEW_WIDTH}
+      arrowCss={arrowPosition}
+    >
       {displayPlaceholders
         ? Array.from({ length: PLACEHOLDERS_COUNT }).map((_, idx) => (
             <StyledVideoPreviewBase key={`video-placeholder-${idx}`} />
           ))
-        : videos!.map((video) => (
+        : videos!.map((video, idx) => (
             <StyledVideoPreview
               id={video.id}
               channelId={video.channel.id}
@@ -53,6 +72,7 @@ const VideoGallery: React.FC<VideoGalleryProps> = ({ title, videos, loading }) =
               duration={video.duration}
               posterURL={video.thumbnailUrl}
               key={video.id}
+              imgRef={idx === 0 ? imgRef : undefined}
             />
           ))}
     </Gallery>

+ 5 - 2
src/shared/components/Carousel/Carousel.tsx

@@ -1,3 +1,4 @@
+import { SerializedStyles } from '@emotion/core'
 import React, { useRef } from 'react'
 
 import { useGlider, GliderProps } from '../Glider'
@@ -7,12 +8,14 @@ import { Container, GliderContainer, Arrow, Track, BackgroundGradient } from './
 type CarouselProps = {
   trackPadding?: string
   className?: string
+  arrowCss?: SerializedStyles
 } & GliderProps
 
 const Carousel: React.FC<CarouselProps> = ({
   children,
   trackPadding = '0',
   className = '',
+  arrowCss,
   slidesToShow = 'auto',
   ...gliderOptions
 }) => {
@@ -27,12 +30,12 @@ const Carousel: React.FC<CarouselProps> = ({
   })
   return (
     <Container {...getContainerProps({ className })}>
-      <Arrow {...getPrevArrowProps()} icon="chevron-left" ref={prevArrowRef} />
+      <Arrow {...getPrevArrowProps()} icon="chevron-left" ref={prevArrowRef} css={arrowCss} />
       <BackgroundGradient direction="prev" />
       <GliderContainer {...getGliderProps()} ref={ref}>
         <Track {...getTrackProps({ trackPadding })}>{children}</Track>
       </GliderContainer>
-      <Arrow {...getNextArrowProps()} icon="chevron-right" ref={nextArrowRef} />
+      <Arrow {...getNextArrowProps()} icon="chevron-right" ref={nextArrowRef} css={arrowCss} />
       <BackgroundGradient direction="next" />
     </Container>
   )

+ 14 - 3
src/shared/components/VideoPreview/VideoPreview.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react'
+import React, { useState, useLayoutEffect } from 'react'
 import useResizeObserver from 'use-resize-observer'
 
 import {
@@ -41,8 +41,7 @@ type VideoPreviewProps = {
   showChannel?: boolean
   showMeta?: boolean
   main?: boolean
-
-  imgRef?: React.Ref<HTMLImageElement>
+  imgRef?: ((instance: HTMLImageElement | null) => void) | React.MutableRefObject<HTMLImageElement | null | undefined>
   onClick?: (e: React.MouseEvent<HTMLElement>) => void
   onChannelClick?: (e: React.MouseEvent<HTMLElement>) => void
   className?: string
@@ -63,6 +62,7 @@ const VideoPreview: React.FC<VideoPreviewProps> = ({
   onClick,
   onChannelClick,
   className,
+  imgRef: externalImgRef,
 }) => {
   const [scalingFactor, setScalingFactor] = useState(MIN_SCALING_FACTOR)
   const { ref: imgRef } = useResizeObserver<HTMLImageElement>({
@@ -74,6 +74,17 @@ const VideoPreview: React.FC<VideoPreviewProps> = ({
     },
   })
 
+  useLayoutEffect(() => {
+    if (externalImgRef) {
+      if (typeof externalImgRef === 'function') {
+        externalImgRef(imgRef.current)
+        return
+      }
+
+      externalImgRef.current = imgRef.current
+    }
+  })
+
   const channelClickable = !!onChannelClick
 
   const handleChannelClick = (e: React.MouseEvent<HTMLElement>) => {