From 7465a0e999f04c297bb8c1e21ba545648ca58633 Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Mon, 21 Nov 2022 19:14:20 +0100 Subject: [PATCH] feat: various improvements to banner (#5317) * initial commit * chore: responsive improvements * feat: responsive carousel * chore: fix carousel * chore: less magic, components * chore: bump friction a bit to compensate for carousel changes * fix: position of loading bubbles * chore: fix max-width on fullscreen breakpoints not taking 100 space * fix full screen max-width * chore: remove first card offset --- .../nft/svgs/marketplaces/looksrare-grey.svg | 3 + public/nft/svgs/marketplaces/opensea-grey.svg | 3 + .../nft/svgs/marketplaces/uniswap-magenta.svg | 3 + public/nft/svgs/marketplaces/x2y2-grey.svg | 5 + src/nft/components/explore/Banner.tsx | 18 +-- src/nft/components/explore/Carousel.tsx | 73 +++------ src/nft/components/explore/CarouselCard.tsx | 151 +++++++++--------- src/theme/components/text.tsx | 1 - 8 files changed, 128 insertions(+), 129 deletions(-) create mode 100644 public/nft/svgs/marketplaces/looksrare-grey.svg create mode 100644 public/nft/svgs/marketplaces/opensea-grey.svg create mode 100644 public/nft/svgs/marketplaces/uniswap-magenta.svg create mode 100644 public/nft/svgs/marketplaces/x2y2-grey.svg diff --git a/public/nft/svgs/marketplaces/looksrare-grey.svg b/public/nft/svgs/marketplaces/looksrare-grey.svg new file mode 100644 index 0000000000..c6156eb381 --- /dev/null +++ b/public/nft/svgs/marketplaces/looksrare-grey.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/nft/svgs/marketplaces/opensea-grey.svg b/public/nft/svgs/marketplaces/opensea-grey.svg new file mode 100644 index 0000000000..23d6b1ed4e --- /dev/null +++ b/public/nft/svgs/marketplaces/opensea-grey.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/nft/svgs/marketplaces/uniswap-magenta.svg b/public/nft/svgs/marketplaces/uniswap-magenta.svg new file mode 100644 index 0000000000..277e76f1bb --- /dev/null +++ b/public/nft/svgs/marketplaces/uniswap-magenta.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/nft/svgs/marketplaces/x2y2-grey.svg b/public/nft/svgs/marketplaces/x2y2-grey.svg new file mode 100644 index 0000000000..9edb6ac60b --- /dev/null +++ b/public/nft/svgs/marketplaces/x2y2-grey.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/nft/components/explore/Banner.tsx b/src/nft/components/explore/Banner.tsx index cb1bb243c8..19cf367a74 100644 --- a/src/nft/components/explore/Banner.tsx +++ b/src/nft/components/explore/Banner.tsx @@ -6,7 +6,7 @@ import { calculateCardIndex } from 'nft/utils' import { Suspense, useCallback, useMemo, useState } from 'react' import { useQuery } from 'react-query' import { useNavigate } from 'react-router-dom' -import styled, { css } from 'styled-components/macro' +import styled from 'styled-components/macro' import { opacify } from 'theme/utils' import { Carousel, LoadingCarousel } from './Carousel' @@ -16,14 +16,12 @@ const BannerContainer = styled.div` display: flex; justify-content: center; width: 100%; - padding: 32px 16px 0 16px; + padding-top: 32px; position: relative; -` -// Safari has issues with blur / overflow -// https://stackoverflow.com/a/71353198 -const fixBlurOnSafari = css` - transform: translate3d(0, 0, 0); + @media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { + padding: 32px 16px 0 16px; + } ` const AbsoluteFill = styled.div` @@ -34,13 +32,15 @@ const AbsoluteFill = styled.div` bottom: 0; ` +// Safari has issues with blur / overflow, forcing GPU rendering with `translate3d` fixes it +// https://stackoverflow.com/a/71353198 const BannerBackground = styled(AbsoluteFill)<{ backgroundImage: string }>` - ${fixBlurOnSafari} + transform: translate3d(0, 0, 0) scaleY(1.1); background-image: ${(props) => `url(${props.backgroundImage})`}; filter: blur(62px); + opacity: ${({ theme }) => (theme.darkMode ? 0.3 : 0.2)}; - transform: scaleY(1.1); ` const PlainBackground = styled(AbsoluteFill)` diff --git a/src/nft/components/explore/Carousel.tsx b/src/nft/components/explore/Carousel.tsx index 93159b53e3..0ac75eb9c8 100644 --- a/src/nft/components/explore/Carousel.tsx +++ b/src/nft/components/explore/Carousel.tsx @@ -1,19 +1,8 @@ -import { useWindowSize } from 'hooks/useWindowSize' import { ChevronLeftIcon, ChevronRightIcon } from 'nft/components/icons' import { calculateCardIndex, calculateFirstCardIndex, calculateRank } from 'nft/utils' -import { ReactNode, useCallback, useEffect, useRef, useState } from 'react' +import { ReactNode, useCallback, useEffect, useRef } from 'react' import { a, useSprings } from 'react-spring' -import styled, { css } from 'styled-components/macro' - -const MAX_CARD_WIDTH = 530 - -const carouselHeightStyle = css` - height: 296px; - - @media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { - height: 316px; - } -` +import styled from 'styled-components/macro' const CarouselContainer = styled.div` display: flex; @@ -22,38 +11,44 @@ const CarouselContainer = styled.div` ` const CarouselCardContainer = styled.div` - ${carouselHeightStyle} - position: relative; width: 100%; - max-width: ${MAX_CARD_WIDTH}px; overflow-x: hidden; + max-width: 100%; + height: 390px; + + @media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + max-width: 600px; + } ` const CarouselItemCard = styled(a.div)` - ${carouselHeightStyle} - display: flex; justify-content: center; padding: 4px 12px 32px; position: absolute; will-change: transform; + width: calc(100%); + height: calc(100%); - @media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) { + @media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) { padding: 4px 32px 32px; } ` const CarouselItemIcon = styled.div` - ${carouselHeightStyle} align-items: center; - color: ${({ theme }) => theme.textPrimary}; + color: ${({ theme }) => theme.accentAction}; cursor: pointer; display: none; user-select: none; + height: calc(100%); + padding: 4px 0 32px; + @media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { display: flex; } + :hover { opacity: ${({ theme }) => theme.opacity.hover}; } @@ -65,53 +60,43 @@ interface CarouselProps { toggleNextSlide: (idx: number) => void } -const FIRST_CARD_OFFSET = 0 +const MAX_CARD_WIDTH = 800 export const Carousel = ({ children, activeIndex, toggleNextSlide }: CarouselProps) => { - const { width } = useWindowSize() - const carouselCardContainerRef = useRef(null) - const [cardWidth, setCardWidth] = useState(MAX_CARD_WIDTH) - - useEffect(() => { - if (carouselCardContainerRef.current) { - setCardWidth(Math.min(carouselCardContainerRef.current.offsetWidth, MAX_CARD_WIDTH)) - } - }, [width]) - const idx = useCallback((x: number, l = children.length) => calculateCardIndex(x, l), [children]) const getPos = useCallback( (i: number, firstVis: number, firstVisIdx: number) => calculateFirstCardIndex(i, firstVis, firstVisIdx, idx), [idx] ) const [springs, set] = useSprings(children.length, (i) => ({ - x: (i < children.length - 1 ? i : -1) * cardWidth + FIRST_CARD_OFFSET, + x: (i < children.length - 1 ? i : -1) * MAX_CARD_WIDTH, })) const prev = useRef([0, 1]) const runSprings = useCallback( (y: number, vy: number) => { - const firstVis = idx(Math.floor(y / cardWidth) % children.length) + const firstVis = idx(Math.floor(y / MAX_CARD_WIDTH) % children.length) const firstVisIdx = vy < 0 ? children.length - 2 : 1 set((i) => { const position = getPos(i, firstVis, firstVisIdx) const prevPosition = getPos(i, prev.current[0], prev.current[1]) const rank = calculateRank(firstVis, firstVisIdx, position, children.length, y) return { - x: (-y % (cardWidth * children.length)) + cardWidth * rank + FIRST_CARD_OFFSET, + x: (-y % (MAX_CARD_WIDTH * children.length)) + MAX_CARD_WIDTH * rank, immediate: vy < 0 ? prevPosition > position : prevPosition < position, - config: { tension: 250, friction: 30 }, + config: { tension: 250, friction: 35 }, } }) prev.current = [firstVis, firstVisIdx] }, - [idx, getPos, set, cardWidth, children.length] + [idx, getPos, set, children.length] ) const direction = useRef(0) useEffect(() => { - runSprings(activeIndex * cardWidth, direction.current) - }, [activeIndex, cardWidth, runSprings]) + runSprings(activeIndex * MAX_CARD_WIDTH, direction.current) + }, [activeIndex, runSprings]) const toggleSlide = useCallback( (next: -1 | 1) => { @@ -135,15 +120,9 @@ export const Carousel = ({ children, activeIndex, toggleNextSlide }: CarouselPro toggleSlide(-1)}> - + {springs.map(({ x }, i) => ( - + {children[i]} ))} diff --git a/src/nft/components/explore/CarouselCard.tsx b/src/nft/components/explore/CarouselCard.tsx index e1418fa7da..3f3a898d13 100644 --- a/src/nft/components/explore/CarouselCard.tsx +++ b/src/nft/components/explore/CarouselCard.tsx @@ -6,7 +6,7 @@ import { VerifiedIcon } from 'nft/components/icons' import { Markets, TrendingCollection } from 'nft/types' import { formatWeiToDecimal } from 'nft/utils' import styled, { useTheme } from 'styled-components/macro' -import { ThemedText } from 'theme' +import { ThemedText } from 'theme/components/text' const CarouselCardContainer = styled.div` display: flex; @@ -14,13 +14,10 @@ const CarouselCardContainer = styled.div` background-color: ${({ theme }) => theme.backgroundSurface}; border: 1px solid ${({ theme }) => theme.backgroundOutline}; border-radius: 20px; - gap: 8px; overflow: hidden; height: 100%; - @media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) { - gap: 20px; - } ` + const CarouselCardBorder = styled.div` width: 100%; position: relative; @@ -58,12 +55,6 @@ const CarouselCardBorder = styled.div` const CardHeaderContainer = styled.div<{ src: string }>` position: relative; - width: 100%; - height: 108px; - padding-top: 32px; - padding-bottom: 16px; - padding-left: 28px; - padding-right: 28px; background-image: ${({ src }) => `url(${src})`}; background-size: cover; background-position: center; @@ -71,12 +62,6 @@ const CardHeaderContainer = styled.div<{ src: string }>` const LoadingCardHeaderContainer = styled.div` position: relative; - width: 100%; - height: 108px; - padding-top: 32px; - padding-bottom: 16px; - padding-left: 28px; - padding-right: 28px; animation: ${loadingAnimation} 1.5s infinite; animation-fill-mode: both; background: linear-gradient( @@ -89,15 +74,15 @@ const LoadingCardHeaderContainer = styled.div` background-size: 400%; ` -const CardHeaderRow = styled.div` +const CardHeaderColumn = styled.div` position: relative; - z-index: 1; display: flex; - gap: 8px; + flex: 1; align-items: center; - @media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) { - gap: 12px; - } + flex-direction: column; + gap: 8px; + padding: 40px; + z-index: 1; ` const CardNameRow = styled.div` @@ -126,7 +111,7 @@ const LoadingCollectionNameContainer = styled(LoadingBubble)` const HeaderOverlay = styled.div` position: absolute; - height: 108px; + bottom: 0px; top: 0px; right: 0px; left: 0px; @@ -135,16 +120,16 @@ const HeaderOverlay = styled.div` ` const CollectionImage = styled.img` - width: 60px; - height: 60px; + width: 86px; + height: 86px; background: ${({ theme }) => theme.accentTextLightPrimary}; border: 2px solid ${({ theme }) => theme.accentTextLightPrimary}; border-radius: 100px; ` const LoadingCollectionImage = styled.div` - width: 60px; - height: 60px; + width: 86px; + height: 86px; border-radius: 100px; animation: ${loadingAnimation} 1.5s infinite; animation-fill-mode: both; @@ -158,44 +143,42 @@ const LoadingCollectionImage = styled.div` background-size: 400%; ` -const CardBottomContainer = styled.div` - display: grid; - grid-template-columns: auto auto auto; - row-gap: 8px; - column-gap: 20px; - padding-right: 28px; - padding-left: 28px; - padding-bottom: 20px; - justify-content: space-between; - - @media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.lg}px`}) { - row-gap: 16px; - } -` - -const HeaderRow = styled.div` - color: ${({ theme }) => theme.userThemeColor}; - font-size: 14px; - font-weight: 500; - line-height: 20px; - row-gap: 8px; - - @media only screen and (min-width: ${({ theme }) => `${theme.breakpoint.lg}px`}) { - font-size: 16px; - line-height: 24px; - row-gap: 12px; - } -` - const LoadingTableElement = styled(LoadingBubble)` width: 50px; ` const TableElement = styled.div` - color: ${({ theme }) => theme.textSecondary}; - font-size: 14px; - font-weight: 400; - line-height: 20px; + display: flex; + align-items: center; + gap: 6px; +` + +const FirstColumnTextWrapper = styled.div` + @media (min-width: ${({ theme }) => theme.breakpoint.sm}px) and (max-width: ${({ theme }) => theme.breakpoint.lg}px) { + display: none; + } +} +` + +const CardBottomContainer = styled.div` + display: grid; + flex: 1; + gap: 8px; + grid-template-columns: auto auto auto; + padding: 16px 16px 20px; + + ${TableElement}:nth-child(3n-1), ${LoadingTableElement}:nth-child(3n-1) { + justify-self: center; + } + + ${TableElement}:nth-child(3n), ${LoadingTableElement}:nth-child(3n) { + justify-self: right; + } +` + +const MarketplaceIcon = styled.img` + width: 20px; + height: 20px; ` interface MarketplaceRowProps { @@ -207,12 +190,23 @@ interface MarketplaceRowProps { export const MarketplaceRow = ({ marketplace, floorInEth, listings }: MarketplaceRowProps) => { return ( <> - {marketplace} - {floorInEth !== undefined ? formatNumberOrString(floorInEth, NumberType.NFTTokenFloorPriceTrailingZeros) : '-'}{' '} - ETH + + + {marketplace} + + + + + {floorInEth !== undefined + ? formatNumberOrString(floorInEth, NumberType.NFTTokenFloorPriceTrailingZeros) + : '-'}{' '} + ETH + + + + {listings ?? '-'} - {listings ?? '-'} ) } @@ -238,9 +232,22 @@ export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => { <> - Uniswap - {formatWeiToDecimal(collection.floor.toString())} ETH Floor - {gqlCollection.marketplaceCount?.reduce((acc, cur) => acc + cur.count, 0)} Listings + + + + Uniswap + + + + + {formatWeiToDecimal(collection.floor.toString())} ETH Floor + + + + + {gqlCollection.marketplaceCount?.reduce((acc, cur) => acc + cur.count, 0)} Listings + + {MARKETS_TO_CHECK.map((market) => { const marketplace = gqlCollection.marketplaceCount?.find( (marketplace) => marketplace.marketplace === market @@ -280,7 +287,7 @@ const CarouselCardHeader = ({ collection }: { collection: TrendingCollection }) const theme = useTheme() return ( - + @@ -294,7 +301,7 @@ const CarouselCardHeader = ({ collection }: { collection: TrendingCollection }) )} - + ) @@ -308,10 +315,10 @@ export const LoadingCarouselCard = ({ collection }: { collection?: TrendingColle ) : ( - + - + )} diff --git a/src/theme/components/text.tsx b/src/theme/components/text.tsx index 886c61dac6..a1db8a329d 100644 --- a/src/theme/components/text.tsx +++ b/src/theme/components/text.tsx @@ -12,7 +12,6 @@ const TextWrapper = styled(Text)<{ color: keyof string }>` type TextProps = Omit // todo: export each component individually - export const ThemedText = { // todo: there should be just one `Body` with default color, no need to make all variations BodyPrimary(props: TextProps) {