ソースを参照

Extract rankingNumber into Carousel (#1151)

* remove RankingNumber from VideoTileBase

* move div wrapper to VideoGallery

* remove ranking from ChannelCardBase

* font-size adjustments

* refactor ranking number, create new component

* adjustments

* more adjustments
Bartosz Dryl 3 年 前
コミット
d2de487297

+ 1 - 3
src/components/ChannelCard.tsx

@@ -7,12 +7,11 @@ import { ChannelCardBase } from '@/shared/components'
 
 export type ChannelCardProps = {
   id?: string
-  rankingNumber?: number
   className?: string
   onClick?: () => void
 }
 
-export const ChannelCard: React.FC<ChannelCardProps> = ({ id, rankingNumber, className }) => {
+export const ChannelCard: React.FC<ChannelCardProps> = ({ id, className }) => {
   const { channel, loading } = useChannel(id ?? '', { skip: !id })
   const { url } = useAsset({ entity: channel, assetType: AssetType.AVATAR })
 
@@ -32,7 +31,6 @@ export const ChannelCard: React.FC<ChannelCardProps> = ({ id, rankingNumber, cla
       follows={channel?.follows}
       onFollow={handleFollow}
       isFollowing={isFollowing}
-      rankingNumber={rankingNumber}
       title={channel?.title}
     />
   )

+ 10 - 4
src/components/ChannelGallery.tsx

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
 
 import { BasicChannelFieldsFragment } from '@/api/queries'
 import { ChannelCard } from '@/components/ChannelCard'
-import { Gallery, breakpointsOfGrid } from '@/shared/components'
+import { Gallery, RankingNumberTile, breakpointsOfGrid } from '@/shared/components'
 
 type ChannelGalleryProps = {
   title?: string
@@ -49,9 +49,15 @@ export const ChannelGallery: React.FC<ChannelGalleryProps> = ({ title, channels
   const placeholderItems = Array.from({ length: loading ? PLACEHOLDERS_COUNT : 0 }, () => ({ id: undefined }))
   return (
     <Gallery title={title} responsive={breakpoints} itemWidth={350} dotsVisible>
-      {[...(channels ? channels : []), ...placeholderItems].map((channel, idx) => (
-        <ChannelCard key={idx} id={channel.id} rankingNumber={hasRanking ? idx + 1 : undefined} />
-      ))}
+      {[...(channels ? channels : []), ...placeholderItems].map((channel, idx) =>
+        hasRanking ? (
+          <RankingNumberTile variant="channel" rankingNumber={idx + 1} key={idx}>
+            <ChannelCard id={channel.id} />
+          </RankingNumberTile>
+        ) : (
+          <ChannelCard key={idx} id={channel.id} />
+        )
+      )}
     </Gallery>
   )
 }

+ 18 - 23
src/components/VideoGallery.tsx

@@ -2,10 +2,10 @@ import styled from '@emotion/styled'
 import React, { useMemo } from 'react'
 
 import { VideoFieldsFragment } from '@/api/queries'
-import { Gallery } from '@/shared/components'
+import { Gallery, RankingNumberTile } from '@/shared/components'
 import { breakpointsOfGrid } from '@/shared/components/Grid'
 import { AvatarContainer } from '@/shared/components/VideoTileBase/VideoTileBase.styles'
-import { media, sizes } from '@/shared/theme'
+import { media } from '@/shared/theme'
 
 import { VideoTile } from './VideoTile'
 
@@ -95,19 +95,30 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
       seeAllUrl={seeAllUrl}
       className={className}
     >
-      {[...videos, ...placeholderItems]?.map((video, idx) => (
-        <GalleryWrapper key={`${idx}-${video.id}`} hasRanking={hasRanking}>
+      {[...videos, ...placeholderItems]?.map((video, idx) =>
+        hasRanking ? (
+          <RankingNumberTile variant="video" rankingNumber={idx + 1} key={`${idx}-${video.id}`}>
+            <StyledVideoTile
+              id={video.id}
+              progress={video?.progress}
+              removeButton={video ? removeButton : false}
+              onClick={createClickHandler(video.id)}
+              onNotFound={createNotFoundHandler(video.id)}
+              onRemoveButtonClick={createRemoveButtonClickHandler(video.id)}
+            />
+          </RankingNumberTile>
+        ) : (
           <StyledVideoTile
+            key={`${idx}-${video.id}`}
             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>
   )
 }
@@ -123,19 +134,3 @@ const StyledVideoTile = styled(VideoTile)`
     }
   }
 `
-
-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)};
-  }
-`

+ 1 - 2
src/components/VideoTile.tsx

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

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

@@ -47,7 +47,12 @@ export const GliderContainer = styled.div`
 `
 
 export const Track = styled.div`
-  align-items: flex-start;
+  .glider-slide:not(:first-of-type) {
+    margin-left: ${sizes(4)};
+    ${media.large} {
+      margin-left: ${sizes(6)};
+    }
+  }
 `
 
 export const Dots = styled.div`

+ 0 - 1
src/shared/components/Carousel/Carousel.tsx

@@ -99,7 +99,6 @@ export const Carousel = forwardRef<
       getPrevArrowProps,
       getNextArrowProps,
     }))
-
     return (
       <Container {...getContainerProps({ className })}>
         <GliderContainer {...getGliderProps()} ref={gliderRef}>

+ 0 - 7
src/shared/components/ChannelCardBase/ChannelCardBase.stories.tsx

@@ -11,15 +11,8 @@ export default {
     avatarUrl: 'https://eu-central-1.linodeobjects.com/atlas-assets/channel-avatars/2.jpg',
     title: 'Example Channel',
     id: '3',
-    rankingNumber: 4,
     follows: 200,
   },
-  argTypes: {
-    variant: {
-      control: { type: 'select', options: ['primary', 'secondary'] },
-      defaultValue: 'primary',
-    },
-  },
   decorators: [
     (Story) => (
       <BrowserRouter>

+ 3 - 49
src/shared/components/ChannelCardBase/ChannelCardBase.style.ts

@@ -1,29 +1,15 @@
 import styled from '@emotion/styled'
 import { Link } from 'react-router-dom'
 
-import { colors, media, sizes, transitions } from '@/shared/theme'
+import { colors, sizes, transitions } from '@/shared/theme'
 
 import { Avatar } from '../Avatar'
 import { Button } from '../Button'
 import { Text } from '../Text'
 
-type ChannelCardWrapperProps = {
-  hasRanking?: boolean
-}
-
-export const ChannelCardWrapper = styled.div<ChannelCardWrapperProps>`
-  position: relative;
-  display: flex;
-  justify-content: ${({ hasRanking }) => (hasRanking ? 'flex-end' : 'unset')};
-  width: 100%;
-
-  ${() => ChannelCardArticle} {
-    width: ${({ hasRanking }) => (hasRanking ? '78%' : '100%')};
-  }
-`
-
 export const ChannelCardArticle = styled.article`
   position: relative;
+  display: flex;
 
   :hover:not(:active) {
     ${() => ChannelCardAnchor} {
@@ -34,6 +20,7 @@ export const ChannelCardArticle = styled.article`
 `
 
 export const ChannelCardAnchor = styled(Link)`
+  width: 100%;
   text-decoration: none;
   align-items: center;
   transition: transform, box-shadow;
@@ -74,36 +61,3 @@ export const ChannelFollows = styled(Text)`
 export const FollowButton = styled(Button)`
   margin-top: ${sizes(4)};
 `
-
-export const RankingNumber = styled.span`
-  position: absolute;
-  top: -8px;
-  left: -48px;
-  z-index: -1;
-  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-start;
-    font-size: 160px;
-    left: -77px;
-  }
-
-  ${media.xlarge} {
-    left: -75px;
-    font-size: 150px;
-  }
-
-  ${media.xxlarge} {
-    left: -88px;
-    font-size: 180px;
-  }
-`

+ 26 - 34
src/shared/components/ChannelCardBase/ChannelCardBase.tsx

@@ -6,12 +6,10 @@ import { formatNumberShort } from '@/utils/number'
 import {
   ChannelCardAnchor,
   ChannelCardArticle,
-  ChannelCardWrapper,
   ChannelFollows,
   ChannelTitle,
   FollowButton,
   InfoWrapper,
-  RankingNumber,
   StyledAvatar,
 } from './ChannelCardBase.style'
 
@@ -19,7 +17,6 @@ import { SkeletonLoader } from '../SkeletonLoader'
 
 export type ChannelCardBaseProps = {
   id?: string | null
-  rankingNumber?: number
   isLoading?: boolean
   title?: string | null
   follows?: number | null
@@ -32,7 +29,6 @@ export type ChannelCardBaseProps = {
 
 export const ChannelCardBase: React.FC<ChannelCardBaseProps> = ({
   id,
-  rankingNumber,
   isLoading,
   title,
   follows,
@@ -42,36 +38,32 @@ export const ChannelCardBase: React.FC<ChannelCardBaseProps> = ({
   className,
   onClick,
 }) => {
-  const hasRanking = !!rankingNumber
   return (
-    <ChannelCardWrapper className={className} hasRanking={hasRanking}>
-      <ChannelCardArticle>
-        {hasRanking && <RankingNumber>{rankingNumber}</RankingNumber>}
-        <ChannelCardAnchor onClick={onClick} to={absoluteRoutes.viewer.channel(id || '')}>
-          <StyledAvatar size="channel-card" loading={isLoading} assetUrl={avatarUrl} />
-          <InfoWrapper>
-            {isLoading ? (
-              <SkeletonLoader width="100px" height="20px" bottomSpace="4px" />
-            ) : (
-              <ChannelTitle variant="h6">{title}</ChannelTitle>
-            )}
-            {isLoading ? (
-              <SkeletonLoader width="70px" height="20px" bottomSpace="16px" />
-            ) : (
-              <ChannelFollows variant="body2" secondary>
-                {formatNumberShort(follows || 0)} followers
-              </ChannelFollows>
-            )}
-            {isLoading ? (
-              <SkeletonLoader width="60px" height="30px" />
-            ) : (
-              <FollowButton variant="secondary" size="small" onClick={onFollow}>
-                {isFollowing ? 'Unfollow' : 'Follow'}
-              </FollowButton>
-            )}
-          </InfoWrapper>
-        </ChannelCardAnchor>
-      </ChannelCardArticle>
-    </ChannelCardWrapper>
+    <ChannelCardArticle className={className}>
+      <ChannelCardAnchor onClick={onClick} to={absoluteRoutes.viewer.channel(id || '')}>
+        <StyledAvatar size="channel-card" loading={isLoading} assetUrl={avatarUrl} />
+        <InfoWrapper>
+          {isLoading ? (
+            <SkeletonLoader width="100px" height="20px" bottomSpace="4px" />
+          ) : (
+            <ChannelTitle variant="h6">{title}</ChannelTitle>
+          )}
+          {isLoading ? (
+            <SkeletonLoader width="70px" height="20px" bottomSpace="16px" />
+          ) : (
+            <ChannelFollows variant="body2" secondary>
+              {formatNumberShort(follows || 0)} followers
+            </ChannelFollows>
+          )}
+          {isLoading ? (
+            <SkeletonLoader width="60px" height="30px" />
+          ) : (
+            <FollowButton variant="secondary" size="small" onClick={onFollow}>
+              {isFollowing ? 'Unfollow' : 'Follow'}
+            </FollowButton>
+          )}
+        </InfoWrapper>
+      </ChannelCardAnchor>
+    </ChannelCardArticle>
   )
 }

+ 74 - 0
src/shared/components/RankingNumberTile/RankingNumberTile.style.ts

@@ -0,0 +1,74 @@
+import { css } from '@emotion/react'
+import styled from '@emotion/styled'
+
+import { colors, media, sizes } from '@/shared/theme'
+
+export const RankingNumberTileWrapper = styled.div`
+  position: relative;
+  display: flex;
+  justify-content: flex-end;
+`
+
+const variantStyles = (variant: 'channel' | 'video') => {
+  switch (variant) {
+    case 'channel':
+      return css`
+        height: 100%;
+        top: -${sizes(5)};
+        ${media.medium} {
+          top: 0;
+          align-items: flex-start;
+          font-size: 140px;
+        }
+        ${media.xlarge} {
+          font-size: 180px;
+        }
+      `
+    case 'video':
+      return css`
+        height: 50%;
+        ${media.medium} {
+          line-height: 0.7;
+          font-size: 140px;
+        }
+        ${media.xlarge} {
+          font-size: 180px;
+        }
+      `
+    default:
+      return null
+  }
+}
+
+type RankingNumberProps = {
+  variant: 'channel' | 'video'
+}
+export const RankingNumber = styled.div<RankingNumberProps>`
+  position: absolute;
+  line-height: 1;
+  z-index: -5;
+  left: 0;
+  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;
+
+  ${({ variant }) => variantStyles(variant)};
+`
+
+export const ChildrenWrapper = styled.div`
+  --ranking-number-gap: 48px;
+
+  width: calc(100% - var(--ranking-number-gap));
+  ${media.medium} {
+    --ranking-number-gap: 72px;
+  }
+  ${media.xlarge} {
+    --ranking-number-gap: 92px;
+  }
+`

+ 17 - 0
src/shared/components/RankingNumberTile/RankingNumberTile.tsx

@@ -0,0 +1,17 @@
+import React from 'react'
+
+import { ChildrenWrapper, RankingNumber, RankingNumberTileWrapper } from './RankingNumberTile.style'
+
+type RankingNumberTileProps = {
+  rankingNumber: number
+  variant: 'channel' | 'video'
+}
+
+export const RankingNumberTile: React.FC<RankingNumberTileProps> = ({ rankingNumber, children, variant }) => {
+  return (
+    <RankingNumberTileWrapper>
+      <RankingNumber variant={variant}>{rankingNumber}</RankingNumber>
+      <ChildrenWrapper>{children}</ChildrenWrapper>
+    </RankingNumberTileWrapper>
+  )
+}

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

@@ -0,0 +1 @@
+export * from './RankingNumberTile'

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

@@ -318,35 +318,3 @@ 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;
-  }
-`

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

@@ -45,7 +45,6 @@ import {
   ProgressBar,
   ProgressOverlay,
   PublishingStateText,
-  RankingNumber,
   RemoveButton,
   SpacedSkeletonLoader,
   StyledAvatar,
@@ -67,7 +66,6 @@ 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 =
@@ -155,7 +153,6 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   onCopyVideoURLClick,
   onDeleteVideoClick,
   isPullupDisabled,
-  rankingNumber,
 }) => {
   const { openContextMenu, contextMenuOpts } = useContextMenu()
   const [scalingFactor, setScalingFactor] = useState(MIN_SCALING_FACTOR)
@@ -205,7 +202,6 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   return (
     <Container main={main} className={className}>
       <CoverWrapper main={main}>
-        {rankingNumber && <RankingNumber>{rankingNumber}</RankingNumber>}
         <CoverContainer clickable={clickable}>
           <SwitchTransition>
             <CSSTransition

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

@@ -48,3 +48,4 @@ export * from './LoadMoreButton'
 export * from './CallToActionButton'
 export * from './GridHeading'
 export * from './ChannelCardBase'
+export * from './RankingNumberTile'