Переглянути джерело

Fix layout shift on popular page (#1188)

* fix layout shift on popular page

* cr fixes

* heading adjustments
Bartosz Dryl 3 роки тому
батько
коміт
0d5d81c5bc

+ 4 - 2
src/components/ChannelGallery.tsx

@@ -42,11 +42,13 @@ export const ChannelGallery: React.FC<ChannelGalleryProps> = ({ title, channels
     })
   }, [hasRanking])
 
-  if (!loading && channels?.length === 0) {
+  if (loading === false && channels?.length === 0) {
     return null
   }
 
-  const placeholderItems = Array.from({ length: loading ? PLACEHOLDERS_COUNT : 0 }, () => ({ id: undefined }))
+  const placeholderItems = Array.from({ length: loading || !channels?.length ? PLACEHOLDERS_COUNT : 0 }, () => ({
+    id: undefined,
+  }))
   return (
     <Gallery title={title} responsive={breakpoints} itemWidth={350} dotsVisible>
       {[...(channels ? channels : []), ...placeholderItems].map((channel, idx) =>

+ 8 - 1
src/components/ChannelWithVideos/ChannelWithVideos.tsx

@@ -83,7 +83,14 @@ export const ChannelWithVideos: FC<ChannelWithVideosProps> = ({ channelId }) =>
           {isLoading ? (
             <SkeletonLoader width="90px" height="40px" />
           ) : (
-            <FollowButton variant="secondary" size={'medium'} onClick={() => toggleFollowing()}>
+            <FollowButton
+              variant="secondary"
+              size={'medium'}
+              onClick={(e) => {
+                e.preventDefault()
+                toggleFollowing()
+              }}
+            >
               {isFollowing ? 'Unfollow' : 'Follow'}
             </FollowButton>
           )}

+ 6 - 12
src/components/InfiniteGrids/InfiniteChannelWithVideosGrid.tsx

@@ -13,17 +13,11 @@ import {
 import { ChannelWithVideos } from '@/components/ChannelWithVideos'
 import { useInfiniteGrid } from '@/components/InfiniteGrids/useInfiniteGrid'
 import { languages } from '@/config/languages'
-import { GridHeadingContainer, LoadMoreButton, Select, SkeletonLoader, Text } from '@/shared/components'
+import { GridHeadingContainer, LoadMoreButton, Select, SkeletonLoader, Text, TitleContainer } from '@/shared/components'
 import { SvgGlyphChevronRight } from '@/shared/icons'
 import { transitions } from '@/shared/theme'
 
-import {
-  AdditionalLink,
-  LanguageSelectWrapper,
-  LoadMoreButtonWrapper,
-  Separator,
-  TitleWrapper,
-} from './InfiniteGrid.style'
+import { AdditionalLink, LanguageSelectWrapper, LoadMoreButtonWrapper, Separator } from './InfiniteGrid.style'
 
 type InfiniteChannelWithVideosGridProps = {
   onDemand?: boolean
@@ -134,9 +128,9 @@ export const InfiniteChannelWithVideosGrid: FC<InfiniteChannelWithVideosGridProp
 
   return (
     <section className={className}>
-      <TitleWrapper>
+      <GridHeadingContainer>
         {title && (
-          <GridHeadingContainer>
+          <TitleContainer>
             {!isReady ? <SkeletonLoader height={23} width={250} /> : <Text variant="h4">{title}</Text>}
             {languageSelector && (
               <CSSTransition
@@ -169,9 +163,9 @@ export const InfiniteChannelWithVideosGrid: FC<InfiniteChannelWithVideosGridProp
                 {additionalLink.name}
               </AdditionalLink>
             )}
-          </GridHeadingContainer>
+          </TitleContainer>
         )}
-      </TitleWrapper>
+      </GridHeadingContainer>
       {itemsToShow.map((channel, idx) => (
         <Fragment key={`channels-with-videos-${idx}`}>
           <ChannelWithVideos channelId={channel.id} />

+ 0 - 5
src/components/InfiniteGrids/InfiniteGrid.style.ts

@@ -7,11 +7,6 @@ export const LoadMoreButtonWrapper = styled.div`
   margin-top: ${sizes(12)};
 `
 
-export const TitleWrapper = styled.div`
-  display: flex;
-  align-items: center;
-`
-
 export const LanguageSelectWrapper = styled.div`
   margin-left: ${sizes(6)};
   width: ${sizes(34)};

+ 13 - 7
src/components/InfiniteGrids/InfiniteVideoGrid.tsx

@@ -7,17 +7,18 @@ import {
   GetVideosConnectionQueryVariables,
   VideoWhereInput,
 } from '@/api/queries'
-import { Grid, GridHeadingContainer, LoadMoreButton, SkeletonLoader, Text } from '@/shared/components'
+import { Grid, GridHeadingContainer, LoadMoreButton, SkeletonLoader, Text, TitleContainer } from '@/shared/components'
 import { SvgGlyphChevronRight } from '@/shared/icons'
 import { SentryLogger } from '@/utils/logs'
 
-import { AdditionalLink, LoadMoreButtonWrapper, TitleWrapper } from './InfiniteGrid.style'
+import { AdditionalLink, LoadMoreButtonWrapper } from './InfiniteGrid.style'
 import { useInfiniteGrid } from './useInfiniteGrid'
 
 import { VideoTile } from '../VideoTile'
 
 type InfiniteVideoGridProps = {
   title?: string
+  titleLoader?: boolean
   categoryId?: string
   channelId?: string
   channelIdIn?: string[] | null
@@ -62,6 +63,7 @@ export const InfiniteVideoGrid: React.FC<InfiniteVideoGridProps> = ({
   onDemand = false,
   additionalLink,
   isFeatured = false,
+  titleLoader,
 }) => {
   const [videosPerRow, setVideosPerRow] = useState(INITIAL_VIDEOS_PER_ROW)
   const queryVariables: { where: VideoWhereInput } = {
@@ -172,10 +174,14 @@ export const InfiniteVideoGrid: React.FC<InfiniteVideoGridProps> = ({
   // Right now we'll make the first request and then right after another one based on the resized columns
   return (
     <section className={className}>
-      <TitleWrapper>
+      <GridHeadingContainer>
         {title && (
-          <GridHeadingContainer>
-            {!ready ? <SkeletonLoader height={23} width={250} /> : <Text variant="h4">{title}</Text>}
+          <TitleContainer>
+            {(!ready || !displayedItems.length) && titleLoader ? (
+              <SkeletonLoader height={30} width={250} />
+            ) : (
+              <Text variant="h4">{title}</Text>
+            )}
             {additionalLink && (
               <AdditionalLink
                 to={additionalLink.url}
@@ -187,9 +193,9 @@ export const InfiniteVideoGrid: React.FC<InfiniteVideoGridProps> = ({
                 {additionalLink.name}
               </AdditionalLink>
             )}
-          </GridHeadingContainer>
+          </TitleContainer>
         )}
-      </TitleWrapper>
+      </GridHeadingContainer>
       <Grid onResize={(sizes) => setVideosPerRow(sizes.length)}>{gridContent}</Grid>
       {shouldShowLoadMoreButton && (
         <LoadMoreButtonWrapper>

+ 1 - 2
src/components/TopTenChannels.tsx

@@ -7,10 +7,9 @@ import { ChannelGallery } from './ChannelGallery'
 export const TopTenChannels = () => {
   const { channels, loading } = useMostFollowedChannelsAllTime({ limit: 10 })
 
-  const isLoading = loading || channels === null
   return (
     <section>
-      <ChannelGallery hasRanking channels={channels} loading={isLoading} title="Top 10 Channels" />
+      <ChannelGallery hasRanking channels={channels} loading={loading} title="Top 10 Channels" />
     </section>
   )
 }

+ 1 - 1
src/components/TopTenThisWeek.tsx

@@ -8,7 +8,7 @@ export const TopTenThisWeek = () => {
 
   return (
     <section>
-      <VideoGallery title="Top 10 this week" videos={videos || []} loading={loading} hasRanking />
+      <VideoGallery title="Top 10 this week" videos={videos} loading={loading} hasRanking />
     </section>
   )
 }

+ 6 - 4
src/components/VideoGallery.tsx

@@ -22,7 +22,7 @@ type CustomVideosType = VideoFieldsWithProgress[] | VideoWithIdAndProgress[]
 
 type VideoGalleryProps = {
   title?: string
-  videos?: CustomVideosType
+  videos?: CustomVideosType | null
   loading?: boolean
   removeButton?: boolean
   onRemoveButtonClick?: (id: string) => void
@@ -76,10 +76,12 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
       }
     })
   }, [hasRanking])
-  if (!loading && videos?.length === 0) {
+
+  if (loading === false && videos?.length === 0) {
     return null
   }
-  const placeholderItems = Array.from({ length: loading ? PLACEHOLDERS_COUNT : 0 }, () => ({
+
+  const placeholderItems = Array.from({ length: loading || !videos?.length ? PLACEHOLDERS_COUNT : 0 }, () => ({
     id: undefined,
     progress: undefined,
   }))
@@ -95,7 +97,7 @@ export const VideoGallery: React.FC<VideoGalleryProps> = ({
       seeAllUrl={seeAllUrl}
       className={className}
     >
-      {[...videos, ...placeholderItems]?.map((video, idx) =>
+      {[...(videos ? videos : []), ...placeholderItems]?.map((video, idx) =>
         hasRanking ? (
           <RankingNumberTile variant="video" rankingNumber={idx + 1} key={`${idx}-${video.id}`}>
             <StyledVideoTile

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

@@ -16,11 +16,6 @@ export const CarouselArrowsContainer = styled.div`
   margin-left: auto;
 `
 
-export const TitleWrapper = styled.div`
-  align-items: baseline;
-  display: flex;
-`
-
 export const SeeAllLink = styled(Button)`
   flex-shrink: 0;
   margin-left: ${sizes(8)};

+ 13 - 12
src/shared/components/Gallery/Gallery.tsx

@@ -1,12 +1,13 @@
 import React, { ComponentProps, useRef } from 'react'
 
-import { GridHeadingContainer } from '@/shared/components'
+import { TitleContainer } from '@/shared/components'
 import { Arrow } from '@/shared/components/Carousel/Carousel.style'
 import { SvgGlyphChevronLeft, SvgGlyphChevronRight, SvgPlayerPlay } from '@/shared/icons'
 
-import { CarouselArrowsContainer, Container, SeeAllLink, TitleWrapper } from './Gallery.style'
+import { CarouselArrowsContainer, Container, SeeAllLink } from './Gallery.style'
 
 import { Carousel, CarouselProps } from '../Carousel/Carousel'
+import { GridHeadingContainer } from '../GridHeading'
 import { Text } from '../Text'
 
 export type GalleryProps = {
@@ -28,7 +29,7 @@ export const Gallery: React.FC<GalleryProps> = ({ title, className, seeAllUrl, .
   return (
     <Container className={className}>
       <GridHeadingContainer>
-        <TitleWrapper>
+        <TitleContainer>
           {title && <Text variant="h4">{title}</Text>}
           {seeAllUrl && (
             <>
@@ -44,15 +45,15 @@ export const Gallery: React.FC<GalleryProps> = ({ title, className, seeAllUrl, .
               </SeeAllLink>
             </>
           )}
-        </TitleWrapper>
-        <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>
+          <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>
+        </TitleContainer>
       </GridHeadingContainer>
       <Carousel
         {...carouselProps}

+ 9 - 3
src/shared/components/GridHeading/GridHeading.styles.ts

@@ -4,9 +4,15 @@ import { colors, sizes } from '@/shared/theme'
 
 export const GridHeadingContainer = styled.div`
   display: flex;
-  width: 100%;
-  align-items: start;
-  margin-bottom: ${sizes(12)};
+  align-items: center;
   padding-bottom: ${sizes(5)};
   border-bottom: 1px solid ${colors.gray[700]};
+  margin-bottom: ${sizes(12)};
+`
+
+export const TitleContainer = styled.div`
+  display: flex;
+  width: 100%;
+  min-height: 40px;
+  align-items: center;
 `

+ 8 - 1
src/views/viewer/HomeView.tsx

@@ -50,13 +50,20 @@ export const HomeView: React.FC = () => {
       <VideoHero />
       <Container className={transitions.names.slide}>
         {!followedLoading && followedChannelsVideosCount ? (
-          <InfiniteVideoGrid title="Followed channels" channelIdIn={channelIdIn} ready={!followedLoading} onDemand />
+          <InfiniteVideoGrid
+            title="Followed channels"
+            channelIdIn={channelIdIn}
+            ready={!followedLoading}
+            onDemand
+            titleLoader
+          />
         ) : null}
         <InfiniteVideoGrid
           title="Popular on Joystream"
           idIn={mostViewedVideosIds}
           ready={!mostViewedVideosLoading}
           onDemand
+          titleLoader
         />
         <TopTenThisWeek />
         <OfficialJoystreamUpdate />

+ 1 - 1
src/views/viewer/NewView/NewView.tsx

@@ -11,7 +11,7 @@ import { sizes } from '@/shared/theme'
 export const NewView: React.FC = () => (
   <StyledViewWrapper>
     <Header variant="h2">New & Noteworthy</Header>
-    <InfiniteVideoGrid title="Videos worth watching" isFeatured onDemand />
+    <InfiniteVideoGrid title="Videos worth watching" isFeatured onDemand titleLoader />
     <PromisingNewChannels />
     <CallToActionWrapper>
       <CallToActionButton label="Home" to={absoluteRoutes.viewer.index()} colorVariant="yellow" icon={<SvgNavHome />} />

+ 1 - 2
src/views/viewer/PopularView/PopularView.tsx

@@ -26,11 +26,10 @@ export const PopularView: FC = () => {
   if (mostViewedVideosError) {
     throw mostViewedVideosError
   }
-
   return (
     <StyledViewWrapper>
       <Header variant="h2">Popular on Joystream</Header>
-      <VideoGallery hasRanking title="Top 10 this month" videos={videos || []} loading={loading} />
+      <VideoGallery hasRanking title="Top 10 this month" videos={videos} loading={loading} />
       <InfiniteVideoGrid title="Popular videos" idIn={mostViewedVideosIds} ready={!mostViewedVideosLoading} onDemand />
       <InfiniteChannelWithVideosGrid
         title="Popular channels"