Commit bab2f47a authored by aballerr's avatar aballerr Committed by GitHub

style: filters update (#4749)

* styles filter update
parent 03237255
...@@ -18,6 +18,7 @@ import { ...@@ -18,6 +18,7 @@ import {
useIsMobile, useIsMobile,
} from 'nft/hooks' } from 'nft/hooks'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading' import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { usePriceRange } from 'nft/hooks/usePriceRange'
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'
...@@ -63,6 +64,13 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -63,6 +64,13 @@ 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 setPriceRangeLow = usePriceRange((state) => state.setPriceRangeLow)
const priceRangeLow = usePriceRange((state) => state.priceRangeLow)
const priceRangeHigh = usePriceRange((state) => state.priceRangeHigh)
const setPriceRangeHigh = usePriceRange((state) => state.setPriceRangeHigh)
const setPrevMinMax = usePriceRange((state) => state.setPrevMinMax)
const setIsCollectionNftsLoading = useIsCollectionLoading((state) => state.setIsCollectionNftsLoading) const setIsCollectionNftsLoading = useIsCollectionLoading((state) => state.setIsCollectionNftsLoading)
const removeTrait = useCollectionFilters((state) => state.removeTrait) const removeTrait = useCollectionFilters((state) => state.removeTrait)
const removeMarket = useCollectionFilters((state) => state.removeMarket) const removeMarket = useCollectionFilters((state) => state.removeMarket)
...@@ -231,7 +239,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -231,7 +239,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
const minMaxPriceChipText: string | undefined = useMemo(() => { const minMaxPriceChipText: string | undefined = useMemo(() => {
if (debouncedMinPrice && debouncedMaxPrice) { if (debouncedMinPrice && debouncedMaxPrice) {
return `Price: ${debouncedMinPrice}-${debouncedMaxPrice} ETH` return `Price: ${debouncedMinPrice} - ${debouncedMaxPrice} ETH`
} else if (debouncedMinPrice) { } else if (debouncedMinPrice) {
return `Min. Price: ${debouncedMinPrice} ETH` return `Min. Price: ${debouncedMinPrice} ETH`
} else if (debouncedMaxPrice) { } else if (debouncedMaxPrice) {
...@@ -269,6 +277,21 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -269,6 +277,21 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
} }
}, [collectionStats, location]) }, [collectionStats, location])
useEffect(() => {
if (collectionStats && collectionStats.floorPrice) {
const lowValue = collectionStats.floorPrice
const maxValue = 10 * collectionStats.floorPrice
if (priceRangeLow === '') {
setPriceRangeLow(lowValue?.toFixed(2))
}
if (priceRangeHigh === '') {
setPriceRangeHigh(maxValue.toFixed(2))
}
}
}, [collectionStats, priceRangeLow, priceRangeHigh, setPriceRangeHigh, setPriceRangeLow])
return ( return (
<> <>
<AnimatedBox position="sticky" top="72" width="full" zIndex="3"> <AnimatedBox position="sticky" top="72" width="full" zIndex="3">
...@@ -320,6 +343,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -320,6 +343,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
scrollToTop() scrollToTop()
setMin('') setMin('')
setMax('') setMax('')
setPrevMinMax([0, 100])
}} }}
/> />
)} )}
...@@ -327,6 +351,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -327,6 +351,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
<ClearAllButton <ClearAllButton
onClick={() => { onClick={() => {
reset() reset()
setPrevMinMax([0, 100])
scrollToTop() scrollToTop()
}} }}
> >
......
...@@ -14,14 +14,15 @@ export const CollectionSearch = () => { ...@@ -14,14 +14,15 @@ export const CollectionSearch = () => {
<Box <Box
as="input" as="input"
borderColor={{ default: 'backgroundOutline', focus: 'genieBlue' }} borderColor={{ default: 'backgroundOutline', focus: 'genieBlue' }}
borderWidth="1px" borderWidth="1.5px"
borderStyle="solid" borderStyle="solid"
borderRadius="12" borderRadius="12"
padding="12" padding="12"
backgroundColor="backgroundSurface" backgroundColor="backgroundSurface"
width="332"
fontSize="16" fontSize="16"
height="44" height="44"
color={{ placeholder: 'textSecondary', default: 'textPrimary' }} color={{ placeholder: 'textTertiary', default: 'textPrimary' }}
value={searchByNameText} value={searchByNameText}
placeholder={iscollectionStatsLoading ? '' : 'Search by name'} placeholder={iscollectionStatsLoading ? '' : 'Search by name'}
className={clsx(iscollectionStatsLoading && styles.filterButtonLoading)} className={clsx(iscollectionStatsLoading && styles.filterButtonLoading)}
......
...@@ -5,7 +5,7 @@ export const container = style([ ...@@ -5,7 +5,7 @@ export const container = style([
sprinkles({ sprinkles({
overflow: 'auto', overflow: 'auto',
height: 'viewHeight', height: 'viewHeight',
paddingTop: '24', paddingTop: '4',
}), }),
{ {
width: '300px', width: '300px',
...@@ -26,57 +26,81 @@ export const container = style([ ...@@ -26,57 +26,81 @@ export const container = style([
]) ])
export const rowHover = style([ export const rowHover = style([
sprinkles({
borderRadius: '12',
}),
{ {
':hover': { ':hover': {
background: themeVars.colors.backgroundSurface, background: themeVars.colors.backgroundInteractive,
borderRadius: 12,
}, },
}, },
]) ])
export const rowHoverOpen = style([ export const row = style([
sprinkles({ sprinkles({
borderTopLeftRadius: '12', display: 'flex',
borderTopRightRadius: '12', paddingRight: '16',
borderBottomLeftRadius: '0', cursor: 'pointer',
borderBottomRightRadius: '0', fontSize: '16',
lineHeight: '20',
justifyContent: 'space-between',
alignItems: 'center',
paddingLeft: '12',
paddingTop: '10',
paddingBottom: '10',
}), }),
{
':hover': {
background: themeVars.colors.backgroundOutline,
},
},
]) ])
export const subRowHover = style({ export const subRowHover = style({
':hover': { ':hover': {
background: themeVars.colors.backgroundOutline, background: themeVars.colors.backgroundInteractive,
}, },
}) })
export const detailsOpen = sprinkles({ export const borderTop = sprinkles({
background: 'backgroundModule', borderTopStyle: 'solid',
overflow: 'hidden', borderTopColor: 'backgroundOutline',
borderStyle: 'solid', borderTopWidth: '1px',
borderWidth: '1px',
borderColor: 'backgroundOutline',
}) })
export const summaryOpen = sprinkles({ export const borderBottom = sprinkles({
borderStyle: 'solid', borderBottomStyle: 'solid',
borderWidth: '1px', borderBottomColor: 'backgroundOutline',
borderColor: 'backgroundOutline', borderBottomWidth: '1px',
}) })
export const detailsOpen = style([
borderTop,
sprinkles({
overflow: 'hidden',
marginTop: '8',
marginBottom: '8',
}),
])
export const filterDropDowns = style([ export const filterDropDowns = style([
borderBottom,
sprinkles({ sprinkles({
overflowY: 'scroll', overflowY: 'scroll',
}), }),
{ {
maxHeight: '190px', maxHeight: '302px',
'::-webkit-scrollbar': { display: 'none' }, '::-webkit-scrollbar': { display: 'none' },
scrollbarWidth: 'none', scrollbarWidth: 'none',
}, },
]) ])
export const chevronIcon = style({
marginLeft: -1,
})
export const chevronContainer = style([
sprinkles({
color: 'textSecondary',
display: 'inline-block',
height: '28',
width: '28',
transition: '250',
}),
{
marginRight: -1,
},
])
...@@ -3,77 +3,41 @@ import * as styles from 'nft/components/collection/Filters.css' ...@@ -3,77 +3,41 @@ import * as styles from 'nft/components/collection/Filters.css'
import { MarketplaceSelect } from 'nft/components/collection/MarketplaceSelect' import { MarketplaceSelect } from 'nft/components/collection/MarketplaceSelect'
import { PriceRange } from 'nft/components/collection/PriceRange' import { PriceRange } from 'nft/components/collection/PriceRange'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { Radio } from 'nft/components/layout/Radio' import { Checkbox } from 'nft/components/layout/Checkbox'
import { subhead } from 'nft/css/common.css'
import { useCollectionFilters } from 'nft/hooks' import { useCollectionFilters } from 'nft/hooks'
import { Trait } from 'nft/hooks/useCollectionFilters' import { Trait } from 'nft/hooks/useCollectionFilters'
import { TraitPosition } from 'nft/hooks/useTraitsOpen'
import { groupBy } from 'nft/utils/groupBy' import { groupBy } from 'nft/utils/groupBy'
import { FocusEventHandler, FormEvent, useMemo, useState } from 'react' import { useMemo } from 'react'
import { useReducer } from 'react' import { useReducer } from 'react'
import { Input } from '../layout/Input'
import { TraitSelect } from './TraitSelect' import { TraitSelect } from './TraitSelect'
export const Filters = ({ export const Filters = ({ traits }: { traits: Trait[] }) => {
traits,
traitsByAmount,
}: {
traits: Trait[]
traitsByAmount: {
traitCount: number
numWithTrait: number
}[]
}) => {
const { buyNow, setBuyNow } = useCollectionFilters((state) => ({ const { buyNow, setBuyNow } = useCollectionFilters((state) => ({
buyNow: state.buyNow, buyNow: state.buyNow,
setBuyNow: state.setBuyNow, setBuyNow: state.setBuyNow,
})) }))
const traitsByGroup: Record<string, Trait[]> = useMemo(() => { const traitsByGroup: Record<string, Trait[]> = useMemo(() => (traits ? groupBy(traits, 'trait_type') : {}), [traits])
if (traits) {
let groupedTraits = groupBy(traits, 'trait_type')
groupedTraits['Number of traits'] = []
for (let i = 0; i < traitsByAmount.length; i++) {
groupedTraits['Number of traits'].push({
trait_type: 'Number of traits',
trait_value: traitsByAmount[i].traitCount,
trait_count: traitsByAmount[i].numWithTrait,
})
}
groupedTraits = Object.assign({ 'Number of traits': null }, groupedTraits)
return groupedTraits
} else return {}
}, [traits, traitsByAmount])
const [buyNowHovered, toggleBuyNowHover] = useReducer((state) => !state, false) const [buyNowHovered, toggleBuyNowHover] = useReducer((state) => !state, false)
const [search, setSearch] = useState('')
const handleBuyNowToggle = () => { const handleBuyNowToggle = () => {
setBuyNow(!buyNow) setBuyNow(!buyNow)
} }
const handleFocus: FocusEventHandler<HTMLInputElement> = (e) => {
e.currentTarget.placeholder = ''
}
const handleBlur: FocusEventHandler<HTMLInputElement> = (e) => {
e.currentTarget.placeholder = 'Search traits'
}
return ( return (
<Box className={styles.container}> <Box className={styles.container}>
<Row width="full" justifyContent="space-between"> <Row width="full" justifyContent="space-between"></Row>
<Row as="span" fontSize="20" color="textPrimary"> <Column marginTop="8">
Filters
</Row>
</Row>
<Column paddingTop="8">
<Row <Row
justifyContent="space-between" justifyContent="space-between"
className={styles.rowHover} className={`${styles.row} ${styles.rowHover}`}
gap="2" gap="2"
borderRadius="12"
paddingTop="12" paddingTop="12"
paddingRight="16"
paddingBottom="12" paddingBottom="12"
paddingLeft="12" paddingLeft="12"
cursor="pointer"
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault()
handleBuyNowToggle() handleBuyNowToggle()
...@@ -81,41 +45,30 @@ export const Filters = ({ ...@@ -81,41 +45,30 @@ export const Filters = ({
onMouseEnter={toggleBuyNowHover} onMouseEnter={toggleBuyNowHover}
onMouseLeave={toggleBuyNowHover} onMouseLeave={toggleBuyNowHover}
> >
<Box fontSize="14" fontWeight="medium" as="summary"> <Box className={subhead}>Buy now</Box>
Buy now <Checkbox hovered={buyNowHovered} checked={buyNow} onClick={handleBuyNowToggle}>
</Box> <span />
<Radio hovered={buyNowHovered} checked={buyNow} onClick={handleBuyNowToggle} /> </Checkbox>
</Row> </Row>
<MarketplaceSelect /> <MarketplaceSelect />
<Box marginTop="12" marginBottom="12"> <PriceRange />
<Box as="span" fontSize="20"> {Object.entries(traitsByGroup).length > 0 && (
Price <Box
</Box> as="span"
<PriceRange /> color="textSecondary"
</Box> paddingLeft="8"
<Box marginTop="12"> marginTop="12"
<Box as="span" fontSize="20"> marginBottom="12"
Traits className={styles.borderTop}
</Box> ></Box>
)}
<Column marginTop="12" marginBottom="60" gap={{ sm: '4' }}> <Column>
<Input {Object.entries(traitsByGroup).map(([type, traits], index) => (
display={!traits?.length ? 'none' : undefined} // the index is offset by two because price range and marketplace appear prior to it
value={search} <TraitSelect key={type} {...{ type, traits }} index={index + TraitPosition.TRAIT_START_INDEX} />
onChange={(e: FormEvent<HTMLInputElement>) => setSearch(e.currentTarget.value)} ))}
width="full" </Column>
marginBottom="8"
placeholder="Search traits"
autoComplete="off"
onFocus={handleFocus}
onBlur={handleBlur}
style={{ border: '2px solid rgba(153, 161, 189, 0.24)', maxWidth: '300px' }}
/>
{Object.entries(traitsByGroup).map(([type, traits]) => (
<TraitSelect key={type} {...{ type, traits, search }} />
))}
</Column>
</Box>
</Column> </Column>
</Box> </Box>
) )
......
...@@ -5,6 +5,8 @@ import { Column, Row } from 'nft/components/Flex' ...@@ -5,6 +5,8 @@ import { Column, Row } from 'nft/components/Flex'
import { ChevronUpIcon } from 'nft/components/icons' import { ChevronUpIcon } from 'nft/components/icons'
import { subheadSmall } from 'nft/css/common.css' import { subheadSmall } from 'nft/css/common.css'
import { useCollectionFilters } from 'nft/hooks/useCollectionFilters' import { useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { useTraitsOpen } from 'nft/hooks/useTraitsOpen'
import { TraitPosition } from 'nft/hooks/useTraitsOpen'
import { FormEvent, useEffect, useReducer, useState } from 'react' import { FormEvent, useEffect, useReducer, useState } from 'react'
import { Checkbox } from '../layout/Checkbox' import { Checkbox } from '../layout/Checkbox'
...@@ -58,9 +60,11 @@ const MarketplaceItem = ({ ...@@ -58,9 +60,11 @@ const MarketplaceItem = ({
fontWeight="normal" fontWeight="normal"
className={`${subheadSmall} ${styles.subRowHover}`} className={`${subheadSmall} ${styles.subRowHover}`}
paddingLeft="12" paddingLeft="12"
paddingRight="12" paddingRight="16"
borderRadius="12"
cursor="pointer" cursor="pointer"
style={{ paddingBottom: '21px', paddingTop: '21px', maxHeight: '44px' }} maxHeight="44"
style={{ paddingBottom: '22px', paddingTop: '22px' }}
onMouseEnter={toggleHover} onMouseEnter={toggleHover}
onMouseLeave={toggleHover} onMouseLeave={toggleHover}
onClick={handleCheckbox} onClick={handleCheckbox}
...@@ -91,55 +95,60 @@ export const MarketplaceSelect = () => { ...@@ -91,55 +95,60 @@ export const MarketplaceSelect = () => {
})) }))
const [isOpen, setOpen] = useState(!!selectedMarkets.length) const [isOpen, setOpen] = useState(!!selectedMarkets.length)
const setTraitsOpen = useTraitsOpen((state) => state.setTraitsOpen)
return ( return (
<Box <>
as="details" <Box className={styles.detailsOpen} opacity={isOpen ? '1' : '0'} />
className={clsx(subheadSmall, !isOpen && styles.rowHover, isOpen && styles.detailsOpen)}
borderRadius="12"
open={isOpen}
>
<Box <Box
as="summary" as="details"
className={clsx(isOpen && styles.summaryOpen, isOpen ? styles.rowHoverOpen : styles.rowHover)} className={clsx(subheadSmall, !isOpen && styles.rowHover)}
display="flex" open={isOpen}
justifyContent="space-between" borderRadius={isOpen ? '0' : '12'}
cursor="pointer"
alignItems="center"
fontSize="14"
paddingTop="12"
paddingLeft="12"
paddingRight="12"
paddingBottom={isOpen ? '8' : '12'}
onClick={(e) => {
e.preventDefault()
setOpen(!isOpen)
}}
> >
Marketplaces
<Box <Box
color="textSecondary" as="summary"
transition="250" className={`${styles.row} ${styles.rowHover}`}
height="28" display="flex"
width="28" justifyContent="space-between"
style={{ alignItems="center"
transform: `rotate(${isOpen ? 0 : 180}deg)`, fontSize="16"
paddingTop="12"
paddingLeft="12"
paddingBottom="12"
lineHeight="20"
borderRadius="12"
maxHeight="48"
onClick={(e) => {
e.preventDefault()
setOpen(!isOpen)
setTraitsOpen(TraitPosition.MARKPLACE_INDEX, !isOpen)
}} }}
> >
<ChevronUpIcon /> Marketplaces
<Box display="flex" alignItems="center">
<Box
className={styles.chevronContainer}
style={{
transform: `rotate(${isOpen ? 0 : 180}deg)`,
}}
>
<ChevronUpIcon className={styles.chevronIcon} />
</Box>
</Box>
</Box> </Box>
<Column className={styles.filterDropDowns} paddingBottom="8" paddingLeft="0">
{Object.entries(marketPlaceItems).map(([value, title]) => (
<MarketplaceItem
key={value}
title={title}
value={value}
count={marketCount?.[value] || 0}
{...{ addMarket, removeMarket, isMarketSelected: selectedMarkets.includes(value) }}
/>
))}
</Column>
</Box> </Box>
<Column className={styles.filterDropDowns} paddingLeft="0"> </>
{Object.entries(marketPlaceItems).map(([value, title]) => (
<MarketplaceItem
key={value}
title={title}
value={value}
count={marketCount?.[value] || 0}
{...{ addMarket, removeMarket, isMarketSelected: selectedMarkets.includes(value) }}
/>
))}
</Column>
</Box>
) )
} }
import { style } from '@vanilla-extract/css'
import { body } from 'nft/css/common.css'
import { sprinkles, themeVars } from 'nft/css/sprinkles.css'
// https://www.npmjs.com/package/react-slider
// https://codesandbox.io/s/peaceful-pine-gqszx6?file=/src/styles.css:587-641
export const sliderLight = style([
sprinkles({
height: '8',
marginTop: '20',
borderRadius: '8',
width: 'full',
}),
])
export const sliderDark = style([
sliderLight,
sprinkles({
backgroundColor: 'accentAction',
}),
])
export const tracker = style([
sprinkles({
backgroundColor: 'accentAction',
height: '8',
}),
{
selectors: {
'&:nth-child(1)': {
backgroundColor: themeVars.colors.accentActionSoft,
borderRadius: 8,
opacity: 0.65,
},
'&:nth-child(3)': {
backgroundColor: themeVars.colors.accentActionSoft,
borderRadius: 8,
opacity: 0.65,
},
},
},
])
export const thumb = style([
sprinkles({
width: '12',
height: '20',
borderRadius: '4',
cursor: 'pointer',
boxShadow: 'shallow',
backgroundColor: 'grey50',
}),
{
top: -6,
},
])
export const priceInput = style([
body,
sprinkles({
backgroundColor: 'transparent',
padding: '12',
borderRadius: '12',
borderStyle: 'solid',
borderWidth: '1.5px',
}),
])
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { NumericInput } from 'nft/components/layout/Input' import { NumericInput } from 'nft/components/layout/Input'
import { body } from 'nft/css/common.css'
import { useIsMobile } from 'nft/hooks' import { useIsMobile } from 'nft/hooks'
import { useCollectionFilters } from 'nft/hooks/useCollectionFilters' import { useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { usePriceRange } from 'nft/hooks/usePriceRange'
import { TraitPosition } from 'nft/hooks/useTraitsOpen'
import { scrollToTop } from 'nft/utils/scrollToTop' import { scrollToTop } from 'nft/utils/scrollToTop'
import { useEffect, useState } from 'react' import { FormEvent, useEffect, useState } from 'react'
import { FocusEventHandler, FormEvent } from 'react' import { FocusEventHandler } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import ReactSlider from 'react-slider'
import { useIsDarkMode } from 'state/user/hooks'
import * as styles from './PriceRange.css'
import { TraitsHeader } from './TraitsHeader'
export const PriceRange = () => { export const PriceRange = () => {
const [placeholderText, setPlaceholderText] = useState('') const [placeholderText, setPlaceholderText] = useState('')
...@@ -13,14 +22,23 @@ export const PriceRange = () => { ...@@ -13,14 +22,23 @@ export const PriceRange = () => {
const setMaxPrice = useCollectionFilters((state) => state.setMaxPrice) const setMaxPrice = useCollectionFilters((state) => state.setMaxPrice)
const minPrice = useCollectionFilters((state) => state.minPrice) const minPrice = useCollectionFilters((state) => state.minPrice)
const maxPrice = useCollectionFilters((state) => state.maxPrice) const maxPrice = useCollectionFilters((state) => state.maxPrice)
const isMobile = useIsMobile() const priceRangeLow = usePriceRange((state) => state.priceRangeLow)
const priceRangeHigh = usePriceRange((state) => state.priceRangeHigh)
const setPriceRangeLow = usePriceRange((statae) => statae.setPriceRangeLow)
const setPriceRangeHigh = usePriceRange((statae) => statae.setPriceRangeHigh)
const prevMinMax = usePriceRange((state) => state.prevMinMax)
const setPrevMinMax = usePriceRange((state) => state.setPrevMinMax)
const isDarktheme = useIsDarkMode()
const isMobile = useIsMobile()
const location = useLocation() const location = useLocation()
useEffect(() => { useEffect(() => {
setMinPrice('') setMinPrice('')
setMaxPrice('') setMaxPrice('')
}, [location.pathname, setMinPrice, setMaxPrice]) setPriceRangeLow('')
setPriceRangeHigh('')
}, [location.pathname, setMinPrice, setMaxPrice, setPriceRangeLow, setPriceRangeHigh])
const handleFocus: FocusEventHandler<HTMLInputElement> = (e) => { const handleFocus: FocusEventHandler<HTMLInputElement> = (e) => {
setPlaceholderText(e.currentTarget.placeholder) setPlaceholderText(e.currentTarget.placeholder)
...@@ -32,53 +50,127 @@ export const PriceRange = () => { ...@@ -32,53 +50,127 @@ export const PriceRange = () => {
setPlaceholderText('') setPlaceholderText('')
} }
const updateMinPriceRange = (v: FormEvent<HTMLInputElement>) => {
const [, prevMax] = prevMinMax
// if there is actually a number, update the slider place
if (v.currentTarget.value) {
// we are calculating the new slider position here
const diff = parseInt(v.currentTarget.value) - parseInt(priceRangeLow)
const newLow = Math.floor(100 * (diff / (parseInt(priceRangeHigh) - parseInt(priceRangeLow))))
// if the slider min value is larger than or equal to the max, we don't want it to move past the max
// so we put the sliders on top of each other
// if it is less than, we can move it
if (parseInt(v.currentTarget.value) >= parseInt(maxPrice)) {
setPrevMinMax([prevMax, prevMax])
} else {
setPrevMinMax([newLow, prevMax])
}
} else {
// if there is no number, reset the slider position
setPrevMinMax([0, prevMax])
}
// set min price for price range querying
setMinPrice(v.currentTarget.value)
scrollToTop()
}
const updateMaxPriceRange = (v: FormEvent<HTMLInputElement>) => {
const [prevMin] = prevMinMax
if (v.currentTarget.value) {
const range = parseInt(priceRangeHigh) - parseInt(v.currentTarget.value)
const newMax = Math.floor(100 - 100 * (range / (parseInt(priceRangeHigh) - parseInt(priceRangeLow))))
if (parseInt(v.currentTarget.value) <= parseInt(minPrice)) {
setPrevMinMax([prevMin, prevMin])
} else {
setPrevMinMax([prevMin, newMax])
}
} else {
setPrevMinMax([prevMin, 100])
}
setMaxPrice(v.currentTarget.value)
scrollToTop()
}
const handleSliderLogic = (minMax: Array<number>) => {
const [newMin, newMax] = minMax
// strip commas so parseFloat can parse properly
const priceRangeHighNumber = parseFloat(priceRangeHigh.replace(/,/g, ''))
const priceRangeLowNumber = parseFloat(priceRangeLow.replace(/,/g, ''))
const diff = priceRangeHighNumber - priceRangeLowNumber
// minprice
const minChange = newMin / 100
const newMinPrice = minChange * diff + priceRangeLowNumber
// max price
const maxChange = (100 - newMax) / 100
const newMaxPrice = priceRangeHighNumber - maxChange * diff
setMinPrice(newMinPrice.toFixed(2))
setMaxPrice(newMaxPrice.toFixed(2))
// set back to placeholder when they move back to end of range
if (newMin === 0) {
setMinPrice('')
}
if (newMax === 100) {
setMaxPrice('')
}
// update the previous minMax for future checks
setPrevMinMax(minMax)
}
return ( return (
<Row gap="12" marginTop="12" color="textPrimary"> <TraitsHeader title="Price range" index={TraitPosition.PRICE_RANGE_INDEX}>
<Row position="relative" style={{ flex: 1 }}> <Row gap="12" marginTop="12" color="textPrimary">
<NumericInput <Row position="relative">
style={{ <NumericInput
width: isMobile ? '100%' : '142px', style={{
border: '2px solid rgba(153, 161, 189, 0.24)', width: isMobile ? '100%' : '126px',
}} }}
borderRadius="12" className={styles.priceInput}
padding="12" placeholder={priceRangeLow}
fontSize="14" onChange={updateMinPriceRange}
color={{ placeholder: 'textSecondary', default: 'textPrimary' }} onFocus={handleFocus}
backgroundColor="transparent" value={minPrice}
placeholder="Min" onBlur={handleBlur}
defaultValue={minPrice} />
onChange={(v: FormEvent<HTMLInputElement>) => { </Row>
scrollToTop() <Box className={body}>to</Box>
setMinPrice(v.currentTarget.value) <Row position="relative" flex="1">
}} <NumericInput
onFocus={handleFocus} style={{
value={minPrice} width: isMobile ? '100%' : '126px',
onBlur={handleBlur} }}
/> className={styles.priceInput}
placeholder={priceRangeHigh}
value={maxPrice}
onChange={updateMaxPriceRange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
</Row>
</Row> </Row>
<Row position="relative" style={{ flex: 1 }}>
<NumericInput <Row marginBottom="20" paddingLeft="8" paddingRight="8">
style={{ <ReactSlider
width: isMobile ? '100%' : '142px', disabled={!priceRangeLow || !priceRangeHigh}
border: '2px solid rgba(153, 161, 189, 0.24)', defaultValue={[0, 100]}
}} value={prevMinMax}
borderColor={{ default: 'backgroundOutline', focus: 'textSecondary' }} className={isDarktheme ? styles.sliderDark : styles.sliderLight}
borderRadius="12" trackClassName={styles.tracker}
padding="12" thumbClassName={styles.thumb}
fontSize="14" onChange={handleSliderLogic}
color={{ placeholder: 'textSecondary', default: 'textPrimary' }}
backgroundColor="transparent"
placeholder="Max"
defaultValue={maxPrice}
value={maxPrice}
onChange={(v: FormEvent<HTMLInputElement>) => {
scrollToTop()
setMaxPrice(v.currentTarget.value)
}}
onFocus={handleFocus}
onBlur={handleBlur}
/> />
</Row> </Row>
</Row> </TraitsHeader>
) )
} }
...@@ -5,7 +5,7 @@ const TraitChipWrap = styled.div` ...@@ -5,7 +5,7 @@ const TraitChipWrap = styled.div`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 8px 4px 8px 12px; padding: 2px 4px 2px 12px;
font-weight: 600; font-weight: 600;
border-radius: 12px; border-radius: 12px;
background-color: ${({ theme }) => theme.backgroundInteractive}; background-color: ${({ theme }) => theme.backgroundInteractive};
...@@ -24,7 +24,6 @@ export const TraitChip = ({ onClick, value }: { value: string; onClick: () => vo ...@@ -24,7 +24,6 @@ export const TraitChip = ({ onClick, value }: { value: string; onClick: () => vo
return ( return (
<TraitChipWrap> <TraitChipWrap>
<span>{value}</span> <span>{value}</span>
<CrossIconWrap onClick={onClick}> <CrossIconWrap onClick={onClick}>
<CrossIcon cursor="pointer" /> <CrossIcon cursor="pointer" />
</CrossIconWrap> </CrossIconWrap>
......
import clsx from 'clsx'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { ChevronUpIcon } from 'nft/components/icons'
import { Checkbox } from 'nft/components/layout/Checkbox' import { Checkbox } from 'nft/components/layout/Checkbox'
import { subheadSmall } from 'nft/css/common.css' import { subheadSmall } from 'nft/css/common.css'
import { Trait, useCollectionFilters } from 'nft/hooks/useCollectionFilters' import { Trait, useCollectionFilters } from 'nft/hooks/useCollectionFilters'
import { pluralize } from 'nft/utils/roundAndPluralize' import { pluralize } from 'nft/utils/roundAndPluralize'
import { scrollToTop } from 'nft/utils/scrollToTop' import { scrollToTop } from 'nft/utils/scrollToTop'
import { FormEvent, MouseEvent, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { FormEvent, MouseEvent, useEffect, useMemo, useState } from 'react'
import { Input } from '../layout/Input'
import * as styles from './Filters.css' import * as styles from './Filters.css'
import { TraitsHeader } from './TraitsHeader'
const TraitItem = ({ const TraitItem = ({
trait, trait,
...@@ -68,8 +68,13 @@ const TraitItem = ({ ...@@ -68,8 +68,13 @@ const TraitItem = ({
justifyContent="space-between" justifyContent="space-between"
cursor="pointer" cursor="pointer"
paddingLeft="12" paddingLeft="12"
paddingRight="12" paddingRight="16"
style={{ paddingBottom: '21px', paddingTop: '21px', maxHeight: '44px' }} borderRadius="12"
style={{
paddingBottom: '22px',
paddingTop: '22px',
}}
maxHeight="44"
onMouseEnter={handleHover} onMouseEnter={handleHover}
onMouseLeave={handleHover} onMouseLeave={handleHover}
onClick={handleCheckbox} onClick={handleCheckbox}
...@@ -79,6 +84,7 @@ const TraitItem = ({ ...@@ -79,6 +84,7 @@ const TraitItem = ({
whiteSpace="nowrap" whiteSpace="nowrap"
textOverflow="ellipsis" textOverflow="ellipsis"
overflow="hidden" overflow="hidden"
style={{ minHeight: 15 }}
maxWidth={!showFullTraitName ? '160' : 'full'} maxWidth={!showFullTraitName ? '160' : 'full'}
onMouseOver={(e) => isEllipsisActive(e)} onMouseOver={(e) => isEllipsisActive(e)}
onMouseLeave={() => toggleShowFullTraitName({ shouldShow: false, trait_type: '', trait_value: '' })} onMouseLeave={() => toggleShowFullTraitName({ shouldShow: false, trait_type: '', trait_value: '' })}
...@@ -88,7 +94,7 @@ const TraitItem = ({ ...@@ -88,7 +94,7 @@ const TraitItem = ({
: trait.trait_value} : trait.trait_value}
</Box> </Box>
<Checkbox checked={isCheckboxSelected} hovered={hovered} onChange={handleCheckbox}> <Checkbox checked={isCheckboxSelected} hovered={hovered} onChange={handleCheckbox}>
<Box as="span" color="textSecondary" minWidth={'8'} paddingTop={'2'} paddingRight={'12'} position={'relative'}> <Box as="span" color="textTertiary" minWidth="8" paddingTop="2" paddingRight="12" position="relative">
{!showFullTraitName && trait.trait_count} {!showFullTraitName && trait.trait_count}
</Box> </Box>
</Checkbox> </Checkbox>
...@@ -96,83 +102,32 @@ const TraitItem = ({ ...@@ -96,83 +102,32 @@ const TraitItem = ({
) )
} }
export const TraitSelect = ({ traits, type, search }: { traits: Trait[]; type: string; search: string }) => { export const TraitSelect = ({ traits, type, index }: { traits: Trait[]; type: string; index: number }) => {
const debouncedSearch = useDebounce(search, 300)
const addTrait = useCollectionFilters((state) => state.addTrait) const addTrait = useCollectionFilters((state) => state.addTrait)
const removeTrait = useCollectionFilters((state) => state.removeTrait) const removeTrait = useCollectionFilters((state) => state.removeTrait)
const selectedTraits = useCollectionFilters((state) => state.traits) const selectedTraits = useCollectionFilters((state) => state.traits)
const [search, setSearch] = useState('')
const debouncedSearch = useDebounce(search, 300)
const [isOpen, setOpen] = useState( const searchedTraits = useMemo(
traits.some(({ trait_type, trait_value }) => { () => traits.filter((t) => t.trait_value.toString().toLowerCase().includes(debouncedSearch.toLowerCase())),
return selectedTraits.some((selectedTrait) => { [debouncedSearch, traits]
return selectedTrait.trait_type === trait_type && selectedTrait.trait_value === String(trait_value)
})
})
) )
const { isTypeIncluded, searchedTraits } = useMemo(() => { return traits.length ? (
const isTypeIncluded = type.includes(debouncedSearch) <TraitsHeader index={index} numTraits={traits.length} title={type}>
const searchedTraits = traits.filter( <Input
(t) => isTypeIncluded || t.trait_value.toString().toLowerCase().includes(debouncedSearch.toLowerCase()) value={search}
) onChange={(e: FormEvent<HTMLInputElement>) => setSearch(e.currentTarget.value)}
return { searchedTraits, isTypeIncluded } placeholder="Search"
}, [debouncedSearch, traits, type]) marginTop="8"
marginBottom="8"
useLayoutEffect(() => { autoComplete="off"
if (debouncedSearch && searchedTraits.length) { position="static"
setOpen(true) width="full"
return () => { />
setOpen(false) <Column className={styles.filterDropDowns} paddingLeft="0" paddingBottom="8">
} {searchedTraits.map((trait, index) => {
}
return
}, [searchedTraits, debouncedSearch, setOpen])
return searchedTraits.length || isTypeIncluded ? (
<Box
as="details"
className={clsx(subheadSmall, !isOpen && styles.rowHover, isOpen && styles.detailsOpen)}
borderRadius="12"
open={isOpen}
>
<Box
as="summary"
className={clsx(isOpen && styles.summaryOpen, isOpen ? styles.rowHoverOpen : styles.rowHover)}
display="flex"
paddingTop="8"
paddingRight="12"
paddingBottom="8"
paddingLeft="12"
justifyContent="space-between"
cursor="pointer"
alignItems="center"
onClick={(e) => {
e.preventDefault()
setOpen(!isOpen)
}}
>
{type}
<Box display="flex" alignItems="center">
<Box color="textSecondary" display="inline-block" marginRight="12">
{searchedTraits.length}
</Box>
<Box
color="textSecondary"
display="inline-block"
transition="250"
height="28"
width="28"
style={{
transform: `rotate(${isOpen ? 0 : 180}deg)`,
}}
>
<ChevronUpIcon />
</Box>
</Box>
</Box>
<Column className={styles.filterDropDowns} paddingLeft="0">
{searchedTraits.map((trait) => {
const isTraitSelected = selectedTraits.find( const isTraitSelected = selectedTraits.find(
({ trait_type, trait_value }) => ({ trait_type, trait_value }) =>
trait_type === trait.trait_type && String(trait_value) === String(trait.trait_value) trait_type === trait.trait_type && String(trait_value) === String(trait.trait_value)
...@@ -187,6 +142,6 @@ export const TraitSelect = ({ traits, type, search }: { traits: Trait[]; type: s ...@@ -187,6 +142,6 @@ export const TraitSelect = ({ traits, type, search }: { traits: Trait[]; type: s
) )
})} })}
</Column> </Column>
</Box> </TraitsHeader>
) : null ) : null
} }
import clsx from 'clsx'
import { Box } from 'nft/components/Box'
import * as styles from 'nft/components/collection/Filters.css'
import { ChevronUpIcon } from 'nft/components/icons'
import { subheadSmall } from 'nft/css/common.css'
import { TraitPosition, useTraitsOpen } from 'nft/hooks/useTraitsOpen'
import { ReactNode, useEffect, useState } from 'react'
interface TraitsHeaderProps {
title: string
children: ReactNode
numTraits?: number
index?: number
}
export const TraitsHeader = (props: TraitsHeaderProps) => {
const { children, index, title } = props
const [isOpen, setOpen] = useState(false)
const traitsOpen = useTraitsOpen((state) => state.traitsOpen)
const setTraitsOpen = useTraitsOpen((state) => state.setTraitsOpen)
const prevTraitIsOpen = index !== undefined ? traitsOpen[index - 1] : false
const showBorderTop = index !== TraitPosition.TRAIT_START_INDEX
useEffect(() => {
if (index !== undefined) {
setTraitsOpen(index, isOpen)
}
}, [isOpen, index, setTraitsOpen])
return (
<>
{showBorderTop && (
<Box
className={clsx(subheadSmall, !isOpen && styles.rowHover, styles.detailsOpen)}
opacity={!prevTraitIsOpen && isOpen && index !== 0 ? '1' : '0'}
marginTop={prevTraitIsOpen ? '0' : '8'}
/>
)}
<Box as="details" className={clsx(subheadSmall, !isOpen && styles.rowHover)} open={isOpen}>
<Box
as="summary"
className={`${styles.row} ${styles.rowHover}`}
onClick={(e) => {
e.preventDefault()
setOpen(!isOpen)
}}
>
{title}
<Box display="flex" alignItems="center">
<Box color="textTertiary" display="inline-block" marginRight="12">
{props.numTraits}
</Box>
<Box
className={styles.chevronContainer}
style={{
transform: `rotate(${isOpen ? 0 : 180}deg)`,
}}
>
<ChevronUpIcon className={styles.chevronIcon} />
</Box>
</Box>
</Box>
{children}
</Box>
</>
)
}
...@@ -6,6 +6,7 @@ import { ArrowsIcon, ChevronUpIcon, ReversedArrowsIcon } from 'nft/components/ic ...@@ -6,6 +6,7 @@ import { ArrowsIcon, ChevronUpIcon, ReversedArrowsIcon } from 'nft/components/ic
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 { useIsCollectionLoading } from 'nft/hooks'
import { useCollectionFilters } 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'
...@@ -26,13 +27,18 @@ export const SortDropdown = ({ ...@@ -26,13 +27,18 @@ export const SortDropdown = ({
top?: number top?: number
left?: number left?: number
}) => { }) => {
const sortBy = useCollectionFilters((state) => state.sortBy)
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(sortBy)
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading) const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
const [maxWidth, setMaxWidth] = useState(0) const [maxWidth, setMaxWidth] = useState(0)
useEffect(() => {
setSelectedIndex(sortBy)
}, [sortBy])
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
useOnClickOutside(ref, () => isOpen && toggleOpen()) useOnClickOutside(ref, () => isOpen && toggleOpen())
......
...@@ -35,13 +35,13 @@ export const checkMark = sprinkles({ ...@@ -35,13 +35,13 @@ export const checkMark = sprinkles({
display: 'none', display: 'none',
height: '24', height: '24',
width: '24', width: '24',
color: 'blue400', color: 'white',
}) })
export const checkMarkActive = style([ export const checkMarkActive = style([
sprinkles({ sprinkles({
display: 'inline-block', display: 'inline-block',
color: 'blue400', color: 'white',
position: 'absolute', position: 'absolute',
top: '0', top: '0',
right: '1', right: '1',
......
...@@ -24,8 +24,9 @@ export const Checkbox: React.FC<CheckboxProps> = ({ hovered, children, ...props ...@@ -24,8 +24,9 @@ export const Checkbox: React.FC<CheckboxProps> = ({ hovered, children, ...props
{children} {children}
<Box <Box
as="span" as="span"
borderColor={props.checked || hovered ? 'blue400' : 'grey400'} borderColor={props.checked || hovered ? 'accentAction' : 'grey400'}
className={styles.checkbox} className={styles.checkbox}
background={props.checked ? 'accentAction' : undefined}
// This element is purely decorative so // This element is purely decorative so
// we hide it for screen readers // we hide it for screen readers
aria-hidden="true" aria-hidden="true"
......
...@@ -28,6 +28,8 @@ export const NumericInput = forwardRef<HTMLInputElement, BoxProps>((props, ref) ...@@ -28,6 +28,8 @@ export const NumericInput = forwardRef<HTMLInputElement, BoxProps>((props, ref)
as="input" as="input"
autoComplete="off" autoComplete="off"
type="text" type="text"
borderColor={{ default: 'backgroundOutline', focus: 'textSecondary' }}
color={{ placeholder: 'textSecondary', default: 'textPrimary' }}
onInput={(v: FormEvent<HTMLInputElement>) => { onInput={(v: FormEvent<HTMLInputElement>) => {
if (v.currentTarget.value === '.') { if (v.currentTarget.value === '.') {
v.currentTarget.value = '0.' v.currentTarget.value = '0.'
......
...@@ -34,6 +34,7 @@ const themeContractValues = { ...@@ -34,6 +34,7 @@ const themeContractValues = {
elevation: '', elevation: '',
tooltip: '', tooltip: '',
deep: '', deep: '',
shallow: '',
}, },
} }
...@@ -77,6 +78,7 @@ const dimensions = { ...@@ -77,6 +78,7 @@ const dimensions = {
'276': '276px', '276': '276px',
'288': '288px', '288': '288px',
'292': '292px', '292': '292px',
'332': '332px',
'386': '386px', '386': '386px',
half: '50%', half: '50%',
full: '100%', full: '100%',
...@@ -178,7 +180,7 @@ export const vars = createGlobalTheme(':root', { ...@@ -178,7 +180,7 @@ export const vars = createGlobalTheme(':root', {
grey300: '#99A1BD', grey300: '#99A1BD',
grey200: '#B7BED4', grey200: '#B7BED4',
grey100: '#DDE3F7', grey100: '#DDE3F7',
grey50: '#EDEFF7', grey50: '#F5F6FC',
accentTextLightTertiary: 'rgba(255, 255, 255, 0.12)', accentTextLightTertiary: 'rgba(255, 255, 255, 0.12)',
outline: 'rgba(153, 161, 189, 0.24)', outline: 'rgba(153, 161, 189, 0.24)',
lightGrayOverlay: '#99A1BD14', lightGrayOverlay: '#99A1BD14',
...@@ -383,6 +385,7 @@ const unresponsiveProperties = defineProperties({ ...@@ -383,6 +385,7 @@ const unresponsiveProperties = defineProperties({
cursor: ['default', 'pointer', 'auto'], cursor: ['default', 'pointer', 'auto'],
borderStyle, borderStyle,
borderBottomStyle: borderStyle, borderBottomStyle: borderStyle,
borderTopStyle: borderStyle,
borderRadius: vars.radii, borderRadius: vars.radii,
borderTopLeftRadius: vars.radii, borderTopLeftRadius: vars.radii,
borderTopRightRadius: vars.radii, borderTopRightRadius: vars.radii,
...@@ -393,6 +396,7 @@ const unresponsiveProperties = defineProperties({ ...@@ -393,6 +396,7 @@ const unresponsiveProperties = defineProperties({
borderTop: vars.border, borderTop: vars.border,
borderWidth, borderWidth,
borderBottomWidth: borderWidth, borderBottomWidth: borderWidth,
borderTopWidth: borderWidth,
fontFamily: vars.fonts, fontFamily: vars.fonts,
overflow, overflow,
overflowX: overflow, overflowX: overflow,
......
import create from 'zustand'
import { devtools } from 'zustand/middleware'
interface PriceRangeProps {
priceRangeLow: string
setPriceRangeLow: (priceRangeLow: string) => void
priceRangeHigh: string
setPriceRangeHigh: (priceRangeHigh: string) => void
prevMinMax: Array<number>
setPrevMinMax: (prevMinMax: Array<number>) => void
}
export const usePriceRange = create<PriceRangeProps>()(
devtools(
(set) => ({
priceRangeLow: '',
setPriceRangeLow: (priceRangeLow: string) => {
set(() => {
return { priceRangeLow }
})
},
priceRangeHigh: '',
setPriceRangeHigh: (priceRangeHigh: string) => {
set(() => {
return { priceRangeHigh }
})
},
prevMinMax: [0, 100],
setPrevMinMax: (prevMinMax: Array<number>) => {
set(() => {
return { prevMinMax }
})
},
}),
{ name: 'usePriceRange' }
)
)
import create from 'zustand'
import { devtools } from 'zustand/middleware'
interface traitOpen {
[key: number]: boolean
}
interface TraitsOpenState {
traitsOpen: traitOpen
setTraitsOpen: (index: number, isOpen: boolean) => void
}
export enum TraitPosition {
MARKPLACE_INDEX = 0,
PRICE_RANGE_INDEX = 1,
TRAIT_START_INDEX = 2,
}
export const useTraitsOpen = create<TraitsOpenState>()(
devtools(
(set) => ({
traitsOpen: {},
setTraitsOpen: (index, isOpen) => {
set(({ traitsOpen }) => ({ traitsOpen: { ...traitsOpen, [index]: isOpen } }))
},
}),
{ name: 'useTraitsOpen' }
)
)
...@@ -96,12 +96,7 @@ const Collection = () => { ...@@ -96,12 +96,7 @@ const Collection = () => {
</Column> </Column>
<Row alignItems="flex-start" position="relative" paddingX="48"> <Row alignItems="flex-start" position="relative" paddingX="48">
<Box position="sticky" top="72" width="0"> <Box position="sticky" top="72" width="0">
{isFiltersExpanded && ( {isFiltersExpanded && <Filters traits={collectionStats?.traits ?? []} />}
<Filters
traitsByAmount={collectionStats?.numTraitsByAmount ?? []}
traits={collectionStats?.traits ?? []}
/>
)}
</Box> </Box>
{/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */} {/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */}
......
...@@ -32,5 +32,6 @@ export const darkTheme: Theme = { ...@@ -32,5 +32,6 @@ export const darkTheme: Theme = {
elevation: '0px 4px 16px rgba(70, 115, 250, 0.4)', elevation: '0px 4px 16px rgba(70, 115, 250, 0.4)',
tooltip: '0px 4px 16px rgba(255, 255, 255, 0.2)', tooltip: '0px 4px 16px rgba(255, 255, 255, 0.2)',
deep: '12px 16px 24px rgba(0, 0, 0, 0.24), 12px 8px 12px rgba(0, 0, 0, 0.24), 4px 4px 8px rgba(0, 0, 0, 0.32)', deep: '12px 16px 24px rgba(0, 0, 0, 0.24), 12px 8px 12px rgba(0, 0, 0, 0.24), 4px 4px 8px rgba(0, 0, 0, 0.32)',
shallow: '4px 4px 10px rgba(0, 0, 0, 0.24), 2px 2px 4px rgba(0, 0, 0, 0.12), 1px 2px 2px rgba(0, 0, 0, 0.12)',
}, },
} }
...@@ -32,5 +32,6 @@ export const lightTheme: Theme = { ...@@ -32,5 +32,6 @@ export const lightTheme: Theme = {
elevation: '0px 4px 16px rgba(70, 115, 250, 0.4)', elevation: '0px 4px 16px rgba(70, 115, 250, 0.4)',
tooltip: '0px 4px 16px rgba(10, 10, 59, 0.2)', tooltip: '0px 4px 16px rgba(10, 10, 59, 0.2)',
deep: '8px 12px 20px rgba(51, 53, 72, 0.04), 4px 6px 12px rgba(51, 53, 72, 0.02), 4px 4px 8px rgba(51, 53, 72, 0.04)', deep: '8px 12px 20px rgba(51, 53, 72, 0.04), 4px 6px 12px rgba(51, 53, 72, 0.02), 4px 4px 8px rgba(51, 53, 72, 0.04)',
shallow: '4px 4px 10px rgba(0, 0, 0, 0.24), 2px 2px 4px rgba(0, 0, 0, 0.12), 1px 2px 2px rgba(0, 0, 0, 0.12)',
}, },
} }
...@@ -3799,6 +3799,13 @@ ...@@ -3799,6 +3799,13 @@
"@types/history" "*" "@types/history" "*"
"@types/react" "*" "@types/react" "*"
"@types/react-slider@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/react-slider/-/react-slider-1.3.1.tgz#a3816989eb4fc172e7df316930f242fec90690fc"
integrity sha512-4X2yK7RyCIy643YCFL+bc6XNmcnBtt8n88uuyihvcn5G7Lut23eNQU3q3KmwF7MWIfKfsW5NxCjw0SeDZRtgaA==
dependencies:
"@types/react" "*"
"@types/react-table@^7.7.12": "@types/react-table@^7.7.12":
version "7.7.12" version "7.7.12"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.12.tgz#628011d3cb695b07c678704a61f2f1d5b8e567fd" resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.12.tgz#628011d3cb695b07c678704a61f2f1d5b8e567fd"
...@@ -14481,7 +14488,7 @@ prompts@2.4.0, prompts@^2.0.1: ...@@ -14481,7 +14488,7 @@ prompts@2.4.0, prompts@^2.0.1:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
...@@ -15009,6 +15016,13 @@ react-scripts@^4.0.3: ...@@ -15009,6 +15016,13 @@ react-scripts@^4.0.3:
optionalDependencies: optionalDependencies:
fsevents "^2.1.3" fsevents "^2.1.3"
react-slider@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-slider/-/react-slider-2.0.4.tgz#21c656ffabc3bb4481cf6b49e6d647baeda83572"
integrity sha512-sWwQD01n6v+MbeLCYthJGZPc0kzOyhQHyd0bSo0edg+IAxTVQmj3Oy4SBK65eX6gNwS9meUn6Z5sIBUVmwAd9g==
dependencies:
prop-types "^15.8.1"
react-spring@^9.5.5: react-spring@^9.5.5:
version "9.5.5" version "9.5.5"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-9.5.5.tgz#314009a65efc04d0ef157d3d60590dbb9de65f3c" resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-9.5.5.tgz#314009a65efc04d0ef157d3d60590dbb9de65f3c"
......
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