Procházet zdrojové kódy

Add ranking number to carousel (#1046)

Rafał Pawłow před 3 roky
rodič
revize
75a327cb1c

+ 0 - 2
src/components/OfficialJoystreamUpdate.tsx

@@ -2,7 +2,6 @@ import styled from '@emotion/styled'
 import React from 'react'
 
 import { useVideos } from '@/api/hooks'
-import { VideoOrderByInput } from '@/api/queries'
 import { VideoGallery } from '@/components'
 import { readEnv } from '@/config/envs'
 import { sizes } from '@/shared/theme'
@@ -15,7 +14,6 @@ export const OfficialJoystreamUpdate = () => {
     where: {
       channelId_eq: channelId,
     },
-    orderBy: VideoOrderByInput.CreatedAtDesc,
     limit: MAX_VIDEOS,
   })
 

+ 64 - 29
src/components/VideoGallery.tsx

@@ -1,10 +1,11 @@
 import styled from '@emotion/styled'
-import React from 'react'
+import React, { useMemo } from 'react'
 
 import { VideoFieldsFragment } from '@/api/queries'
 import { Gallery } from '@/shared/components'
 import { breakpointsOfGrid } from '@/shared/components/Grid'
-import { sizes } from '@/shared/theme'
+import { AvatarContainer } from '@/shared/components/VideoTileBase/VideoTileBase.styles'
+import { media, sizes } from '@/shared/theme'
 
 import { VideoTile } from './VideoTile'
 
@@ -27,26 +28,13 @@ type VideoGalleryProps = {
   onRemoveButtonClick?: (id: string) => void
   onVideoNotFound?: (id: string) => void
   onVideoClick?: (id: string) => void
+  hasRanking?: boolean
   seeAllUrl?: string
 }
 
 const PLACEHOLDERS_COUNT = 12
-
-// This is needed since Gliderjs and the Grid have different resizing policies
-const breakpoints = breakpointsOfGrid({
-  breakpoints: 6,
-  minItemWidth: 300,
-  gridColumnGap: 24,
-  viewportContainerDifference: 64,
-}).map((breakpoint, idx) => ({
-  breakpoint,
-  settings: {
-    slidesToShow: idx + 1,
-    slidesToScroll: idx + 1,
-  },
-}))
-
 const MIN_VIDEO_PREVIEW_WIDTH = 281
+const CAROUSEL_SMALL_BREAKPOINT = 688
 
 export const VideoGallery: React.FC<VideoGalleryProps> = ({
   title,
@@ -57,7 +45,33 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
   onRemoveButtonClick,
   onVideoNotFound,
   seeAllUrl,
+  hasRanking = true,
 }) => {
+  const breakpoints = useMemo(() => {
+    return breakpointsOfGrid({
+      breakpoints: 6,
+      minItemWidth: 300,
+      gridColumnGap: 24,
+      viewportContainerDifference: 64,
+    }).map((breakpoint, idx) => {
+      if (breakpoint <= CAROUSEL_SMALL_BREAKPOINT && hasRanking) {
+        return {
+          breakpoint,
+          settings: {
+            slidesToShow: idx + 1.5,
+            slidesToScroll: idx + 1,
+          },
+        }
+      }
+      return {
+        breakpoint,
+        settings: {
+          slidesToShow: idx + 1,
+          slidesToScroll: idx + 1,
+        },
+      }
+    })
+  }, [hasRanking])
   if (!loading && videos?.length === 0) {
     return null
   }
@@ -79,25 +93,46 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
       seeAllUrl={seeAllUrl}
     >
       {[...videos, ...placeholderItems]?.map((video, idx) => (
-        <StyledVideoTile
-          id={video.id}
-          progress={video?.progress}
-          key={idx}
-          removeButton={video ? removeButton : false}
-          onClick={createClickHandler(video.id)}
-          onNotFound={createNotFoundHandler(video.id)}
-          onRemoveButtonClick={createRemoveButtonClickHandler(video.id)}
-        />
+        <GalleryWrapper key={`${idx}-${video.id}`} hasRanking={hasRanking}>
+          <StyledVideoTile
+            id={video.id}
+            progress={video?.progress}
+            removeButton={video ? removeButton : false}
+            onClick={createClickHandler(video.id)}
+            onNotFound={createNotFoundHandler(video.id)}
+            onRemoveButtonClick={createRemoveButtonClickHandler(video.id)}
+            rankingNumber={hasRanking ? idx + 1 : undefined}
+          />
+        </GalleryWrapper>
       ))}
     </Gallery>
   )
 }
 
 const StyledVideoTile = styled(VideoTile)`
+  flex-shrink: 0;
+
+  ${AvatarContainer} {
+    display: none;
+
+    ${media.medium} {
+      display: block;
+    }
+  }
+`
+
+const GalleryWrapper = styled.div<{ hasRanking?: boolean }>`
+  position: relative;
+  ${({ hasRanking }) => `
+    display: ${hasRanking ? 'flex' : 'block'};
+    justify-content: ${hasRanking ? 'flex-end' : 'unset'};
+  `}
+
+  ${StyledVideoTile} {
+    width: ${({ hasRanking }) => (hasRanking ? '78%' : '100%')};
+  }
+
   & + & {
     margin-left: ${sizes(6)};
   }
-
-  /* MIN_VIDEO_TILE_WIDTH */
-  min-width: 300px;
 `

+ 2 - 1
src/components/VideoTile.tsx

@@ -19,7 +19,7 @@ export type VideoTileProps = {
 } & VideoTileBaseMetaProps &
   Pick<VideoTileBaseProps, 'progress' | 'className'>
 
-export const VideoTile: React.FC<VideoTileProps> = ({ id, onNotFound, ...metaProps }) => {
+export const VideoTile: React.FC<VideoTileProps> = ({ id, onNotFound, rankingNumber, ...metaProps }) => {
   const { video, loading, videoHref } = useVideoSharedLogic({ id, isDraft: false, onNotFound })
   const { url: thumbnailPhotoUrl } = useAsset({
     entity: video,
@@ -45,6 +45,7 @@ export const VideoTile: React.FC<VideoTileProps> = ({ id, onNotFound, ...metaPro
       thumbnailUrl={thumbnailPhotoUrl}
       isLoading={loading}
       contentKey={id}
+      rankingNumber={rankingNumber}
       {...metaProps}
     />
   )

+ 11 - 1
src/shared/components/Carousel/Carousel.style.ts

@@ -1,6 +1,6 @@
 import styled from '@emotion/styled'
 
-import { colors, sizes, transitions, typography, zIndex } from '@/shared/theme'
+import { colors, media, sizes, transitions, typography, zIndex } from '@/shared/theme'
 
 import { IconButton } from '../IconButton'
 
@@ -16,11 +16,16 @@ type HasPadding = {
 }
 
 export const Arrow = styled(IconButton)`
+  display: none;
   z-index: ${zIndex.nearOverlay};
   cursor: pointer;
   padding: ${sizes(2)};
   font-size: ${typography.sizes.subtitle2};
 
+  ${media.medium} {
+    display: block;
+  }
+
   &.disabled {
     opacity: 0.5;
   }
@@ -54,6 +59,11 @@ export const Track = styled.div`
 
 export const Dots = styled.div`
   margin-top: ${sizes(13)};
+  display: none;
+
+  ${media.medium} {
+    display: flex;
+  }
 
   .glider-dot {
     background-color: transparent;

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

@@ -22,5 +22,6 @@ export const TitleWrapper = styled.div`
 `
 
 export const SeeAllLink = styled(Button)`
+  flex-shrink: 0;
   margin-left: ${sizes(8)};
 `

+ 33 - 0
src/shared/components/VideoTileBase/VideoTileBase.styles.tsx

@@ -29,6 +29,7 @@ type ScalesWithCoverProps = {
 }
 
 export const CoverWrapper = styled.div<MainProps>`
+  position: relative;
   width: 100%;
   max-width: ${({ main }) => (main ? '650px' : '')};
 `
@@ -313,3 +314,35 @@ export const CoverTopLeftContainer = styled.div`
   top: ${sizes(2)};
   left: ${sizes(2)};
 `
+
+export const RankingNumber = styled.span`
+  position: absolute;
+  top: 0;
+  left: -48px;
+  height: 100%;
+  color: black;
+  font-weight: 700;
+  font-size: 100px;
+  -webkit-text-stroke-width: 4px;
+  -webkit-text-stroke-color: ${colors.gray[500]};
+  font-family: 'PxGrotesk', sans-serif;
+  letter-spacing: -0.17em;
+  display: flex;
+  align-items: center;
+
+  ${media.large} {
+    align-items: flex-end;
+    font-size: 160px;
+    left: -77px;
+  }
+
+  ${media.xlarge} {
+    left: -75px;
+    font-size: 150px;
+  }
+
+  ${media.xxlarge} {
+    left: -88px;
+    font-size: 180px;
+  }
+`

+ 4 - 0
src/shared/components/VideoTileBase/VideoTileBase.tsx

@@ -45,6 +45,7 @@ import {
   ProgressBar,
   ProgressOverlay,
   PublishingStateText,
+  RankingNumber,
   RemoveButton,
   SpacedSkeletonLoader,
   StyledAvatar,
@@ -66,6 +67,7 @@ export type VideoTileBaseMetaProps = {
   onChannelClick?: (e: React.MouseEvent<HTMLElement>) => void
   onCoverResize?: (width: number | undefined, height: number | undefined) => void
   onRemoveButtonClick?: (e: React.MouseEvent<HTMLElement>) => void
+  rankingNumber?: number
 }
 
 export type VideoTilePublisherProps =
@@ -153,6 +155,7 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   onCopyVideoURLClick,
   onDeleteVideoClick,
   isPullupDisabled,
+  rankingNumber,
 }) => {
   const { openContextMenu, contextMenuOpts } = useContextMenu()
   const [scalingFactor, setScalingFactor] = useState(MIN_SCALING_FACTOR)
@@ -202,6 +205,7 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   return (
     <Container main={main} className={className}>
       <CoverWrapper main={main}>
+        {rankingNumber && <RankingNumber>{rankingNumber}</RankingNumber>}
         <CoverContainer clickable={clickable}>
           <SwitchTransition>
             <CSSTransition