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 { Center } from 'nft/components/Flex'
|
||||
import { bodySmall, buttonTextMedium, header2 } from 'nft/css/common.css'
|
||||
import { useCollectionFilters } from 'nft/hooks'
|
||||
import { AssetsFetcher } from 'nft/queries'
|
||||
import { useMemo } from 'react'
|
||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
@ -14,6 +15,8 @@ interface CollectionNftsProps {
|
||||
}
|
||||
|
||||
export const CollectionNfts = ({ contractAddress }: CollectionNftsProps) => {
|
||||
const buyNow = useCollectionFilters((state) => state.buyNow)
|
||||
|
||||
const {
|
||||
data: collectionAssets,
|
||||
isSuccess: AssetsFetchSuccess,
|
||||
@ -24,11 +27,13 @@ export const CollectionNfts = ({ contractAddress }: CollectionNftsProps) => {
|
||||
'collectionNfts',
|
||||
{
|
||||
contractAddress,
|
||||
notForSale: !buyNow,
|
||||
},
|
||||
],
|
||||
async ({ pageParam = 0 }) => {
|
||||
return await AssetsFetcher({
|
||||
contractAddress,
|
||||
notForSale: !buyNow,
|
||||
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 { CollectionNfts } from 'nft/components/collection/CollectionNfts'
|
||||
import { CollectionStats } from 'nft/components/collection/CollectionStats'
|
||||
import { CollectionNfts, CollectionStats, FilterButton, Filters } from 'nft/components/collection'
|
||||
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 { CollectionStatsFetcher } from 'nft/queries'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useSpring } from 'react-spring/web'
|
||||
|
||||
const FILTER_WIDTH = 332
|
||||
|
||||
const Collection = () => {
|
||||
const { contractAddress } = useParams()
|
||||
|
||||
const isMobile = useIsMobile()
|
||||
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
|
||||
|
||||
const { data: collectionStats } = useQuery(['collectionStats', contractAddress], () =>
|
||||
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 (
|
||||
<Column width="full">
|
||||
<Box width="full" height="160">
|
||||
@ -34,8 +43,30 @@ const Collection = () => {
|
||||
<CollectionStats stats={collectionStats} isMobile={isMobile} />
|
||||
</Row>
|
||||
)}
|
||||
<Row alignItems="flex-start" position="relative" paddingLeft="32" paddingRight="32">
|
||||
<AnimatedBox width="full">
|
||||
<Row alignItems="flex-start" position="relative" paddingX="48">
|
||||
<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} />}
|
||||
</AnimatedBox>
|
||||
</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