Browse Source

Merge pull request #1757 from Gamaranto/carousel-fixes

Carousel Improvements
Bedeho Mender 4 years ago
parent
commit
503a3a164b

+ 2 - 4
src/components/ChannelGallery.tsx

@@ -3,7 +3,7 @@ import styled from '@emotion/styled'
 
 import { ChannelPreviewBase, Gallery } from '@/shared/components'
 import ChannelPreview from './ChannelPreviewWithNavigation'
-import { spacing } from '@/shared/theme'
+import { sizes } from '@/shared/theme'
 import { ChannelFields } from '@/api/queries/__generated__/ChannelFields'
 
 type ChannelGalleryProps = {
@@ -14,13 +14,11 @@ type ChannelGalleryProps = {
 
 const PLACEHOLDERS_COUNT = 12
 
-const trackPadding = `${spacing.xs} 0 0 ${spacing.xs}`
-
 const ChannelGallery: React.FC<ChannelGalleryProps> = ({ title, channels, loading }) => {
   const displayPlaceholders = loading || !channels
 
   return (
-    <Gallery title={title} trackPadding={trackPadding} itemWidth={220} exactWidth={true}>
+    <Gallery title={title} itemWidth={220} exactWidth={true} paddingLeft={sizes.b2} paddingTop={sizes.b2}>
       {displayPlaceholders
         ? Array.from({ length: PLACEHOLDERS_COUNT }).map((_, idx) => (
             <ChannelPreviewBase key={`channel-placeholder-${idx}`} />

+ 34 - 9
src/components/VideoGallery.tsx

@@ -1,13 +1,17 @@
-import React from 'react'
-
+import React, { useState, useMemo, useCallback } from 'react'
+import { css } from '@emotion/core'
 import styled from '@emotion/styled'
 
-import { breakpointsOfGrid, Gallery, MIN_VIDEO_PREVIEW_WIDTH, VideoPreviewBase } from '@/shared/components'
+import {
+  breakpointsOfGrid,
+  Gallery,
+  MIN_VIDEO_PREVIEW_WIDTH,
+  VideoPreviewBase,
+  CAROUSEL_ARROW_HEIGHT,
+} 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
@@ -17,8 +21,6 @@ type VideoGalleryProps = {
 
 const PLACEHOLDERS_COUNT = 12
 
-const trackPadding = `${spacing.xs} 0 0 0`
-
 // This is needed since Gliderjs and the Grid have different resizing policies
 const breakpoints = breakpointsOfGrid({
   breakpoints: 6,
@@ -29,14 +31,36 @@ const breakpoints = breakpointsOfGrid({
   breakpoint,
   settings: {
     slidesToShow: idx + 1,
+    slidesToScroll: idx + 1,
   },
 }))
 
 const VideoGallery: React.FC<VideoGalleryProps> = ({ title, videos, loading }) => {
+  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 css`
+      top: ${topPx}px;
+    `
+  }, [coverHeight])
+
   const displayPlaceholders = loading || !videos
 
   return (
-    <Gallery title={title} trackPadding={trackPadding} responsive={breakpoints} itemWidth={MIN_VIDEO_PREVIEW_WIDTH}>
+    <Gallery
+      title={title}
+      paddingLeft={sizes.b2}
+      paddingTop={sizes.b2}
+      responsive={breakpoints}
+      itemWidth={MIN_VIDEO_PREVIEW_WIDTH}
+      arrowCss={arrowPosition}
+    >
       {displayPlaceholders
         ? Array.from({ length: PLACEHOLDERS_COUNT }).map((_, idx) => (
             <StyledVideoPreviewBase key={`video-placeholder-${idx}`} />
@@ -53,6 +77,7 @@ const VideoGallery: React.FC<VideoGalleryProps> = ({ title, videos, loading }) =
               duration={video.duration}
               posterURL={video.thumbnailUrl}
               key={video.id}
+              onCoverResize={onCoverResize}
             />
           ))}
     </Gallery>

+ 23 - 6
src/shared/components/Carousel/Carousel.style.ts

@@ -1,16 +1,29 @@
 import styled from '@emotion/styled'
 import Button from '../Button'
 
+export const CAROUSEL_ARROW_HEIGHT = 48
+
 export const Container = styled.div`
   position: relative;
 `
 
-export const BackgroundGradient = styled.div<{ direction: 'prev' | 'next' }>`
+type HasDirection = {
+  direction: 'prev' | 'next'
+}
+
+type HasPadding = {
+  paddingLeft: number
+  paddingTop: 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: 1;
   background-image: linear-gradient(
@@ -23,8 +36,8 @@ export const BackgroundGradient = styled.div<{ direction: 'prev' | 'next' }>`
 
 export const Arrow = styled(Button)`
   position: absolute;
-  width: 48px;
-  height: 48px;
+  width: ${CAROUSEL_ARROW_HEIGHT}px;
+  height: ${CAROUSEL_ARROW_HEIGHT}px;
   transition: none;
 
   &.disabled {
@@ -45,10 +58,14 @@ export const Arrow = styled(Button)`
   }
 `
 
-export const GliderContainer = styled.div`
+export const GliderContainer = styled.div<HasPadding>`
   scrollbar-width: none;
+  padding-left: ${(props) => props.paddingLeft}px;
+  padding-top: ${(props) => props.paddingTop}px;
+  margin-left: ${(props) => -props.paddingLeft}px;
+  margin-top: ${(props) => -props.paddingTop}px;
 `
-export const Track = styled.div<{ trackPadding: string }>`
+
+export const Track = styled.div`
   align-items: flex-start;
-  padding: ${(props) => props.trackPadding};
 `

+ 14 - 8
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'
@@ -5,14 +6,18 @@ import { useGlider, GliderProps } from '../Glider'
 import { Container, GliderContainer, Arrow, Track, BackgroundGradient } from './Carousel.style'
 
 type CarouselProps = {
-  trackPadding?: string
+  paddingLeft?: number
+  paddingTop?: number
   className?: string
+  arrowCss?: SerializedStyles
 } & GliderProps
 
 const Carousel: React.FC<CarouselProps> = ({
   children,
-  trackPadding = '0',
+  paddingLeft = 0,
+  paddingTop = 0,
   className = '',
+  arrowCss,
   slidesToShow = 'auto',
   ...gliderOptions
 }) => {
@@ -25,15 +30,16 @@ const Carousel: React.FC<CarouselProps> = ({
     arrows: { prev: prevArrowRef.current, next: nextArrowRef.current },
     ...gliderOptions,
   })
+
   return (
     <Container {...getContainerProps({ className })}>
-      <Arrow {...getPrevArrowProps()} icon="chevron-left" ref={prevArrowRef} />
-      <BackgroundGradient direction="prev" />
-      <GliderContainer {...getGliderProps()} ref={ref}>
-        <Track {...getTrackProps({ trackPadding })}>{children}</Track>
+      <Arrow {...getPrevArrowProps()} icon="chevron-left" ref={prevArrowRef} css={arrowCss} />
+      <BackgroundGradient direction="prev" paddingLeft={paddingLeft} paddingTop={paddingTop} />
+      <GliderContainer {...getGliderProps()} paddingLeft={paddingLeft} paddingTop={paddingTop} ref={ref}>
+        <Track {...getTrackProps()}>{children}</Track>
       </GliderContainer>
-      <Arrow {...getNextArrowProps()} icon="chevron-right" ref={nextArrowRef} />
-      <BackgroundGradient direction="next" />
+      <Arrow {...getNextArrowProps()} icon="chevron-right" ref={nextArrowRef} css={arrowCss} />
+      <BackgroundGradient direction="next" paddingLeft={paddingLeft} paddingTop={paddingTop} />
     </Container>
   )
 }

+ 1 - 1
src/shared/components/Carousel/index.ts

@@ -1,3 +1,3 @@
 import Carousel from './Carousel'
-
+export { CAROUSEL_ARROW_HEIGHT } from './Carousel.style'
 export default Carousel

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

@@ -41,8 +41,7 @@ type VideoPreviewProps = {
   showChannel?: boolean
   showMeta?: boolean
   main?: boolean
-
-  imgRef?: React.Ref<HTMLImageElement>
+  onCoverResize?: (width: number | undefined, height: number | undefined) => void
   onClick?: (e: React.MouseEvent<HTMLElement>) => void
   onChannelClick?: (e: React.MouseEvent<HTMLElement>) => void
   className?: string
@@ -63,11 +62,15 @@ const VideoPreview: React.FC<VideoPreviewProps> = ({
   onClick,
   onChannelClick,
   className,
+  onCoverResize,
 }) => {
   const [scalingFactor, setScalingFactor] = useState(MIN_SCALING_FACTOR)
   const { ref: imgRef } = useResizeObserver<HTMLImageElement>({
     onResize: (size) => {
-      const { width: videoPreviewWidth } = size
+      const { width: videoPreviewWidth, height: videoPreviewHeight } = size
+      if (onCoverResize) {
+        onCoverResize(videoPreviewWidth, videoPreviewHeight)
+      }
       if (videoPreviewWidth && !main) {
         setScalingFactor(calculateScalingFactor(videoPreviewWidth))
       }

+ 2 - 2
src/shared/components/VideoPreview/VideoPreviewBase.styles.tsx

@@ -30,9 +30,9 @@ export const fadeInAnimation = css`
   animation: ${fadeIn} 0.5s ease-in;
 `
 
-export const CoverWrapper = styled.div`
-  max-width: 650px;
+export const CoverWrapper = styled.div<MainProps>`
   width: 100%;
+  max-width: ${({ main }) => (main ? '650px' : '')};
 `
 const clickableAnimation = (clickable: boolean) =>
   clickable

+ 1 - 1
src/shared/components/VideoPreview/VideoPreviewBase.tsx

@@ -50,7 +50,7 @@ const VideoPreviewBase: React.FC<VideoPreviewBaseProps> = ({
 
   return (
     <Container main={main} className={className}>
-      <CoverWrapper onClick={onClick}>
+      <CoverWrapper main={main} onClick={onClick}>
         <CoverContainer clickable={clickable}>{coverNode || coverPlaceholder}</CoverContainer>
       </CoverWrapper>
       <InfoContainer main={main}>

+ 1 - 1
src/shared/components/index.ts

@@ -1,6 +1,6 @@
 export { default as Avatar } from './Avatar'
 export { default as Button } from './Button'
-export { default as Carousel } from './Carousel'
+export { default as Carousel, CAROUSEL_ARROW_HEIGHT } from './Carousel'
 export { default as Dropdown } from './Dropdown'
 export { default as Link } from './Link'
 export { default as NavButton } from './NavButton'