Commit 53f4fb9e authored by aballerr's avatar aballerr Committed by GitHub

chore: Merging Loading states 2 (#4708)

* adding in remaining loading styles
Co-authored-by: default avatarAlex Ball <alexball@UNISWAP-MAC-038.local>
parent bb1ccb7f
import { style } from '@vanilla-extract/css' import { style } from '@vanilla-extract/css'
import { buttonTextMedium } from 'nft/css/common.css' import { buttonTextMedium } from 'nft/css/common.css'
import { loadingAsset } from 'nft/css/loading.css'
import { sprinkles, vars } from 'nft/css/sprinkles.css' import { sprinkles, vars } from 'nft/css/sprinkles.css'
export const baseActivitySwitcherToggle = style([ export const baseActivitySwitcherToggle = style([
...@@ -40,3 +41,11 @@ export const selectedActivitySwitcherToggle = style([ ...@@ -40,3 +41,11 @@ export const selectedActivitySwitcherToggle = style([
}, },
}, },
]) ])
export const styledLoading = style([
loadingAsset,
{
width: 58,
height: 20,
},
])
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { useIsCollectionLoading } from 'nft/hooks'
import * as styles from './ActivitySwitcher.css' import * as styles from './ActivitySwitcher.css'
...@@ -10,22 +11,31 @@ export const ActivitySwitcher = ({ ...@@ -10,22 +11,31 @@ export const ActivitySwitcher = ({
showActivity: boolean showActivity: boolean
toggleActivity: () => void toggleActivity: () => void
}) => { }) => {
const isLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
const loadingVals = new Array(2).fill(<div className={styles.styledLoading} />)
return ( return (
<Row gap="24" marginBottom="28"> <Row gap="24" marginBottom="28">
<Box {isLoading ? (
as="button" loadingVals
className={showActivity ? styles.activitySwitcherToggle : styles.selectedActivitySwitcherToggle} ) : (
onClick={() => showActivity && toggleActivity()} <>
> <Box
Items as="button"
</Box> className={showActivity ? styles.activitySwitcherToggle : styles.selectedActivitySwitcherToggle}
<Box onClick={() => showActivity && toggleActivity()}
as="button" >
className={!showActivity ? styles.activitySwitcherToggle : styles.selectedActivitySwitcherToggle} Items
onClick={() => !showActivity && toggleActivity()} </Box>
> <Box
Activity as="button"
</Box> className={!showActivity ? styles.activitySwitcherToggle : styles.selectedActivitySwitcherToggle}
onClick={() => !showActivity && toggleActivity()}
>
Activity
</Box>
</>
)}
</Row> </Row>
) )
} }
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
useFiltersExpanded, useFiltersExpanded,
useIsMobile, useIsMobile,
} from 'nft/hooks' } from 'nft/hooks'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { AssetsFetcher } from 'nft/queries' import { AssetsFetcher } from 'nft/queries'
import { DropDownOption, GenieCollection, UniformHeight, UniformHeights } from 'nft/types' import { DropDownOption, GenieCollection, UniformHeight, UniformHeights } from 'nft/types'
import { getRarityStatus } from 'nft/utils/asset' import { getRarityStatus } from 'nft/utils/asset'
...@@ -46,6 +47,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -46,6 +47,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const setMarketCount = useCollectionFilters((state) => state.setMarketCount) const setMarketCount = useCollectionFilters((state) => state.setMarketCount)
const setSortBy = useCollectionFilters((state) => state.setSortBy) const setSortBy = useCollectionFilters((state) => state.setSortBy)
const buyNow = useCollectionFilters((state) => state.buyNow) const buyNow = useCollectionFilters((state) => state.buyNow)
const setIsCollectionNftsLoading = useIsCollectionLoading((state) => state.setIsCollectionNftsLoading)
const debouncedMinPrice = useDebounce(minPrice, 500) const debouncedMinPrice = useDebounce(minPrice, 500)
const debouncedMaxPrice = useDebounce(maxPrice, 500) const debouncedMaxPrice = useDebounce(maxPrice, 500)
...@@ -115,6 +117,10 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -115,6 +117,10 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
} }
) )
useEffect(() => {
setIsCollectionNftsLoading(isLoading)
}, [isLoading, setIsCollectionNftsLoading])
const [uniformHeight, setUniformHeight] = useState<UniformHeight>(UniformHeights.unset) const [uniformHeight, setUniformHeight] = useState<UniformHeight>(UniformHeights.unset)
const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>() const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>()
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded() const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
......
import { style } from '@vanilla-extract/css'
import { loadingAsset } from 'nft/css/loading.css'
import { sprinkles } from 'nft/css/sprinkles.css'
export const filterButtonLoading = style([
loadingAsset,
sprinkles({
border: 'none',
}),
])
import clsx from 'clsx'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import * as styles from 'nft/components/collection/CollectionSearch.css'
import { useIsCollectionLoading } from 'nft/hooks'
import { useCollectionFilters } from 'nft/hooks/useCollectionFilters' import { useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { FormEvent } from 'react' import { FormEvent } from 'react'
export const CollectionSearch = () => { export const CollectionSearch = () => {
const setSearchByNameText = useCollectionFilters((state) => state.setSearch) const setSearchByNameText = useCollectionFilters((state) => state.setSearch)
const searchByNameText = useCollectionFilters((state) => state.search) const searchByNameText = useCollectionFilters((state) => state.search)
const iscollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
return ( return (
<Box <Box
...@@ -19,7 +23,8 @@ export const CollectionSearch = () => { ...@@ -19,7 +23,8 @@ export const CollectionSearch = () => {
height="44" height="44"
color={{ placeholder: 'textSecondary', default: 'textPrimary' }} color={{ placeholder: 'textSecondary', default: 'textPrimary' }}
value={searchByNameText} value={searchByNameText}
placeholder={'Search by name'} placeholder={iscollectionStatsLoading ? '' : 'Search by name'}
className={clsx(iscollectionStatsLoading && styles.filterButtonLoading)}
onChange={(e: FormEvent<HTMLInputElement>) => { onChange={(e: FormEvent<HTMLInputElement>) => {
setSearchByNameText(e.currentTarget.value) setSearchByNameText(e.currentTarget.value)
}} }}
......
import { style } from '@vanilla-extract/css' import { style } from '@vanilla-extract/css'
import { body, bodySmall } from 'nft/css/common.css' import { body, bodySmall } from 'nft/css/common.css'
import { loadingAsset, loadingBlock } from 'nft/css/loading.css'
import { breakpoints, sprinkles } from '../../css/sprinkles.css' import { breakpoints, sprinkles } from '../../css/sprinkles.css'
...@@ -113,3 +114,54 @@ export const statsValue = style([ ...@@ -113,3 +114,54 @@ export const statsValue = style([
lineHeight: '24px', lineHeight: '24px',
}, },
]) ])
export const statsValueLoading = style([
loadingAsset,
sprinkles({
width: '60',
height: '20',
marginTop: '8',
}),
])
export const statsLabelLoading = style([
loadingAsset,
sprinkles({
width: '60',
height: '16',
}),
])
export const descriptionLoading = style([
loadingAsset,
{
maxWidth: 'min(calc(100% - 112px), 600px)',
},
])
export const collectionImageIsLoadingBackground = style([
collectionImage,
sprinkles({
backgroundColor: 'backgroundSurface',
}),
])
export const collectionImageIsLoading = style([
loadingBlock,
collectionImage,
sprinkles({
borderStyle: 'solid',
borderWidth: '4px',
borderColor: 'backgroundSurface',
}),
])
export const nameTextLoading = style([
loadingAsset,
sprinkles({
height: '32',
}),
{
width: 236,
},
])
...@@ -4,6 +4,7 @@ import { Column, Row } from 'nft/components/Flex' ...@@ -4,6 +4,7 @@ import { Column, Row } from 'nft/components/Flex'
import { Marquee } from 'nft/components/layout/Marquee' import { Marquee } from 'nft/components/layout/Marquee'
import { headlineMedium } from 'nft/css/common.css' import { headlineMedium } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { GenieCollection } from 'nft/types' import { GenieCollection } from 'nft/types'
import { ethNumberStandardFormatter } from 'nft/utils/currency' import { ethNumberStandardFormatter } from 'nft/utils/currency'
import { putCommas } from 'nft/utils/putCommas' import { putCommas } from 'nft/utils/putCommas'
...@@ -124,14 +125,13 @@ const CollectionName = ({ ...@@ -124,14 +125,13 @@ const CollectionName = ({
collectionSocialsIsOpen: boolean collectionSocialsIsOpen: boolean
toggleCollectionSocials: () => void toggleCollectionSocials: () => void
}) => { }) => {
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
const nameClass = isCollectionStatsLoading ? styles.nameTextLoading : clsx(headlineMedium, styles.nameText)
return ( return (
<Row justifyContent="space-between"> <Row justifyContent="space-between">
<Row minWidth="0"> <Row minWidth="0">
<Box <Box marginRight={!isVerified ? '12' : '0'} className={nameClass}>
marginRight={!isVerified ? '12' : '0'}
className={clsx(isMobile ? headlineMedium : headlineMedium, styles.nameText)}
style={{ lineHeight: '32px' }}
>
{name} {name}
</Box> </Box>
{isVerified && <VerifiedIcon style={{ width: '32px', height: '32px' }} />} {isVerified && <VerifiedIcon style={{ width: '32px', height: '32px' }} />}
...@@ -144,7 +144,7 @@ const CollectionName = ({ ...@@ -144,7 +144,7 @@ const CollectionName = ({
height="32" height="32"
> >
{collectionStats.discordUrl ? ( {collectionStats.discordUrl ? (
<SocialsIcon href={collectionStats.discordUrl}> <SocialsIcon href={collectionStats.discordUrl ?? ''}>
<DiscordIcon <DiscordIcon
fill={themeVars.colors.textSecondary} fill={themeVars.colors.textSecondary}
color={themeVars.colors.textSecondary} color={themeVars.colors.textSecondary}
...@@ -170,7 +170,7 @@ const CollectionName = ({ ...@@ -170,7 +170,7 @@ const CollectionName = ({
</SocialsIcon> </SocialsIcon>
) : null} ) : null}
{collectionStats.externalUrl ? ( {collectionStats.externalUrl ? (
<SocialsIcon href={collectionStats.externalUrl}> <SocialsIcon href={collectionStats.externalUrl ?? ''}>
<ExternalIcon fill={themeVars.colors.textSecondary} width="26px" height="26px" /> <ExternalIcon fill={themeVars.colors.textSecondary} width="26px" height="26px" />
</SocialsIcon> </SocialsIcon>
) : null} ) : null}
...@@ -196,6 +196,7 @@ const CollectionDescription = ({ description }: { description: string }) => { ...@@ -196,6 +196,7 @@ const CollectionDescription = ({ description }: { description: string }) => {
const [readMore, toggleReadMore] = useReducer((state) => !state, false) const [readMore, toggleReadMore] = useReducer((state) => !state, false)
const baseRef = useRef<HTMLDivElement>(null) const baseRef = useRef<HTMLDivElement>(null)
const descriptionRef = useRef<HTMLDivElement>(null) const descriptionRef = useRef<HTMLDivElement>(null)
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
useEffect(() => { useEffect(() => {
if ( if (
...@@ -209,7 +210,9 @@ const CollectionDescription = ({ description }: { description: string }) => { ...@@ -209,7 +210,9 @@ const CollectionDescription = ({ description }: { description: string }) => {
setShowReadMore(true) setShowReadMore(true)
}, [descriptionRef, baseRef]) }, [descriptionRef, baseRef])
return ( return isCollectionStatsLoading ? (
<Box marginTop={{ sm: '12', md: '16' }} className={styles.descriptionLoading}></Box>
) : (
<Box ref={baseRef} marginTop={{ sm: '12', md: '16' }} style={{ maxWidth: '680px' }}> <Box ref={baseRef} marginTop={{ sm: '12', md: '16' }} style={{ maxWidth: '680px' }}>
<Box <Box
ref={descriptionRef} ref={descriptionRef}
...@@ -228,29 +231,44 @@ const CollectionDescription = ({ description }: { description: string }) => { ...@@ -228,29 +231,44 @@ const CollectionDescription = ({ description }: { description: string }) => {
) )
} }
const StatsItem = ({ children, label, isMobile }: { children: ReactNode; label: string; isMobile: boolean }) => ( const StatsItem = ({ children, label, isMobile }: { children: ReactNode; label: string; isMobile: boolean }) => {
<Box display="flex" flexDirection={isMobile ? 'row' : 'column'} alignItems="baseline" gap="2" height="min"> return (
<Box as="span" className={styles.statsLabel}> <Box display="flex" flexDirection={isMobile ? 'row' : 'column'} alignItems="baseline" gap="2" height="min">
{`${label}${isMobile ? ': ' : ''}`} <Box as="span" className={styles.statsLabel}>
{`${label}${isMobile ? ': ' : ''}`}
</Box>
<span className={styles.statsValue}>{children}</span>
</Box> </Box>
<span className={styles.statsValue}>{children}</span> )
</Box> }
)
const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMobile?: boolean } & BoxProps) => { const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMobile?: boolean } & BoxProps) => {
const numOwnersStr = stats.stats ? putCommas(stats.stats.num_owners) : 0 const numOwnersStr = stats.stats ? putCommas(stats.stats.num_owners) : 0
const totalSupplyStr = stats.stats ? putCommas(stats.stats.total_supply) : 0 const totalSupplyStr = stats.stats ? putCommas(stats.stats.total_supply) : 0
const totalListingsStr = stats.stats ? putCommas(stats.stats.total_listings) : 0 const totalListingsStr = stats.stats ? putCommas(stats.stats.total_listings) : 0
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
// round daily volume & floorPrice to 3 decimals or less // round daily volume & floorPrice to 3 decimals or less
const totalVolumeStr = ethNumberStandardFormatter(stats.stats?.total_volume) const totalVolumeStr = ethNumberStandardFormatter(stats.stats?.total_volume)
const floorPriceStr = ethNumberStandardFormatter(stats.floorPrice) const floorPriceStr = ethNumberStandardFormatter(stats.floorPrice)
const statsLoadingSkeleton = new Array(5).fill(
<>
<Box display="flex" flexDirection={isMobile ? 'row' : 'column'} alignItems="baseline" gap="2" height="min">
<div className={styles.statsLabelLoading} />
<span className={styles.statsValueLoading} />
</Box>
</>
)
return ( return (
<Row gap={{ sm: '20', md: '60' }} {...props}> <Row gap={{ sm: '20', md: '60' }} {...props}>
<StatsItem label="Items" isMobile={isMobile ?? false}> {isCollectionStatsLoading && statsLoadingSkeleton}
{totalSupplyStr} {totalSupplyStr ? (
</StatsItem> <StatsItem label="Items" isMobile={isMobile ?? false}>
{totalSupplyStr}
</StatsItem>
) : null}
{numOwnersStr ? ( {numOwnersStr ? (
<StatsItem label="Owners" isMobile={isMobile ?? false}> <StatsItem label="Owners" isMobile={isMobile ?? false}>
{numOwnersStr} {numOwnersStr}
...@@ -277,10 +295,7 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob ...@@ -277,10 +295,7 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; isMobile: boolean }) => { export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; isMobile: boolean }) => {
const [collectionSocialsIsOpen, toggleCollectionSocials] = useReducer((state) => !state, false) const [collectionSocialsIsOpen, toggleCollectionSocials] = useReducer((state) => !state, false)
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
if (!stats) {
return <div>Loading CollectionStats...</div>
}
return ( return (
<Box <Box
...@@ -291,11 +306,14 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i ...@@ -291,11 +306,14 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i
flexDirection="column" flexDirection="column"
width="full" width="full"
> >
{isCollectionStatsLoading && (
<Box as="div" borderRadius="round" position="absolute" className={styles.collectionImageIsLoadingBackground} />
)}
<Box <Box
as="img" as={isCollectionStatsLoading ? 'div' : 'img'}
borderRadius="round" borderRadius="round"
position="absolute" position="absolute"
className={styles.collectionImage} className={isCollectionStatsLoading ? styles.collectionImageIsLoading : styles.collectionImage}
src={stats.isFoundation && !stats.imageUrl ? '/nft/svgs/marketplaces/foundation.svg' : stats.imageUrl} src={stats.isFoundation && !stats.imageUrl ? '/nft/svgs/marketplaces/foundation.svg' : stats.imageUrl}
/> />
<Box className={styles.statsText}> <Box className={styles.statsText}>
...@@ -309,7 +327,9 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i ...@@ -309,7 +327,9 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i
/> />
{!isMobile && ( {!isMobile && (
<> <>
{stats.description && <CollectionDescription description={stats.description} />} {(stats.description || isCollectionStatsLoading) && (
<CollectionDescription description={stats.description} />
)}
<StatsRow stats={stats} marginTop="20" /> <StatsRow stats={stats} marginTop="20" />
</> </>
)} )}
......
import { style } from '@vanilla-extract/css' import { style } from '@vanilla-extract/css'
import { loadingAsset } from 'nft/css/loading.css'
import { sprinkles, themeVars, vars } from 'nft/css/sprinkles.css' import { sprinkles, themeVars, vars } from 'nft/css/sprinkles.css'
export const filterButton = sprinkles({ export const filterButton = sprinkles({
...@@ -21,3 +22,11 @@ export const filterBadge = style([ ...@@ -21,3 +22,11 @@ export const filterBadge = style([
top: '-3px', top: '-3px',
}, },
]) ])
export const filterButtonLoading = style([
loadingAsset,
sprinkles({
height: '44',
width: '100',
}),
])
...@@ -3,7 +3,7 @@ import { Box } from 'nft/components/Box' ...@@ -3,7 +3,7 @@ import { Box } from 'nft/components/Box'
import * as styles from 'nft/components/collection/FilterButton.css' import * as styles from 'nft/components/collection/FilterButton.css'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { FilterIcon } from 'nft/components/icons' import { FilterIcon } from 'nft/components/icons'
import { useCollectionFilters, useWalletCollections } from 'nft/hooks' import { useCollectionFilters, useIsCollectionLoading, useWalletCollections } from 'nft/hooks'
import { putCommas } from 'nft/utils/putCommas' import { putCommas } from 'nft/utils/putCommas'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
...@@ -32,13 +32,18 @@ export const FilterButton = ({ ...@@ -32,13 +32,18 @@ export const FilterButton = ({
const collectionFilters = useWalletCollections((state) => state.collectionFilters) const collectionFilters = useWalletCollections((state) => state.collectionFilters)
const { pathname } = useLocation() const { pathname } = useLocation()
const isSellPage = pathname.startsWith('/nfts/sell') const isSellPage = pathname.startsWith('/nfts/sell')
const isCollectionNftsLoading = useIsCollectionLoading((state) => state.isCollectionNftsLoading)
const showFilterBadge = isSellPage const showFilterBadge = isSellPage
? collectionFilters.length > 0 ? collectionFilters.length > 0
: minPrice || maxPrice || minRarity || maxRarity || traits.length || markets.length || buyNow : minPrice || maxPrice || minRarity || maxRarity || traits.length || markets.length || buyNow
return ( return (
<Box <Box
className={clsx(styles.filterButton, !isFiltersExpanded && styles.filterButtonExpanded)} className={
isCollectionNftsLoading
? styles.filterButtonLoading
: clsx(styles.filterButton, !isFiltersExpanded && styles.filterButtonExpanded)
}
borderRadius="12" borderRadius="12"
fontSize="16" fontSize="16"
cursor="pointer" cursor="pointer"
...@@ -52,14 +57,20 @@ export const FilterButton = ({ ...@@ -52,14 +57,20 @@ export const FilterButton = ({
height="44" height="44"
whiteSpace="nowrap" whiteSpace="nowrap"
> >
{showFilterBadge && ( {!isCollectionNftsLoading && (
<Row className={styles.filterBadge} color={isFiltersExpanded ? 'grey700' : 'blue400'}> <>
{showFilterBadge && (
</Row> <Row className={styles.filterBadge} color={isFiltersExpanded ? 'grey700' : 'blue400'}>
</Row>
)}
<FilterIcon
style={{ marginBottom: '-4px', paddingRight: `${!isFiltersExpanded || showFilterBadge ? '6px' : '0px'}` }}
/>
</>
)} )}
<FilterIcon
style={{ marginBottom: '-4px', paddingRight: `${!isFiltersExpanded || showFilterBadge ? '6px' : '0px'}` }}
/>
{!isMobile && !isFiltersExpanded && 'Filter'} {!isMobile && !isFiltersExpanded && 'Filter'}
{showFilterBadge && !isMobile ? ( {showFilterBadge && !isMobile ? (
......
import { style } from '@vanilla-extract/css' import { style } from '@vanilla-extract/css'
import { loadingAsset } from 'nft/css/loading.css'
import { sprinkles } from 'nft/css/sprinkles.css'
export const activeDropdown = style({ export const activeDropdown = style({
borderBottom: 'none', borderBottom: 'none',
...@@ -7,3 +9,13 @@ export const activeDropdown = style({ ...@@ -7,3 +9,13 @@ export const activeDropdown = style({
export const activeDropDownItems = style({ export const activeDropDownItems = style({
borderTop: 'none', borderTop: 'none',
}) })
export const isLoadingDropdown = style([
loadingAsset,
sprinkles({
height: '44',
}),
{
width: 220,
},
])
...@@ -5,6 +5,7 @@ import { Row } from 'nft/components/Flex' ...@@ -5,6 +5,7 @@ import { Row } from 'nft/components/Flex'
import { ArrowsIcon, ChevronUpIcon, ReversedArrowsIcon } from 'nft/components/icons' import { ArrowsIcon, ChevronUpIcon, ReversedArrowsIcon } from 'nft/components/icons'
import { buttonTextMedium } from 'nft/css/common.css' import { buttonTextMedium } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useIsCollectionLoading } from 'nft/hooks'
import { DropDownOption } from 'nft/types' import { DropDownOption } from 'nft/types'
import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react' import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react'
...@@ -28,6 +29,7 @@ export const SortDropdown = ({ ...@@ -28,6 +29,7 @@ export const SortDropdown = ({
const [isOpen, toggleOpen] = useReducer((s) => !s, false) const [isOpen, toggleOpen] = useReducer((s) => !s, false)
const [isReversed, toggleReversed] = useReducer((s) => !s, false) const [isReversed, toggleReversed] = useReducer((s) => !s, false)
const [selectedIndex, setSelectedIndex] = useState(0) const [selectedIndex, setSelectedIndex] = useState(0)
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
const [maxWidth, setMaxWidth] = useState(0) const [maxWidth, setMaxWidth] = useState(0)
...@@ -41,6 +43,8 @@ export const SortDropdown = ({ ...@@ -41,6 +43,8 @@ export const SortDropdown = ({
[selectedIndex, dropDownOptions] [selectedIndex, dropDownOptions]
) )
const width = isCollectionStatsLoading ? 220 : inFilters ? 'full' : mini ? 'min' : maxWidth ? maxWidth : '300px'
return ( return (
<Box <Box
ref={ref} ref={ref}
...@@ -49,7 +53,7 @@ export const SortDropdown = ({ ...@@ -49,7 +53,7 @@ export const SortDropdown = ({
borderBottomLeftRadius={isOpen ? '0' : undefined} borderBottomLeftRadius={isOpen ? '0' : undefined}
borderBottomRightRadius={isOpen ? '0' : undefined} borderBottomRightRadius={isOpen ? '0' : undefined}
height="44" height="44"
style={{ width: inFilters ? 'full' : mini ? 'min' : maxWidth ? maxWidth : '300px' }} style={{ width }}
> >
<Box <Box
as="button" as="button"
...@@ -70,51 +74,56 @@ export const SortDropdown = ({ ...@@ -70,51 +74,56 @@ export const SortDropdown = ({
width={inFilters ? 'full' : 'inherit'} width={inFilters ? 'full' : 'inherit'}
onClick={toggleOpen} onClick={toggleOpen}
cursor="pointer" cursor="pointer"
className={clsx(isOpen && !mini && styles.activeDropdown)} className={isCollectionStatsLoading ? styles.isLoadingDropdown : clsx(isOpen && !mini && styles.activeDropdown)}
> >
<Box display="flex" alignItems="center"> {!isCollectionStatsLoading && (
{!isOpen && reversable && ( <>
<Row <Box display="flex" alignItems="center">
onClick={(e) => { {!isOpen && reversable && (
e.stopPropagation() <Row
onClick={(e) => {
e.stopPropagation()
if (dropDownOptions[selectedIndex].reverseOnClick) { if (dropDownOptions[selectedIndex].reverseOnClick) {
dropDownOptions[selectedIndex].reverseOnClick?.() dropDownOptions[selectedIndex].reverseOnClick?.()
toggleReversed() toggleReversed()
} else { } else {
const dropdownIndex = dropDownOptions[selectedIndex].reverseIndex ?? 1 const dropdownIndex = dropDownOptions[selectedIndex].reverseIndex ?? 1
dropDownOptions[dropdownIndex - 1].onClick() dropDownOptions[dropdownIndex - 1].onClick()
setSelectedIndex(dropdownIndex - 1) setSelectedIndex(dropdownIndex - 1)
} }
}} }}
> >
{dropDownOptions[selectedIndex].reverseOnClick && (isReversed ? <ArrowsIcon /> : <ReversedArrowsIcon />)} {dropDownOptions[selectedIndex].reverseOnClick &&
{dropDownOptions[selectedIndex].reverseIndex && (isReversed ? <ArrowsIcon /> : <ReversedArrowsIcon />)}
(selectedIndex > (dropDownOptions[selectedIndex].reverseIndex ?? 1) - 1 ? ( {dropDownOptions[selectedIndex].reverseIndex &&
<ArrowsIcon /> (selectedIndex > (dropDownOptions[selectedIndex].reverseIndex ?? 1) - 1 ? (
) : ( <ArrowsIcon />
<ReversedArrowsIcon /> ) : (
))} <ReversedArrowsIcon />
</Row> ))}
)} </Row>
<Box )}
marginLeft={reversable ? '4' : '0'}
marginRight={mini ? '2' : '0'}
color="textPrimary"
className={buttonTextMedium}
>
{mini ? miniPrompt : isOpen ? 'Sort by' : dropDownOptions[selectedIndex].displayText}
</Box>
</Box>
<ChevronUpIcon <Box
secondaryColor={mini ? themeVars.colors.textPrimary : undefined} marginLeft={reversable ? '4' : '0'}
secondaryWidth={mini ? '20' : undefined} marginRight={mini ? '2' : '0'}
secondaryHeight={mini ? '20' : undefined} color="textPrimary"
style={{ className={buttonTextMedium}
transform: isOpen ? '' : 'rotate(180deg)', >
}} {mini ? miniPrompt : isOpen ? 'Sort by' : dropDownOptions[selectedIndex].displayText}
/> </Box>
</Box>
<ChevronUpIcon
secondaryColor={mini ? themeVars.colors.textPrimary : undefined}
secondaryWidth={mini ? '20' : undefined}
secondaryHeight={mini ? '20' : undefined}
style={{
transform: isOpen ? '' : 'rotate(180deg)',
}}
/>
</>
)}
</Box> </Box>
<Box <Box
position="absolute" position="absolute"
......
export * from './useBag' export * from './useBag'
export * from './useCollectionFilters' export * from './useCollectionFilters'
export * from './useFiltersExpanded' export * from './useFiltersExpanded'
export * from './useIsCollectionLoading'
export * from './useIsMobile' export * from './useIsMobile'
export * from './useIsTablet' export * from './useIsTablet'
export * from './useMarketplaceSelect' export * from './useMarketplaceSelect'
......
import create from 'zustand'
import { devtools } from 'zustand/middleware'
interface State {
isCollectionNftsLoading: boolean
setIsCollectionNftsLoading: (isCollectionNftsLoading: boolean) => void
isCollectionStatsLoading: boolean
setIsCollectionStatsLoading: (isCollectionStatsLoading: boolean) => void
}
export const useIsCollectionLoading = create<State>()(
devtools(
(set) => ({
isCollectionNftsLoading: false,
setIsCollectionNftsLoading: (isCollectionNftsLoading) =>
set(() => {
return { isCollectionNftsLoading }
}),
isCollectionStatsLoading: false,
setIsCollectionStatsLoading: (isCollectionStatsLoading) =>
set(() => {
return { isCollectionStatsLoading }
}),
}),
{ name: 'useIsCollectionLoading' }
)
)
import { style } from '@vanilla-extract/css' import { style } from '@vanilla-extract/css'
import { buttonTextMedium } from 'nft/css/common.css' import { buttonTextMedium } from 'nft/css/common.css'
import { loadingBlock } from 'nft/css/loading.css'
import { sprinkles, vars } from '../../css/sprinkles.css' import { sprinkles, vars } from '../../css/sprinkles.css'
...@@ -46,6 +47,14 @@ export const selectedActivitySwitcherToggle = style([ ...@@ -46,6 +47,14 @@ export const selectedActivitySwitcherToggle = style([
}, },
]) ])
export const loadingBanner = style([
loadingBlock,
sprinkles({
width: 'full',
height: '100',
}),
])
export const noCollectionAssets = sprinkles({ export const noCollectionAssets = sprinkles({
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
......
import { AnimatedBox, Box } from 'nft/components/Box' import { AnimatedBox, Box } from 'nft/components/Box'
import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection' import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks' import { useBag, useCollectionFilters, useFiltersExpanded, useIsCollectionLoading, useIsMobile } from 'nft/hooks'
import * as styles from 'nft/pages/collection/index.css'
import { CollectionStatsFetcher } from 'nft/queries' import { CollectionStatsFetcher } from 'nft/queries'
import { GenieCollection } from 'nft/types'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { useLocation, useNavigate, useParams } from 'react-router-dom' import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { useSpring } from 'react-spring/web' import { useSpring } from 'react-spring/web'
import * as styles from './index.css'
const FILTER_WIDTH = 332 const FILTER_WIDTH = 332
const BAG_WIDTH = 324 const BAG_WIDTH = 324
const Collection = () => { const Collection = () => {
const { contractAddress } = useParams() const { contractAddress } = useParams()
const setIsCollectionStatsLoading = useIsCollectionLoading((state) => state.setIsCollectionStatsLoading)
const isMobile = useIsMobile() const isMobile = useIsMobile()
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded() const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
...@@ -28,6 +29,10 @@ const Collection = () => { ...@@ -28,6 +29,10 @@ const Collection = () => {
CollectionStatsFetcher(contractAddress as string) CollectionStatsFetcher(contractAddress as string)
) )
useEffect(() => {
setIsCollectionStatsLoading(isLoading)
}, [isLoading, setIsCollectionStatsLoading])
const { gridX, gridWidthOffset } = useSpring({ const { gridX, gridWidthOffset } = useSpring({
gridX: isFiltersExpanded ? FILTER_WIDTH : 0, gridX: isFiltersExpanded ? FILTER_WIDTH : 0,
gridWidthOffset: isFiltersExpanded gridWidthOffset: isFiltersExpanded
...@@ -55,20 +60,30 @@ const Collection = () => { ...@@ -55,20 +60,30 @@ const Collection = () => {
return ( return (
<Column width="full"> <Column width="full">
{collectionStats && contractAddress ? ( {contractAddress ? (
<> <>
{' '} {' '}
<Box width="full" height="160"> <Box width="full" height="160">
<Box <Box width="full" height="160">
as="img" {isLoading ? (
maxHeight="full" <Box height="full" width="full" className={styles.loadingBanner} />
width="full" ) : (
src={collectionStats?.bannerImageUrl} <Box
className={`${styles.bannerImage}`} as="img"
/> height="full"
width="full"
src={collectionStats?.bannerImageUrl}
className={isLoading ? styles.loadingBanner : styles.bannerImage}
background="none"
/>
)}
</Box>
</Box> </Box>
<Column paddingX="32"> <Column paddingX="32">
{collectionStats && <CollectionStats stats={collectionStats} isMobile={isMobile} />} {(isLoading || collectionStats !== undefined) && (
<CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} />
)}
<ActivitySwitcher <ActivitySwitcher
showActivity={isActivityToggled} showActivity={isActivityToggled}
toggleActivity={() => { toggleActivity={() => {
...@@ -102,10 +117,11 @@ const Collection = () => { ...@@ -102,10 +117,11 @@ const Collection = () => {
collectionName={collectionStats?.name ?? ''} collectionName={collectionStats?.name ?? ''}
/> />
) )
: contractAddress && ( : contractAddress &&
(isLoading || collectionStats !== undefined) && (
<CollectionNfts <CollectionNfts
collectionStats={collectionStats || ({} as GenieCollection)}
contractAddress={contractAddress} contractAddress={contractAddress}
collectionStats={collectionStats}
rarityVerified={collectionStats?.rarityVerified} rarityVerified={collectionStats?.rarityVerified}
/> />
)} )}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment