Bladeren bron

Adjust video tile variant's font sizes and avatar (#1252)

* Adjust video tile variant's font sizes and avatar

* change video tile min width

* move small size width to const

* PR FIX

* PR FIX 2

* change InfoContainer display to grid

* fix VideoTile spacing

Co-authored-by: Klaudiusz Dembler <dev@kdembler.com>
Rafał Pawłow 3 jaren geleden
bovenliggende
commit
65832ae4d3

+ 2 - 2
src/shared/components/Grid/Grid.tsx

@@ -3,11 +3,11 @@ import styled from '@emotion/styled'
 import React, { useRef } from 'react'
 import useResizeObserver from 'use-resize-observer'
 
+import { toPx } from '@/shared/utils/styles'
+
 import { media, sizes } from '../../theme'
 import { MIN_VIDEO_TILE_WIDTH } from '../VideoTileBase'
 
-const toPx = (n: number | string) => (typeof n === 'number' ? `${n}px` : n)
-
 type GridProps = {
   gap?: number | string
   className?: string

+ 4 - 4
src/shared/components/VideoTileBase/VideoTile.stories.tsx

@@ -34,7 +34,7 @@ const Template: Story<VideoTileBaseProps> = ({ createdAt, ...args }) => {
   return (
     <BrowserRouter>
       <OverlayManagerProvider>
-        <Wrapper main={args.main}>
+        <Wrapper>
           <VideoTileBase {...args} createdAt={createdAtDate} />
         </Wrapper>
       </OverlayManagerProvider>
@@ -51,7 +51,7 @@ const Publisher: Story<VideoTileBaseProps> = ({ createdAt, ...args }) => {
   return (
     <BrowserRouter>
       <OverlayManagerProvider>
-        <Wrapper main={args.main}>
+        <Wrapper>
           <VideoTileBase
             {...args}
             publisherMode
@@ -192,8 +192,8 @@ Mixed.args = {
   },
 }
 
-const Wrapper = styled.div<{ main?: boolean }>`
-  max-width: ${({ main }) => (main ? 1200 : 350)}px;
+const Wrapper = styled.div`
+  max-width: 350px;
 `
 
 const ContainerMix = styled.div`

+ 52 - 91
src/shared/components/VideoTileBase/VideoTileBase.styles.tsx

@@ -1,9 +1,8 @@
 import { css } from '@emotion/react'
 import styled from '@emotion/styled'
-import { fluidRange } from 'polished'
 import { Link } from 'react-router-dom'
 
-import { colors, media, sizes, square, transitions, typography, zIndex } from '@/shared/theme'
+import { colors, sizes, square, transitions, typography, zIndex } from '@/shared/theme'
 
 import { Avatar } from '../Avatar'
 import { IconButton } from '../IconButton'
@@ -12,8 +11,8 @@ import { Text } from '../Text'
 
 export const HOVER_BORDER_SIZE = '2px'
 
-type MainProps = {
-  main: boolean
+type SizeProps = {
+  size?: 'small' | 'big'
 }
 
 type ChannelProps = {
@@ -24,14 +23,17 @@ type ClickableProps = {
   clickable: boolean
 }
 
-type ScalesWithCoverProps = {
-  scalingFactor: number
+type LoadingProps = {
+  isLoading?: boolean
 }
 
-export const CoverWrapper = styled.div<MainProps>`
+type ActiveProps = {
+  isActive?: boolean
+}
+
+export const CoverWrapper = styled.div`
   position: relative;
   width: 100%;
-  max-width: ${({ main }) => (main ? '650px' : '')};
 `
 
 const clickableAnimation = (clickable: boolean) =>
@@ -74,68 +76,63 @@ export const CoverContainer = styled.div<ClickableProps>`
   }
 `
 
-const mainContainerCss = css`
-  ${media.medium} {
-    flex-direction: row;
-  }
-`
-
 export const Anchor = styled(Link)`
   all: unset;
   color: inherit;
 `
 
 export const TitleHeaderAnchor = styled(Link)`
-  all: unset;
-  color: inherit;
-  display: grid;
+  margin-bottom: ${sizes(2)};
+  text-decoration: none;
+  display: block;
 `
 
-export const Container = styled.article<MainProps>`
-  width: 100%;
-  color: ${colors.gray[300]};
-  display: inline-flex;
-  flex-direction: column;
-  ${({ main }) => main && mainContainerCss}
-
-  :hover {
-    ${() => css`
-      ${KebabMenuIconContainer} {
-        display: flex;
-      }
-    `}
-  }
+export const InfoContainer = styled.div`
+  min-height: 94px;
+  display: flex;
+  flex-direction: row;
+  margin-top: ${sizes(3)};
 `
 
-const mainInfoContainerCss = css`
-  ${media.medium} {
-    margin: ${sizes(8)} 0 0 ${sizes(6)};
-  }
+export const AvatarContainer = styled.div`
+  ${square(40)};
+
+  margin-right: ${sizes(3)};
 `
 
-export const InfoContainer = styled.div<MainProps>`
-  min-height: 86px;
-  display: flex;
-  margin-top: ${({ main }) => (main ? sizes(4) : sizes(3))};
-  ${({ main }) => main && mainInfoContainerCss};
+export const TextContainer = styled.div`
+  flex: 1;
 `
 
-export const AvatarContainer = styled.div<ScalesWithCoverProps>`
-  width: calc(40px * ${(props) => props.scalingFactor});
-  min-width: calc(40px * ${(props) => props.scalingFactor});
-  height: calc(40px * ${(props) => props.scalingFactor});
-  margin-right: ${sizes(3)};
+export const KebabMenuButtonIcon = styled(IconButton)<ActiveProps>`
+  ${square(32)};
+
+  margin-left: ${sizes(2)};
+  opacity: ${({ isActive }) => (isActive ? 1 : 0)};
+  pointer-events: ${({ isActive }) => (isActive ? 'auto' : 'none')};
 `
 
-export const TextContainer = styled.div`
-  width: calc(100% - 30px);
+const containerHoverStyles = ({ isLoading }: LoadingProps) =>
+  !isLoading
+    ? css`
+        :hover {
+          ${KebabMenuButtonIcon} {
+            opacity: 1;
+            pointer-events: auto;
+          }
+        }
+      `
+    : null
 
-  ${media.compact} {
-    width: calc(100% - 87px);
-  }
+export const Container = styled.article<LoadingProps>`
+  width: 100%;
+  display: inline-flex;
+  flex-direction: column;
+
+  ${containerHoverStyles};
 `
 
-type MetaContainerProps = { noMarginTop: boolean } & MainProps
+type MetaContainerProps = { noMarginTop: boolean }
 export const MetaContainer = styled.div<MetaContainerProps>`
   width: 100%;
 `
@@ -233,31 +230,6 @@ export const PublishingStateText = styled(Text)`
   margin-left: ${sizes(1.5)};
 `
 
-export const KebabMenuIconContainer = styled.div<{ isActive?: boolean }>`
-  ${square(sizes(9))};
-
-  display: ${({ isActive }) => (isActive ? 'flex' : 'none')};
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
-  position: relative;
-  border-radius: 100%;
-  transition: all ${transitions.timings.regular} ${transitions.easing};
-  margin-left: auto;
-
-  path {
-    transition: all ${transitions.timings.regular} ${transitions.easing};
-  }
-
-  &:hover {
-    path:not([fill='none']) {
-      fill: ${colors.white};
-    }
-
-    background-color: ${colors.transparentPrimary[18]};
-  }
-`
-
 export const CoverDurationOverlay = styled.div`
   position: absolute;
   bottom: ${sizes(2)};
@@ -275,14 +247,9 @@ export const StyledAvatar = styled(Avatar)<ChannelProps>`
   cursor: ${({ channelClickable }) => (channelClickable ? 'pointer' : 'auto')};
 `
 
-export const TitleHeader = styled(Text)<MainProps & ScalesWithCoverProps & ClickableProps>`
-  margin: 0;
-  margin-bottom: ${sizes(2)};
-  font-weight: ${typography.weights.bold};
-  font-size: calc(${(props) => props.scalingFactor} * ${typography.sizes.h6});
-  ${({ main }) => main && fluidRange({ prop: 'fontSize', fromSize: '24px', toSize: '40px' })};
-
-  line-height: ${({ main }) => (main ? 1 : 1.25)};
+export const TitleHeader = styled(Text)<ClickableProps & SizeProps>`
+  font-size: ${({ size }) => (size === 'small' ? typography.sizes.h6 : typography.sizes.subtitle1)};
+  line-height: ${({ size }) => (size === 'small' ? typography.lineHeights.h6 : typography.lineHeights.subtitle1)};
   cursor: ${(props) => (props.clickable ? 'pointer' : 'auto')};
   display: -webkit-box;
   -webkit-line-clamp: 2;
@@ -291,17 +258,11 @@ export const TitleHeader = styled(Text)<MainProps & ScalesWithCoverProps & Click
   overflow-wrap: break-word;
 `
 
-export const ChannelHandle = styled(Text)<ChannelProps & ScalesWithCoverProps>`
-  font-size: calc(${(props) => props.scalingFactor} * ${typography.sizes.subtitle2});
+export const ChannelHandle = styled(Text)<ChannelProps>`
   display: inline-block;
   cursor: ${({ channelClickable }) => (channelClickable ? 'pointer' : 'auto')};
 `
 
-export const MetaText = styled(Text)<MainProps & ScalesWithCoverProps>`
-  font-size: ${({ main, scalingFactor }) =>
-    main ? typography.sizes.h6 : `calc(${scalingFactor}*${typography.sizes.subtitle2}) `};
-`
-
 export const SpacedSkeletonLoader = styled(SkeletonLoader)`
   margin-top: 6px;
 `

+ 68 - 85
src/shared/components/VideoTileBase/VideoTileBase.tsx

@@ -39,9 +39,8 @@ import {
   CoverVideoPublishingStateOverlay,
   CoverWrapper,
   InfoContainer,
-  KebabMenuIconContainer,
+  KebabMenuButtonIcon,
   MetaContainer,
-  MetaText,
   ProgressBar,
   ProgressOverlay,
   PublishingStateText,
@@ -60,11 +59,9 @@ import { Text } from '../Text'
 export type VideoTileBaseMetaProps = {
   showChannel?: boolean
   showMeta?: boolean
-  main?: boolean
   removeButton?: boolean
   onClick?: (event: React.MouseEvent<HTMLElement>) => void
   onChannelClick?: (e: React.MouseEvent<HTMLElement>) => void
-  onCoverResize?: (width: number | undefined, height: number | undefined) => void
   onRemoveButtonClick?: (e: React.MouseEvent<HTMLElement>) => void
 }
 
@@ -112,15 +109,10 @@ export type VideoTileBaseProps = {
 } & VideoTileBaseMetaProps &
   VideoTilePublisherProps
 
-export const MIN_VIDEO_TILE_WIDTH = 300
-const MAX_VIDEO_PREVIEW_WIDTH = 600
-const MIN_SCALING_FACTOR = 1
-const MAX_SCALING_FACTOR = 1.375
-// Linear Interpolation, see https://en.wikipedia.org/wiki/Linear_interpolation
-const calculateScalingFactor = (videoTileWidth: number) =>
-  MIN_SCALING_FACTOR +
-  ((videoTileWidth - MIN_VIDEO_TILE_WIDTH) * (MAX_SCALING_FACTOR - MIN_SCALING_FACTOR)) /
-    (MAX_VIDEO_PREVIEW_WIDTH - MIN_VIDEO_TILE_WIDTH)
+type TileSize = 'small' | 'big' | undefined
+
+export const MIN_VIDEO_TILE_WIDTH = 250
+const SMALL_SIZE_WIDTH = 300
 
 export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   title,
@@ -132,7 +124,6 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   views,
   thumbnailUrl,
   hasThumbnailUploadFailed,
-  onCoverResize,
   channelHref,
   videoHref,
   isLoadingThumbnail,
@@ -140,7 +131,6 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   isLoading = true,
   showChannel = true,
   showMeta = true,
-  main = false,
   removeButton = false,
   videoPublishState = 'default',
   publisherMode = false,
@@ -157,20 +147,22 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   isPullupDisabled,
 }) => {
   const { openContextMenu, contextMenuOpts } = useContextMenu()
-  const [scalingFactor, setScalingFactor] = useState(MIN_SCALING_FACTOR)
+  const [tileSize, setTileSize] = useState<TileSize>(undefined)
+
   const { ref: imgRef } = useResizeObserver<HTMLImageElement>({
     onResize: (size) => {
-      const { width: videoTileWidth, height: videoTileHeight } = size
-      if (onCoverResize) {
-        onCoverResize(videoTileWidth, videoTileHeight)
-      }
-      if (videoTileWidth && !main) {
-        setScalingFactor(calculateScalingFactor(videoTileWidth))
+      const { width: videoTileWidth } = size
+      if (videoTileWidth) {
+        if (tileSize !== 'small' && videoTileWidth < SMALL_SIZE_WIDTH) {
+          setTileSize('small')
+        }
+        if (tileSize !== 'big' && videoTileWidth >= SMALL_SIZE_WIDTH) {
+          setTileSize('big')
+        }
       }
     },
   })
   const [failedLoadImage, setFailedLoadImage] = useState(false)
-  const displayChannel = showChannel && !main
   const clickable = (!!onClick || !!videoHref) && !isLoading
   const channelClickable = (!!onChannelClick || !!channelHref) && !isLoading
 
@@ -202,8 +194,8 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
   }
 
   return (
-    <Container main={main} className={className}>
-      <CoverWrapper main={main}>
+    <Container className={className} isLoading={isLoading}>
+      <CoverWrapper>
         <CoverContainer ref={imgRef} clickable={clickable}>
           <SwitchTransition>
             <CSSTransition
@@ -278,15 +270,15 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
           <ProgressBar style={{ width: `${progress}%` }} />
         </ProgressOverlay>
       )}
-      <InfoContainer main={main}>
-        {displayChannel && (
+      <InfoContainer>
+        {showChannel && (
           <SwitchTransition>
             <CSSTransition
               key={isLoadingAvatar ? 'avatar-placeholder' : 'avatar'}
               timeout={parseInt(transitions.timings.sharp)}
               classNames={transitions.names.fade}
             >
-              <AvatarContainer scalingFactor={scalingFactor}>
+              <AvatarContainer>
                 {isLoading || isLoadingAvatar ? (
                   <SkeletonLoader rounded />
                 ) : (
@@ -310,30 +302,23 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
           >
             <TextContainer>
               {isLoading ? (
-                <SkeletonLoader height={main ? 45 : 18} width="60%" />
+                <SkeletonLoader height={18} width="60%" />
               ) : (
                 <TitleHeaderAnchor to={videoHref ?? ''} onClick={createAnchorClickHandler(videoHref)}>
-                  <TitleHeader
-                    variant="h6"
-                    main={main}
-                    scalingFactor={scalingFactor}
-                    onClick={onClick}
-                    clickable={clickable}
-                  >
+                  <TitleHeader variant="h6" size={tileSize} onClick={onClick} clickable={clickable}>
                     {title || 'Untitled'}
                   </TitleHeader>
                 </TitleHeaderAnchor>
               )}
-              {displayChannel &&
+              {showChannel &&
                 (isLoading ? (
                   <SpacedSkeletonLoader height="12px" width="60%" />
                 ) : (
                   <Anchor to={channelHref ?? ''} onClick={createAnchorClickHandler(channelHref)}>
                     <ChannelHandle
-                      variant="subtitle2"
+                      variant="body2"
                       channelClickable={channelClickable}
                       onClick={handleChannelClick}
-                      scalingFactor={scalingFactor}
                       secondary
                     >
                       {channelTitle}
@@ -341,63 +326,61 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
                   </Anchor>
                 ))}
               {showMeta && (
-                <MetaContainer noMarginTop={!showChannel} main={main}>
+                <MetaContainer noMarginTop={!showChannel}>
                   {isLoading ? (
-                    <SpacedSkeletonLoader height={main ? 16 : 12} width={main ? '40%' : '80%'} />
+                    <SpacedSkeletonLoader height={12} width={'80%'} />
                   ) : createdAt ? (
-                    <MetaText variant="subtitle2" main={main} scalingFactor={scalingFactor} secondary>
+                    <Text variant="body2" secondary>
                       {isDraft
                         ? `Last updated ${formatDateAgo(createdAt)}`
-                        : formatVideoViewsAndDate(views ?? null, createdAt, { fullViews: main })}
-                    </MetaText>
+                        : formatVideoViewsAndDate(views ?? null, createdAt)}
+                    </Text>
                   ) : null}
                 </MetaContainer>
               )}
             </TextContainer>
           </CSSTransition>
         </SwitchTransition>
-        {!isLoading && (
-          <>
-            <KebabMenuIconContainer
-              onClick={(event) => openContextMenu(event, 200)}
-              isActive={contextMenuOpts.isActive}
-            >
-              <SvgGlyphMore />
-            </KebabMenuIconContainer>
-            <ContextMenu contextMenuOpts={contextMenuOpts}>
-              {publisherMode ? (
-                <>
-                  {onOpenInTabClick && (
-                    <ContextMenuItem icon={<SvgGlyphPlay />} onClick={onOpenInTabClick}>
-                      Play in Joystream
-                    </ContextMenuItem>
-                  )}
-                  {onCopyVideoURLClick && (
-                    <ContextMenuItem icon={<SvgGlyphCopy />} onClick={onCopyVideoURLClick}>
-                      Copy video URL
-                    </ContextMenuItem>
-                  )}
-                  {onEditVideoClick && (
-                    <ContextMenuItem icon={<SvgGlyphEdit />} onClick={onEditVideoClick}>
-                      {isDraft ? 'Edit draft' : 'Edit video'}
-                    </ContextMenuItem>
-                  )}
-                  {onDeleteVideoClick && (
-                    <ContextMenuItem icon={<SvgGlyphTrash />} onClick={onDeleteVideoClick}>
-                      {isDraft ? 'Delete draft' : 'Delete video'}
-                    </ContextMenuItem>
-                  )}
-                </>
-              ) : (
-                onCopyVideoURLClick && (
-                  <ContextMenuItem onClick={onCopyVideoURLClick} icon={<SvgGlyphCopy />}>
-                    Copy video URL
-                  </ContextMenuItem>
-                )
+        <KebabMenuButtonIcon
+          onClick={(event) => openContextMenu(event, 200)}
+          variant="tertiary"
+          size="small"
+          isActive={contextMenuOpts.isActive}
+        >
+          <SvgGlyphMore />
+        </KebabMenuButtonIcon>
+        <ContextMenu contextMenuOpts={contextMenuOpts}>
+          {publisherMode ? (
+            <>
+              {onOpenInTabClick && (
+                <ContextMenuItem icon={<SvgGlyphPlay />} onClick={onOpenInTabClick}>
+                  Play in Joystream
+                </ContextMenuItem>
               )}
-            </ContextMenu>
-          </>
-        )}
+              {onCopyVideoURLClick && (
+                <ContextMenuItem icon={<SvgGlyphCopy />} onClick={onCopyVideoURLClick}>
+                  Copy video URL
+                </ContextMenuItem>
+              )}
+              {onEditVideoClick && (
+                <ContextMenuItem icon={<SvgGlyphEdit />} onClick={onEditVideoClick}>
+                  {isDraft ? 'Edit draft' : 'Edit video'}
+                </ContextMenuItem>
+              )}
+              {onDeleteVideoClick && (
+                <ContextMenuItem icon={<SvgGlyphTrash />} onClick={onDeleteVideoClick}>
+                  {isDraft ? 'Delete draft' : 'Delete video'}
+                </ContextMenuItem>
+              )}
+            </>
+          ) : (
+            onCopyVideoURLClick && (
+              <ContextMenuItem onClick={onCopyVideoURLClick} icon={<SvgGlyphCopy />}>
+                Copy video URL
+              </ContextMenuItem>
+            )
+          )}
+        </ContextMenu>
       </InfoContainer>
     </Container>
   )

+ 5 - 1
src/shared/theme/sizes.ts

@@ -1,5 +1,7 @@
 import { css } from '@emotion/react'
 
+import { toPx } from '@/shared/utils/styles'
+
 const base = 4
 
 export function sizes<B extends boolean>(n: number, raw?: B): B extends false ? string : number
@@ -19,9 +21,11 @@ export const zIndex = {
   globalOverlay: 999,
 }
 
-export function square(size: string | number) {
+export function square(rawSize: string | number) {
+  const size = toPx(rawSize)
   return css`
     width: ${size};
+    min-width: ${size};
     height: ${size};
   `
 }

+ 1 - 0
src/shared/utils/styles.ts

@@ -0,0 +1 @@
+export const toPx = (n: number | string) => (typeof n === 'number' ? `${n}px` : n)

+ 2 - 10
src/views/viewer/SearchOverlayView/SearchResults/AllResultsTab.tsx

@@ -4,7 +4,6 @@ import React from 'react'
 import { BasicChannelFieldsFragment, VideoFieldsFragment } from '@/api/queries'
 import { ChannelGallery } from '@/components/ChannelGallery'
 import { VideoGallery } from '@/components/VideoGallery'
-import { VideoTile } from '@/components/VideoTile'
 import { SkeletonLoader } from '@/shared/components/SkeletonLoader'
 import { Text } from '@/shared/components/Text'
 import { sizes } from '@/shared/theme'
@@ -24,23 +23,16 @@ export const AllResultsTab: React.FC<AllResultsTabProps> = ({
   onVideoClick,
   onChannelClick,
 }) => {
-  const [bestMatch, ...videos] = allVideos
-
   return (
     <>
-      <div>
-        {loading && <SkeletonLoader width={200} height={16} bottomSpace={18} />}
-        {bestMatch && <h3>Best Match</h3>}
-        <VideoTile id={bestMatch?.id} main onClick={() => onVideoClick(bestMatch?.id)} />
-      </div>
-      {(videos.length > 0 || loading) && (
+      {(allVideos.length > 0 || loading) && (
         <div>
           {loading ? (
             <SkeletonLoader width={200} height={16} bottomSpace={18} />
           ) : (
             <SectionHeader variant="h5">Videos</SectionHeader>
           )}
-          <VideoGallery videos={videos} loading={loading} onVideoClick={onVideoClick} />
+          <VideoGallery videos={allVideos} loading={loading} onVideoClick={onVideoClick} />
         </div>
       )}
       {(channels.length > 0 || loading) && (