feat: nft filter bar (#4617)
* initial filter window * filters * filter button * adding all filters to filter check * sorting exports * reviewing old css * change to const * responding to comments * removing isMobile * fixing radio input * refactoring radio * refactoring radio * reusing the same class * removing unused props * removing useless clsx * removing scrollToTop
This commit is contained in:
parent
ea0fe83d00
commit
80c1f0cdf9
@ -4,6 +4,7 @@ import { CollectionAsset } from 'nft/components/collection/CollectionAsset'
|
|||||||
import * as styles from 'nft/components/collection/CollectionNfts.css'
|
import * as styles from 'nft/components/collection/CollectionNfts.css'
|
||||||
import { Center } from 'nft/components/Flex'
|
import { Center } from 'nft/components/Flex'
|
||||||
import { bodySmall, buttonTextMedium, header2 } from 'nft/css/common.css'
|
import { bodySmall, buttonTextMedium, header2 } from 'nft/css/common.css'
|
||||||
|
import { useCollectionFilters } from 'nft/hooks'
|
||||||
import { AssetsFetcher } from 'nft/queries'
|
import { AssetsFetcher } from 'nft/queries'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
@ -14,6 +15,8 @@ interface CollectionNftsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CollectionNfts = ({ contractAddress }: CollectionNftsProps) => {
|
export const CollectionNfts = ({ contractAddress }: CollectionNftsProps) => {
|
||||||
|
const buyNow = useCollectionFilters((state) => state.buyNow)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: collectionAssets,
|
data: collectionAssets,
|
||||||
isSuccess: AssetsFetchSuccess,
|
isSuccess: AssetsFetchSuccess,
|
||||||
@ -24,11 +27,13 @@ export const CollectionNfts = ({ contractAddress }: CollectionNftsProps) => {
|
|||||||
'collectionNfts',
|
'collectionNfts',
|
||||||
{
|
{
|
||||||
contractAddress,
|
contractAddress,
|
||||||
|
notForSale: !buyNow,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
async ({ pageParam = 0 }) => {
|
async ({ pageParam = 0 }) => {
|
||||||
return await AssetsFetcher({
|
return await AssetsFetcher({
|
||||||
contractAddress,
|
contractAddress,
|
||||||
|
notForSale: !buyNow,
|
||||||
pageParam,
|
pageParam,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
23
src/nft/components/collection/FilterButton.css.ts
Normal file
23
src/nft/components/collection/FilterButton.css.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { style } from '@vanilla-extract/css'
|
||||||
|
import { sprinkles, themeVars, vars } from 'nft/css/sprinkles.css'
|
||||||
|
|
||||||
|
export const filterButton = sprinkles({
|
||||||
|
backgroundColor: 'blue400',
|
||||||
|
color: 'explicitWhite',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const filterButtonExpanded = style({
|
||||||
|
background: vars.color.lightGrayButton,
|
||||||
|
color: themeVars.colors.blackBlue,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const filterBadge = style([
|
||||||
|
sprinkles({
|
||||||
|
position: 'absolute',
|
||||||
|
left: '18',
|
||||||
|
fontSize: '28',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
top: '-3px',
|
||||||
|
},
|
||||||
|
])
|
69
src/nft/components/collection/FilterButton.tsx
Normal file
69
src/nft/components/collection/FilterButton.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import { Box } from 'nft/components/Box'
|
||||||
|
import * as styles from 'nft/components/collection/FilterButton.css'
|
||||||
|
import { Row } from 'nft/components/Flex'
|
||||||
|
import { FilterIcon } from 'nft/components/icons'
|
||||||
|
import { useCollectionFilters } from 'nft/hooks'
|
||||||
|
import { putCommas } from 'nft/utils/putCommas'
|
||||||
|
|
||||||
|
export const FilterButton = ({
|
||||||
|
onClick,
|
||||||
|
isMobile,
|
||||||
|
isFiltersExpanded,
|
||||||
|
results,
|
||||||
|
}: {
|
||||||
|
isMobile: boolean
|
||||||
|
isFiltersExpanded: boolean
|
||||||
|
results?: number
|
||||||
|
onClick: () => void
|
||||||
|
}) => {
|
||||||
|
const { minPrice, maxPrice, minRarity, maxRarity, traits, markets, buyNow } = useCollectionFilters((state) => ({
|
||||||
|
minPrice: state.minPrice,
|
||||||
|
maxPrice: state.maxPrice,
|
||||||
|
minRarity: state.minRarity,
|
||||||
|
maxRarity: state.maxRarity,
|
||||||
|
traits: state.traits,
|
||||||
|
markets: state.markets,
|
||||||
|
buyNow: state.buyNow,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const showFilterBadge = minPrice || maxPrice || minRarity || maxRarity || traits.length || markets.length || buyNow
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className={clsx(styles.filterButton, !isFiltersExpanded && styles.filterButtonExpanded)}
|
||||||
|
borderRadius="12"
|
||||||
|
fontSize="16"
|
||||||
|
cursor="pointer"
|
||||||
|
position="relative"
|
||||||
|
onClick={onClick}
|
||||||
|
paddingTop="12"
|
||||||
|
paddingLeft="12"
|
||||||
|
paddingBottom="12"
|
||||||
|
paddingRight={isMobile ? '8' : '12'}
|
||||||
|
width={isMobile ? '44' : 'auto'}
|
||||||
|
height="44"
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
>
|
||||||
|
{showFilterBadge && (
|
||||||
|
<Row className={styles.filterBadge} color={isFiltersExpanded ? 'grey700' : 'blue400'}>
|
||||||
|
•
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<FilterIcon
|
||||||
|
style={{ marginBottom: '-4px', paddingRight: `${!isFiltersExpanded || showFilterBadge ? '6px' : '0px'}` }}
|
||||||
|
/>
|
||||||
|
{!isMobile && !isFiltersExpanded && 'Filter'}
|
||||||
|
|
||||||
|
{showFilterBadge && !isMobile ? (
|
||||||
|
<Box display="inline-block" position="relative">
|
||||||
|
{!isFiltersExpanded && (
|
||||||
|
<Box as="span" position="absolute" left="4" style={{ top: '5px', fontSize: '8px' }}>
|
||||||
|
•
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box paddingLeft={!isFiltersExpanded ? '12' : '2'}>{results ? putCommas(results) : 0} results</Box>
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
82
src/nft/components/collection/Filters.css.ts
Normal file
82
src/nft/components/collection/Filters.css.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { style } from '@vanilla-extract/css'
|
||||||
|
import { breakpoints, sprinkles, themeVars } from 'nft/css/sprinkles.css'
|
||||||
|
|
||||||
|
export const container = style([
|
||||||
|
sprinkles({
|
||||||
|
overflow: 'auto',
|
||||||
|
height: 'viewHeight',
|
||||||
|
paddingTop: '24',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
width: '300px',
|
||||||
|
paddingBottom: '96px',
|
||||||
|
'@media': {
|
||||||
|
[`(max-width: ${breakpoints.sm - 1}px)`]: {
|
||||||
|
width: 'auto',
|
||||||
|
height: 'auto',
|
||||||
|
paddingBottom: '0px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
export const rowHover = style([
|
||||||
|
sprinkles({
|
||||||
|
borderRadius: '12',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
':hover': {
|
||||||
|
background: themeVars.colors.lightGray,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
export const rowHoverOpen = style([
|
||||||
|
sprinkles({
|
||||||
|
borderTopLeftRadius: '12',
|
||||||
|
borderTopRightRadius: '12',
|
||||||
|
borderBottomLeftRadius: '0',
|
||||||
|
borderBottomRightRadius: '0',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
':hover': {
|
||||||
|
background: themeVars.colors.medGray,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
export const subRowHover = style({
|
||||||
|
':hover': {
|
||||||
|
background: themeVars.colors.medGray,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const detailsOpen = sprinkles({
|
||||||
|
background: 'darkGray10',
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderColor: 'medGray',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const summaryOpen = sprinkles({
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderColor: 'medGray',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const filterDropDowns = style([
|
||||||
|
sprinkles({
|
||||||
|
overflowY: 'scroll',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
maxHeight: '190px',
|
||||||
|
'::-webkit-scrollbar': { display: 'none' },
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
},
|
||||||
|
])
|
51
src/nft/components/collection/Filters.tsx
Normal file
51
src/nft/components/collection/Filters.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Box } from 'nft/components/Box'
|
||||||
|
import * as styles from 'nft/components/collection/Filters.css'
|
||||||
|
import { Column, Row } from 'nft/components/Flex'
|
||||||
|
import { Radio } from 'nft/components/layout/Radio'
|
||||||
|
import { useCollectionFilters } from 'nft/hooks'
|
||||||
|
import { useReducer } from 'react'
|
||||||
|
|
||||||
|
export const Filters = () => {
|
||||||
|
const { buyNow, setBuyNow } = useCollectionFilters((state) => ({
|
||||||
|
buyNow: state.buyNow,
|
||||||
|
setBuyNow: state.setBuyNow,
|
||||||
|
}))
|
||||||
|
const [buyNowHovered, toggleBuyNowHover] = useReducer((state) => !state, false)
|
||||||
|
|
||||||
|
const handleBuyNowToggle = () => {
|
||||||
|
setBuyNow(!buyNow)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={styles.container}>
|
||||||
|
<Row width="full" justifyContent="space-between">
|
||||||
|
<Row as="span" fontSize="20" color="blackBlue">
|
||||||
|
Filters
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
<Column paddingTop="8">
|
||||||
|
<Row
|
||||||
|
justifyContent="space-between"
|
||||||
|
className={styles.rowHover}
|
||||||
|
gap="2"
|
||||||
|
paddingTop="12"
|
||||||
|
paddingRight="16"
|
||||||
|
paddingBottom="12"
|
||||||
|
paddingLeft="12"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleBuyNowToggle()
|
||||||
|
}}
|
||||||
|
onMouseEnter={toggleBuyNowHover}
|
||||||
|
onMouseLeave={toggleBuyNowHover}
|
||||||
|
>
|
||||||
|
<Box fontSize="14" fontWeight="medium" as="summary">
|
||||||
|
Buy now
|
||||||
|
</Box>
|
||||||
|
<Radio hovered={buyNowHovered} checked={buyNow} onClick={handleBuyNowToggle} />
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
4
src/nft/components/collection/index.ts
Normal file
4
src/nft/components/collection/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { CollectionNfts } from './CollectionNfts'
|
||||||
|
export { CollectionStats } from './CollectionStats'
|
||||||
|
export { FilterButton } from './FilterButton'
|
||||||
|
export { Filters } from './Filters'
|
53
src/nft/components/layout/Radio.css.ts
Normal file
53
src/nft/components/layout/Radio.css.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { style } from '@vanilla-extract/css'
|
||||||
|
import { sprinkles, vars } from 'nft/css/sprinkles.css'
|
||||||
|
|
||||||
|
export const radio = style([
|
||||||
|
sprinkles({
|
||||||
|
position: 'relative',
|
||||||
|
display: 'inline-block',
|
||||||
|
height: '24',
|
||||||
|
width: '24',
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: 'transparent',
|
||||||
|
borderRadius: { default: 'round', before: 'round' },
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: '2px',
|
||||||
|
top: '0',
|
||||||
|
left: '0',
|
||||||
|
right: '0',
|
||||||
|
bottom: '0',
|
||||||
|
transition: {
|
||||||
|
default: '250',
|
||||||
|
before: '250',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
export const greyBorderRadio = style([
|
||||||
|
radio,
|
||||||
|
sprinkles({
|
||||||
|
borderColor: 'grey400',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
export const blueBorderRadio = style([
|
||||||
|
radio,
|
||||||
|
sprinkles({
|
||||||
|
borderColor: 'blue400',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
export const selectedRadio = style([
|
||||||
|
blueBorderRadio,
|
||||||
|
{
|
||||||
|
':before': {
|
||||||
|
position: 'absolute',
|
||||||
|
backgroundColor: vars.color.blue400,
|
||||||
|
content: '',
|
||||||
|
height: '14px',
|
||||||
|
width: '14px',
|
||||||
|
top: 3,
|
||||||
|
left: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
21
src/nft/components/layout/Radio.tsx
Normal file
21
src/nft/components/layout/Radio.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Box } from 'nft/components/Box'
|
||||||
|
import * as styles from 'nft/components/layout/Radio.css'
|
||||||
|
import { MouseEvent } from 'react'
|
||||||
|
|
||||||
|
interface RadioProps {
|
||||||
|
hovered: boolean
|
||||||
|
checked: boolean
|
||||||
|
onClick: (e: MouseEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Radio = ({ hovered, checked, onClick }: RadioProps) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="label"
|
||||||
|
className={checked ? styles.selectedRadio : hovered ? styles.blueBorderRadio : styles.greyBorderRadio}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Radio.displayName = 'Radio'
|
@ -1,22 +1,31 @@
|
|||||||
import { AnimatedBox, Box } from 'nft/components/Box'
|
import { AnimatedBox, Box } from 'nft/components/Box'
|
||||||
import { CollectionNfts } from 'nft/components/collection/CollectionNfts'
|
import { CollectionNfts, CollectionStats, FilterButton, Filters } from 'nft/components/collection'
|
||||||
import { CollectionStats } from 'nft/components/collection/CollectionStats'
|
|
||||||
import { Column, Row } from 'nft/components/Flex'
|
import { Column, Row } from 'nft/components/Flex'
|
||||||
import { useIsMobile } from 'nft/hooks/useIsMobile'
|
import { useFiltersExpanded, useIsMobile } from 'nft/hooks'
|
||||||
import * as styles from 'nft/pages/collection/index.css'
|
import * as styles from 'nft/pages/collection/index.css'
|
||||||
import { CollectionStatsFetcher } from 'nft/queries'
|
import { CollectionStatsFetcher } from 'nft/queries'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { useSpring } from 'react-spring/web'
|
||||||
|
|
||||||
|
const FILTER_WIDTH = 332
|
||||||
|
|
||||||
const Collection = () => {
|
const Collection = () => {
|
||||||
const { contractAddress } = useParams()
|
const { contractAddress } = useParams()
|
||||||
|
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
|
||||||
|
|
||||||
const { data: collectionStats } = useQuery(['collectionStats', contractAddress], () =>
|
const { data: collectionStats } = useQuery(['collectionStats', contractAddress], () =>
|
||||||
CollectionStatsFetcher(contractAddress as string)
|
CollectionStatsFetcher(contractAddress as string)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/// @reviewer these look the same now but will be diff later
|
||||||
|
const { gridX, gridWidthOffset } = useSpring({
|
||||||
|
gridX: isFiltersExpanded ? FILTER_WIDTH : 0,
|
||||||
|
gridWidthOffset: isFiltersExpanded ? FILTER_WIDTH : 0,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column width="full">
|
<Column width="full">
|
||||||
<Box width="full" height="160">
|
<Box width="full" height="160">
|
||||||
@ -34,8 +43,30 @@ const Collection = () => {
|
|||||||
<CollectionStats stats={collectionStats} isMobile={isMobile} />
|
<CollectionStats stats={collectionStats} isMobile={isMobile} />
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
<Row alignItems="flex-start" position="relative" paddingLeft="32" paddingRight="32">
|
<Row alignItems="flex-start" position="relative" paddingX="48">
|
||||||
<AnimatedBox width="full">
|
<Box position="sticky" top="72" width="0">
|
||||||
|
{isFiltersExpanded && <Filters />}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */}
|
||||||
|
<AnimatedBox
|
||||||
|
style={{
|
||||||
|
transform: gridX.interpolate((x) => `translate(${x as number}px)`),
|
||||||
|
width: gridWidthOffset.interpolate((x) => `calc(100% - ${x as number}px)`),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AnimatedBox position="sticky" top="72" width="full" zIndex="3">
|
||||||
|
<Box backgroundColor="white08" width="full" paddingBottom="8" style={{ backdropFilter: 'blur(24px)' }}>
|
||||||
|
<Row marginTop="12" gap="12">
|
||||||
|
<FilterButton
|
||||||
|
isMobile={isMobile}
|
||||||
|
isFiltersExpanded={isFiltersExpanded}
|
||||||
|
onClick={() => setFiltersExpanded(!isFiltersExpanded)}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
</AnimatedBox>
|
||||||
|
|
||||||
{contractAddress && <CollectionNfts contractAddress={contractAddress} />}
|
{contractAddress && <CollectionNfts contractAddress={contractAddress} />}
|
||||||
</AnimatedBox>
|
</AnimatedBox>
|
||||||
</Row>
|
</Row>
|
||||||
|
5
src/nft/utils/index.ts
Normal file
5
src/nft/utils/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './buildActivityAsset'
|
||||||
|
export * from './buildSellObject'
|
||||||
|
export * from './calcPoolPrice'
|
||||||
|
export * from './currency'
|
||||||
|
export * from './listNfts'
|
Loading…
Reference in New Issue
Block a user