feat: Add sell page filters sidebar (#4630)
* working sell filters * split filters into own file * include new file * fix eslint warnings * update filter button param and fix rerender bug * de morgon's law * usecallback * move max_padding * extend htmlinputelement for checkbox * styles cleanup * simplify checkbox sprinkles * add null check to collectionfilteritem * remove x axis scrollbar on collections * update fitlerbutton logic * scrollbar width Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
parent
efaefe2e44
commit
49c5cbbf3b
@ -3,8 +3,9 @@ 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 } from 'nft/hooks'
|
import { useCollectionFilters, useWalletCollections } from 'nft/hooks'
|
||||||
import { putCommas } from 'nft/utils/putCommas'
|
import { putCommas } from 'nft/utils/putCommas'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
export const FilterButton = ({
|
export const FilterButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
@ -26,8 +27,13 @@ export const FilterButton = ({
|
|||||||
markets: state.markets,
|
markets: state.markets,
|
||||||
buyNow: state.buyNow,
|
buyNow: state.buyNow,
|
||||||
}))
|
}))
|
||||||
|
const collectionFilters = useWalletCollections((state) => state.collectionFilters)
|
||||||
|
const { pathname } = useLocation()
|
||||||
|
const isSellPage = pathname.startsWith('/nfts/sell')
|
||||||
|
|
||||||
const showFilterBadge = minPrice || maxPrice || minRarity || maxRarity || traits.length || markets.length || buyNow
|
const showFilterBadge = isSellPage
|
||||||
|
? collectionFilters.length > 0
|
||||||
|
: minPrice || maxPrice || minRarity || maxRarity || traits.length || markets.length || buyNow
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className={clsx(styles.filterButton, !isFiltersExpanded && styles.filterButtonExpanded)}
|
className={clsx(styles.filterButton, !isFiltersExpanded && styles.filterButtonExpanded)}
|
||||||
|
@ -12,19 +12,21 @@ const pathAnim = keyframes({
|
|||||||
const pathAnimCommonProps = {
|
const pathAnimCommonProps = {
|
||||||
animationDirection: 'alternate',
|
animationDirection: 'alternate',
|
||||||
animationTimingFunction: 'linear',
|
animationTimingFunction: 'linear',
|
||||||
animation: `0.5s infinite ${pathAnim}`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const path = style({
|
export const path = style({
|
||||||
selectors: {
|
selectors: {
|
||||||
'&:nth-child(1)': {
|
'&:nth-child(1)': {
|
||||||
|
animation: `0.5s infinite ${pathAnim}`,
|
||||||
...pathAnimCommonProps,
|
...pathAnimCommonProps,
|
||||||
},
|
},
|
||||||
'&:nth-child(2)': {
|
'&:nth-child(2)': {
|
||||||
|
animation: `0.5s infinite ${pathAnim}`,
|
||||||
animationDelay: '0.1s',
|
animationDelay: '0.1s',
|
||||||
...pathAnimCommonProps,
|
...pathAnimCommonProps,
|
||||||
},
|
},
|
||||||
'&:nth-child(3)': {
|
'&:nth-child(3)': {
|
||||||
|
animation: `0.5s infinite ${pathAnim}`,
|
||||||
animationDelay: '0.2s',
|
animationDelay: '0.2s',
|
||||||
...pathAnimCommonProps,
|
...pathAnimCommonProps,
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import { style } from '@vanilla-extract/css'
|
||||||
|
|
||||||
|
export const activeDropdown = style({
|
||||||
|
borderBottom: 'none',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const activeDropDownItems = style({
|
||||||
|
borderTop: 'none',
|
||||||
|
})
|
240
src/nft/components/common/SortDropdown/SortDropdown.tsx
Normal file
240
src/nft/components/common/SortDropdown/SortDropdown.tsx
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||||
|
import { Box } from 'nft/components/Box'
|
||||||
|
import { Row } from 'nft/components/Flex'
|
||||||
|
import { ArrowsIcon, ChevronUpIcon, ReversedArrowsIcon } from 'nft/components/icons'
|
||||||
|
import { buttonTextMedium } from 'nft/css/common.css'
|
||||||
|
import { themeVars } from 'nft/css/sprinkles.css'
|
||||||
|
import { DropDownOption } from 'nft/types'
|
||||||
|
import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import * as styles from './SortDropdown.css'
|
||||||
|
|
||||||
|
export const SortDropdown = ({
|
||||||
|
dropDownOptions,
|
||||||
|
inFilters,
|
||||||
|
mini,
|
||||||
|
miniPrompt,
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
}: {
|
||||||
|
dropDownOptions: DropDownOption[]
|
||||||
|
inFilters?: boolean
|
||||||
|
mini?: boolean
|
||||||
|
miniPrompt?: string
|
||||||
|
top?: number
|
||||||
|
left?: number
|
||||||
|
}) => {
|
||||||
|
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||||
|
const [isReversed, toggleReversed] = useReducer((s) => !s, false)
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||||
|
|
||||||
|
const [maxWidth, setMaxWidth] = useState(0)
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
useOnClickOutside(ref, () => isOpen && toggleOpen())
|
||||||
|
|
||||||
|
useEffect(() => setMaxWidth(0), [dropDownOptions])
|
||||||
|
|
||||||
|
const reversable = useMemo(
|
||||||
|
() => dropDownOptions[selectedIndex].reverseOnClick || dropDownOptions[selectedIndex].reverseIndex,
|
||||||
|
[selectedIndex, dropDownOptions]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
ref={ref}
|
||||||
|
transition="250"
|
||||||
|
borderRadius="12"
|
||||||
|
borderBottomLeftRadius={isOpen ? '0' : undefined}
|
||||||
|
borderBottomRightRadius={isOpen ? '0' : undefined}
|
||||||
|
height="44"
|
||||||
|
style={{ width: inFilters ? 'full' : mini ? 'min' : maxWidth ? maxWidth : '300px' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
as="button"
|
||||||
|
fontSize="14"
|
||||||
|
borderRadius="12"
|
||||||
|
borderStyle={isOpen && !mini ? 'solid' : 'none'}
|
||||||
|
background={mini ? 'none' : 'lightGray'}
|
||||||
|
borderColor="medGray"
|
||||||
|
borderWidth="1px"
|
||||||
|
borderBottomLeftRadius={isOpen ? '0' : undefined}
|
||||||
|
borderBottomRightRadius={isOpen ? '0' : undefined}
|
||||||
|
padding={inFilters ? '12' : mini ? '0' : '8'}
|
||||||
|
color="blackBlue"
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
width={inFilters ? 'full' : 'inherit'}
|
||||||
|
onClick={toggleOpen}
|
||||||
|
cursor="pointer"
|
||||||
|
className={clsx(isOpen && !mini && styles.activeDropdown)}
|
||||||
|
>
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
{!isOpen && reversable && (
|
||||||
|
<Row
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (dropDownOptions[selectedIndex].reverseOnClick) {
|
||||||
|
dropDownOptions[selectedIndex].reverseOnClick?.()
|
||||||
|
toggleReversed()
|
||||||
|
} else {
|
||||||
|
dropDownOptions[dropDownOptions[selectedIndex].reverseIndex ?? 1 - 1].onClick()
|
||||||
|
setSelectedIndex(dropDownOptions[selectedIndex].reverseIndex ?? 1 - 1)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{dropDownOptions[selectedIndex].reverseOnClick && (isReversed ? <ArrowsIcon /> : <ReversedArrowsIcon />)}
|
||||||
|
{dropDownOptions[selectedIndex].reverseIndex &&
|
||||||
|
(selectedIndex > (dropDownOptions[selectedIndex].reverseIndex ?? 1) - 1 ? (
|
||||||
|
<ArrowsIcon />
|
||||||
|
) : (
|
||||||
|
<ReversedArrowsIcon />
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
marginLeft={reversable ? '4' : '0'}
|
||||||
|
marginRight={mini ? '2' : '0'}
|
||||||
|
color="blackBlue"
|
||||||
|
className={buttonTextMedium}
|
||||||
|
>
|
||||||
|
{mini ? miniPrompt : isOpen ? 'Sort by' : dropDownOptions[selectedIndex].displayText}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<ChevronUpIcon
|
||||||
|
secondaryColor={mini ? themeVars.colors.blackBlue : undefined}
|
||||||
|
secondaryWidth={mini ? '20' : undefined}
|
||||||
|
secondaryHeight={mini ? '20' : undefined}
|
||||||
|
style={{
|
||||||
|
transform: isOpen ? '' : 'rotate(180deg)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
zIndex="2"
|
||||||
|
width={inFilters ? 'auto' : 'inherit'}
|
||||||
|
right={inFilters ? '16' : 'auto'}
|
||||||
|
paddingBottom="8"
|
||||||
|
fontSize="14"
|
||||||
|
background="lightGray"
|
||||||
|
borderStyle="solid"
|
||||||
|
borderColor="medGray"
|
||||||
|
borderWidth="1px"
|
||||||
|
borderRadius="8"
|
||||||
|
borderTopLeftRadius={mini ? undefined : '0'}
|
||||||
|
borderTopRightRadius={mini ? undefined : '0'}
|
||||||
|
overflowY="hidden"
|
||||||
|
transition="250"
|
||||||
|
display={isOpen || !maxWidth ? 'block' : 'none'}
|
||||||
|
visibility={maxWidth ? 'visible' : 'hidden'}
|
||||||
|
marginTop={mini ? '12' : '0'}
|
||||||
|
className={clsx(!mini && styles.activeDropDownItems)}
|
||||||
|
style={{
|
||||||
|
top: top ? `${top}px` : 'inherit',
|
||||||
|
left: inFilters ? '16px' : left ? `${left}px` : 'inherit',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!maxWidth
|
||||||
|
? [
|
||||||
|
dropDownOptions.reduce((acc, curr) => {
|
||||||
|
return curr.displayText.length >= acc.displayText.length ? curr : acc
|
||||||
|
}, dropDownOptions[0]),
|
||||||
|
].map((option, index) => {
|
||||||
|
return <LargestItem key={index} option={option} index={index} setMaxWidth={setMaxWidth} />
|
||||||
|
})
|
||||||
|
: isOpen &&
|
||||||
|
dropDownOptions.map((option, index) => {
|
||||||
|
return (
|
||||||
|
<DropDownItem
|
||||||
|
key={index}
|
||||||
|
option={option}
|
||||||
|
index={index}
|
||||||
|
mini={mini}
|
||||||
|
onClick={() => {
|
||||||
|
dropDownOptions[index].onClick()
|
||||||
|
setSelectedIndex(index)
|
||||||
|
toggleOpen()
|
||||||
|
isReversed && toggleReversed()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropDownItem = ({
|
||||||
|
option,
|
||||||
|
index,
|
||||||
|
onClick,
|
||||||
|
mini,
|
||||||
|
}: {
|
||||||
|
option: DropDownOption
|
||||||
|
index: number
|
||||||
|
onClick?: () => void
|
||||||
|
mini?: boolean
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="button"
|
||||||
|
border="none"
|
||||||
|
key={index}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
paddingTop="10"
|
||||||
|
paddingBottom="10"
|
||||||
|
paddingLeft="12"
|
||||||
|
paddingRight={mini ? '20' : '0'}
|
||||||
|
width="full"
|
||||||
|
background={{
|
||||||
|
default: 'lightGray',
|
||||||
|
hover: 'lightGrayButton',
|
||||||
|
}}
|
||||||
|
color="blackBlue"
|
||||||
|
onClick={onClick}
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
{option.icon && (
|
||||||
|
<Box width="28" height="28">
|
||||||
|
{option.icon}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box marginLeft="8" className={buttonTextMedium}>
|
||||||
|
{option.displayText}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_PADDING = 52
|
||||||
|
|
||||||
|
const LargestItem = ({
|
||||||
|
option,
|
||||||
|
index,
|
||||||
|
setMaxWidth,
|
||||||
|
}: {
|
||||||
|
option: DropDownOption
|
||||||
|
index: number
|
||||||
|
setMaxWidth: (width: number) => void
|
||||||
|
}) => {
|
||||||
|
const maxWidthRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (maxWidthRef && maxWidthRef.current) {
|
||||||
|
setMaxWidth(Math.ceil(maxWidthRef.current.getBoundingClientRect().width) + MAX_PADDING)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={index} position="absolute" ref={maxWidthRef}>
|
||||||
|
<DropDownItem option={option} index={index} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
1
src/nft/components/common/SortDropdown/index.ts
Normal file
1
src/nft/components/common/SortDropdown/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './SortDropdown'
|
49
src/nft/components/layout/Checkbox.css.ts
Normal file
49
src/nft/components/layout/Checkbox.css.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { style } from '@vanilla-extract/css'
|
||||||
|
import { sprinkles } from 'nft/css/sprinkles.css'
|
||||||
|
|
||||||
|
export const input = style([
|
||||||
|
sprinkles({ position: 'absolute' }),
|
||||||
|
{
|
||||||
|
top: '-24px',
|
||||||
|
selectors: {
|
||||||
|
'&[type="checkbox"]': {
|
||||||
|
clip: 'rect(0 0 0 0)',
|
||||||
|
clipPath: 'inset(50%)',
|
||||||
|
height: '1px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'absolute',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
width: '1px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
export const checkbox = style([
|
||||||
|
sprinkles({
|
||||||
|
display: 'inline-block',
|
||||||
|
marginRight: '1',
|
||||||
|
borderRadius: '4',
|
||||||
|
height: '24',
|
||||||
|
width: '24',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: '2px',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
export const checkMark = sprinkles({
|
||||||
|
display: 'none',
|
||||||
|
height: '24',
|
||||||
|
width: '24',
|
||||||
|
color: 'blue400',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const checkMarkActive = style([
|
||||||
|
sprinkles({
|
||||||
|
display: 'inline-block',
|
||||||
|
color: 'blue400',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0',
|
||||||
|
right: '1',
|
||||||
|
}),
|
||||||
|
])
|
37
src/nft/components/layout/Checkbox.tsx
Normal file
37
src/nft/components/layout/Checkbox.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
import { Box } from 'nft/components/Box'
|
||||||
|
import { ApprovedCheckmarkIcon } from 'nft/components/icons'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import * as styles from './Checkbox.css'
|
||||||
|
|
||||||
|
interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
hovered: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox: React.FC<CheckboxProps> = ({ hovered, children, ...props }: CheckboxProps) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="label"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
position="relative"
|
||||||
|
overflow="hidden"
|
||||||
|
cursor="pointer"
|
||||||
|
lineHeight="1"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<Box
|
||||||
|
as="span"
|
||||||
|
borderColor={props.checked || hovered ? 'blue400' : 'grey400'}
|
||||||
|
className={styles.checkbox}
|
||||||
|
// This element is purely decorative so
|
||||||
|
// we hide it for screen readers
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<input {...props} className={styles.input} type="checkbox" />
|
||||||
|
<ApprovedCheckmarkIcon className={clsx(styles.checkMark, props.checked && styles.checkMarkActive)} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
239
src/nft/components/sell/select/FilterSidebar.tsx
Normal file
239
src/nft/components/sell/select/FilterSidebar.tsx
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import { AnimatedBox, Box } from 'nft/components/Box'
|
||||||
|
import { Column, Row } from 'nft/components/Flex'
|
||||||
|
import { XMarkIcon } from 'nft/components/icons'
|
||||||
|
import { Checkbox } from 'nft/components/layout/Checkbox'
|
||||||
|
import { buttonTextSmall, headlineSmall } from 'nft/css/common.css'
|
||||||
|
import { themeVars } from 'nft/css/sprinkles.css'
|
||||||
|
import { useFiltersExpanded, useIsMobile, useWalletCollections } from 'nft/hooks'
|
||||||
|
import { WalletCollection } from 'nft/types'
|
||||||
|
import { Dispatch, FormEvent, SetStateAction, useCallback, useEffect, useReducer, useState } from 'react'
|
||||||
|
import { useSpring } from 'react-spring/web'
|
||||||
|
|
||||||
|
import * as styles from './SelectPage.css'
|
||||||
|
|
||||||
|
export const FilterSidebar = ({ SortDropdown }: { SortDropdown: () => JSX.Element }) => {
|
||||||
|
const collectionFilters = useWalletCollections((state) => state.collectionFilters)
|
||||||
|
const setCollectionFilters = useWalletCollections((state) => state.setCollectionFilters)
|
||||||
|
|
||||||
|
const walletCollections = useWalletCollections((state) => state.walletCollections)
|
||||||
|
const listFilter = useWalletCollections((state) => state.listFilter)
|
||||||
|
const setListFilter = useWalletCollections((state) => state.setListFilter)
|
||||||
|
|
||||||
|
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
|
const { sidebarX } = useSpring({
|
||||||
|
sidebarX: isFiltersExpanded ? 0 : -360,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
<AnimatedBox
|
||||||
|
position={{ sm: 'fixed', md: 'sticky' }}
|
||||||
|
top={{ sm: '40', md: 'unset' }}
|
||||||
|
left={{ sm: '0', md: 'unset' }}
|
||||||
|
width={{ sm: 'full', md: 'auto' }}
|
||||||
|
height={{ sm: 'full', md: 'auto' }}
|
||||||
|
zIndex={{ sm: '3', md: 'auto' }}
|
||||||
|
display={isFiltersExpanded ? 'flex' : 'none'}
|
||||||
|
style={{ transform: sidebarX.interpolate((x) => `translateX(${x}px)`) }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
paddingTop={{ sm: '24', md: '0' }}
|
||||||
|
paddingLeft={{ sm: '16', md: '0' }}
|
||||||
|
paddingRight="16"
|
||||||
|
width={{ sm: 'full', md: 'auto' }}
|
||||||
|
>
|
||||||
|
<Row width="full" justifyContent="space-between">
|
||||||
|
<Row as="span" className={headlineSmall} color="blackBlue">
|
||||||
|
Filters
|
||||||
|
</Row>
|
||||||
|
{isMobile && (
|
||||||
|
<Box
|
||||||
|
as="button"
|
||||||
|
border="none"
|
||||||
|
backgroundColor="transparent"
|
||||||
|
color="darkGray"
|
||||||
|
onClick={() => setFiltersExpanded(false)}
|
||||||
|
>
|
||||||
|
<XMarkIcon fill={themeVars.colors.blackBlue} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
<Row marginTop="14" marginLeft="2" gap="6" flexWrap="wrap" width="276">
|
||||||
|
<ListStatusFilterButtons listFilter={listFilter} setListFilter={setListFilter} />
|
||||||
|
</Row>
|
||||||
|
{isMobile && (
|
||||||
|
<Box paddingTop="20">
|
||||||
|
<SortDropdown />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<CollectionSelect
|
||||||
|
collections={walletCollections}
|
||||||
|
collectionFilters={collectionFilters}
|
||||||
|
setCollectionFilters={setCollectionFilters}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</AnimatedBox>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionSelect = ({
|
||||||
|
collections,
|
||||||
|
collectionFilters,
|
||||||
|
setCollectionFilters,
|
||||||
|
}: {
|
||||||
|
collections: WalletCollection[]
|
||||||
|
collectionFilters: Array<string>
|
||||||
|
setCollectionFilters: (address: string) => void
|
||||||
|
}) => {
|
||||||
|
const [collectionSearchText, setCollectionSearchText] = useState('')
|
||||||
|
const [displayCollections, setDisplayCollections] = useState(collections)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (collectionSearchText) {
|
||||||
|
const filtered = collections.filter((collection) =>
|
||||||
|
collection.name?.toLowerCase().includes(collectionSearchText.toLowerCase())
|
||||||
|
)
|
||||||
|
setDisplayCollections(filtered)
|
||||||
|
} else {
|
||||||
|
setDisplayCollections(collections)
|
||||||
|
}
|
||||||
|
}, [collectionSearchText, collections])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box className={headlineSmall} marginTop="20" marginBottom="12">
|
||||||
|
Collections
|
||||||
|
</Box>
|
||||||
|
<Box paddingBottom="12" paddingTop="0" borderRadius="8">
|
||||||
|
<Column as="ul" paddingLeft="0" gap="10" style={{ maxHeight: '508px' }}>
|
||||||
|
<CollectionFilterSearch
|
||||||
|
collectionSearchText={collectionSearchText}
|
||||||
|
setCollectionSearchText={setCollectionSearchText}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
background="lightGray"
|
||||||
|
borderRadius="12"
|
||||||
|
paddingTop="8"
|
||||||
|
paddingBottom="8"
|
||||||
|
overflowY="scroll"
|
||||||
|
style={{ scrollbarWidth: 'none' }}
|
||||||
|
>
|
||||||
|
{displayCollections?.map((collection, index) => (
|
||||||
|
<CollectionItem
|
||||||
|
key={index}
|
||||||
|
collection={collection}
|
||||||
|
collectionFilters={collectionFilters}
|
||||||
|
setCollectionFilters={setCollectionFilters}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Column>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionFilterSearch = ({
|
||||||
|
collectionSearchText,
|
||||||
|
setCollectionSearchText,
|
||||||
|
}: {
|
||||||
|
collectionSearchText: string
|
||||||
|
setCollectionSearchText: Dispatch<SetStateAction<string>>
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="input"
|
||||||
|
borderColor={{ default: 'medGray', focus: 'genieBlue' }}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderStyle="solid"
|
||||||
|
borderRadius="8"
|
||||||
|
padding="12"
|
||||||
|
marginLeft="0"
|
||||||
|
marginBottom="24"
|
||||||
|
backgroundColor="white"
|
||||||
|
fontSize="14"
|
||||||
|
color={{ placeholder: 'darkGray', default: 'blackBlue' }}
|
||||||
|
placeholder="Search collections"
|
||||||
|
value={collectionSearchText}
|
||||||
|
onChange={(e: FormEvent<HTMLInputElement>) => setCollectionSearchText(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionItem = ({
|
||||||
|
collection,
|
||||||
|
collectionFilters,
|
||||||
|
setCollectionFilters,
|
||||||
|
}: {
|
||||||
|
collection: WalletCollection
|
||||||
|
collectionFilters: Array<string>
|
||||||
|
setCollectionFilters: (address: string) => void
|
||||||
|
}) => {
|
||||||
|
const [isCheckboxSelected, setCheckboxSelected] = useState(false)
|
||||||
|
const [hovered, toggleHovered] = useReducer((state) => {
|
||||||
|
return !state
|
||||||
|
}, false)
|
||||||
|
const isChecked = useCallback(
|
||||||
|
(address: string) => {
|
||||||
|
return collectionFilters.some((collection) => collection === address)
|
||||||
|
},
|
||||||
|
[collectionFilters]
|
||||||
|
)
|
||||||
|
const handleCheckbox = () => {
|
||||||
|
setCheckboxSelected(!isCheckboxSelected)
|
||||||
|
setCollectionFilters(collection.address)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
cursor="pointer"
|
||||||
|
paddingRight="14"
|
||||||
|
height="44"
|
||||||
|
as="li"
|
||||||
|
background={hovered ? 'medGray' : undefined}
|
||||||
|
onMouseEnter={toggleHovered}
|
||||||
|
onMouseLeave={toggleHovered}
|
||||||
|
onClick={handleCheckbox}
|
||||||
|
>
|
||||||
|
<Box as="img" borderRadius="round" marginLeft="16" width="20" height="20" src={collection.image} />
|
||||||
|
<Box as="span" marginLeft="6" marginRight="auto" className={styles.collectionName}>
|
||||||
|
{collection.name}{' '}
|
||||||
|
</Box>
|
||||||
|
<Checkbox checked={isChecked(collection.address)} hovered={hovered} onChange={handleCheckbox}>
|
||||||
|
<Box as="span" color="darkGray" marginRight="12" marginLeft="auto">
|
||||||
|
{collection.count}
|
||||||
|
</Box>
|
||||||
|
</Checkbox>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusArray = ['All', 'Unlisted', 'Listed']
|
||||||
|
|
||||||
|
const ListStatusFilterButtons = ({
|
||||||
|
listFilter,
|
||||||
|
setListFilter,
|
||||||
|
}: {
|
||||||
|
listFilter: string
|
||||||
|
setListFilter: (value: string) => void
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{statusArray.map((value, index) => (
|
||||||
|
<Row
|
||||||
|
key={index}
|
||||||
|
borderRadius="12"
|
||||||
|
backgroundColor="medGray"
|
||||||
|
height="44"
|
||||||
|
className={value === listFilter ? styles.buttonSelected : null}
|
||||||
|
onClick={() => setListFilter(value)}
|
||||||
|
width="max"
|
||||||
|
padding="14"
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
<Box className={buttonTextSmall}>{value}</Box>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,20 +1,49 @@
|
|||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { AnimatedBox, Box } from 'nft/components/Box'
|
import { AnimatedBox, Box } from 'nft/components/Box'
|
||||||
import { assetList } from 'nft/components/collection/CollectionNfts.css'
|
import { assetList } from 'nft/components/collection/CollectionNfts.css'
|
||||||
|
import { FilterButton } from 'nft/components/collection/FilterButton'
|
||||||
import { LoadingSparkle } from 'nft/components/common/Loading/LoadingSparkle'
|
import { LoadingSparkle } from 'nft/components/common/Loading/LoadingSparkle'
|
||||||
|
import { SortDropdown } from 'nft/components/common/SortDropdown'
|
||||||
import { Center, Column, Row } from 'nft/components/Flex'
|
import { Center, Column, Row } from 'nft/components/Flex'
|
||||||
import { VerifiedIcon } from 'nft/components/icons'
|
import {
|
||||||
|
BagFillIcon,
|
||||||
|
ClockIconFilled,
|
||||||
|
CrossIcon,
|
||||||
|
NonRarityIconFilled,
|
||||||
|
PaintPaletteIconFilled,
|
||||||
|
TagFillIcon,
|
||||||
|
VerifiedIcon,
|
||||||
|
} from 'nft/components/icons'
|
||||||
|
import { FilterSidebar } from 'nft/components/sell/select/FilterSidebar'
|
||||||
import { subhead, subheadSmall } from 'nft/css/common.css'
|
import { subhead, subheadSmall } from 'nft/css/common.css'
|
||||||
import { useBag, useIsMobile, useSellAsset, useSellPageState, useWalletBalance, useWalletCollections } from 'nft/hooks'
|
import { vars } from 'nft/css/sprinkles.css'
|
||||||
|
import {
|
||||||
|
useBag,
|
||||||
|
useFiltersExpanded,
|
||||||
|
useIsMobile,
|
||||||
|
useSellAsset,
|
||||||
|
useSellPageState,
|
||||||
|
useWalletBalance,
|
||||||
|
useWalletCollections,
|
||||||
|
} from 'nft/hooks'
|
||||||
import { fetchMultipleCollectionStats, fetchWalletAssets, OSCollectionsFetcher } from 'nft/queries'
|
import { fetchMultipleCollectionStats, fetchWalletAssets, OSCollectionsFetcher } from 'nft/queries'
|
||||||
import { SellPageStateType, WalletAsset } from 'nft/types'
|
import { DropDownOption, SellPageStateType, WalletAsset, WalletCollection } from 'nft/types'
|
||||||
import { Dispatch, FormEvent, SetStateAction, useEffect, useMemo, useReducer, useState } from 'react'
|
import { Dispatch, FormEvent, SetStateAction, useEffect, useMemo, useReducer, useState } from 'react'
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
import { useInfiniteQuery, useQuery } from 'react-query'
|
import { useInfiniteQuery, useQuery } from 'react-query'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
import { useSpring } from 'react-spring/web'
|
||||||
|
|
||||||
import * as styles from './SelectPage.css'
|
import * as styles from './SelectPage.css'
|
||||||
|
|
||||||
|
enum SortBy {
|
||||||
|
FloorPrice,
|
||||||
|
LastPrice,
|
||||||
|
DateAcquired,
|
||||||
|
DateCreated,
|
||||||
|
DateListed,
|
||||||
|
}
|
||||||
|
|
||||||
const formatEth = (price: number) => {
|
const formatEth = (price: number) => {
|
||||||
if (price > 1000000) {
|
if (price > 1000000) {
|
||||||
return `${Math.round(price / 1000000)}M`
|
return `${Math.round(price / 1000000)}M`
|
||||||
@ -32,6 +61,8 @@ function roundFloorPrice(price?: number, n?: number) {
|
|||||||
export const SelectPage = () => {
|
export const SelectPage = () => {
|
||||||
const { address } = useWalletBalance()
|
const { address } = useWalletBalance()
|
||||||
const collectionFilters = useWalletCollections((state) => state.collectionFilters)
|
const collectionFilters = useWalletCollections((state) => state.collectionFilters)
|
||||||
|
const setCollectionFilters = useWalletCollections((state) => state.setCollectionFilters)
|
||||||
|
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||||
|
|
||||||
const { data: ownerCollections } = useQuery(
|
const { data: ownerCollections } = useQuery(
|
||||||
['ownerCollections', address],
|
['ownerCollections', address],
|
||||||
@ -79,11 +110,17 @@ export const SelectPage = () => {
|
|||||||
const setWalletAssets = useWalletCollections((state) => state.setWalletAssets)
|
const setWalletAssets = useWalletCollections((state) => state.setWalletAssets)
|
||||||
const displayAssets = useWalletCollections((state) => state.displayAssets)
|
const displayAssets = useWalletCollections((state) => state.displayAssets)
|
||||||
const setDisplayAssets = useWalletCollections((state) => state.setDisplayAssets)
|
const setDisplayAssets = useWalletCollections((state) => state.setDisplayAssets)
|
||||||
|
const walletCollections = useWalletCollections((state) => state.walletCollections)
|
||||||
const setWalletCollections = useWalletCollections((state) => state.setWalletCollections)
|
const setWalletCollections = useWalletCollections((state) => state.setWalletCollections)
|
||||||
|
const listFilter = useWalletCollections((state) => state.listFilter)
|
||||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||||
const reset = useSellAsset((state) => state.reset)
|
const reset = useSellAsset((state) => state.reset)
|
||||||
const setSellPageState = useSellPageState((state) => state.setSellPageState)
|
const setSellPageState = useSellPageState((state) => state.setSellPageState)
|
||||||
|
const [sortBy, setSortBy] = useState(SortBy.DateAcquired)
|
||||||
|
const [orderByASC, setOrderBy] = useState(true)
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
|
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setWalletAssets(ownerAssets?.flat() ?? [])
|
setWalletAssets(ownerAssets?.flat() ?? [])
|
||||||
@ -93,8 +130,6 @@ export const SelectPage = () => {
|
|||||||
ownerCollections && setWalletCollections(ownerCollections)
|
ownerCollections && setWalletCollections(ownerCollections)
|
||||||
}, [ownerCollections, setWalletCollections])
|
}, [ownerCollections, setWalletCollections])
|
||||||
|
|
||||||
const listFilter = useWalletCollections((state) => state.listFilter)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
const filtered = walletAssets.filter((asset) => asset.name?.toLowerCase().includes(searchText.toLowerCase()))
|
const filtered = walletAssets.filter((asset) => asset.name?.toLowerCase().includes(searchText.toLowerCase()))
|
||||||
@ -115,47 +150,169 @@ export const SelectPage = () => {
|
|||||||
}
|
}
|
||||||
}, [collectionStats, ownerCollections, setWalletCollections])
|
}, [collectionStats, ownerCollections, setWalletCollections])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sorted = displayAssets && [...displayAssets]
|
||||||
|
if (sortBy === SortBy.FloorPrice && orderByASC) sorted?.sort((a, b) => (b.floorPrice || 0) - (a.floorPrice || 0))
|
||||||
|
else if (sortBy === SortBy.FloorPrice && !orderByASC)
|
||||||
|
sorted?.sort((a, b) => (a.floorPrice || 0) - (b.floorPrice || 0))
|
||||||
|
else if (sortBy === SortBy.LastPrice && orderByASC) sorted?.sort((a, b) => b.lastPrice - a.lastPrice)
|
||||||
|
else if (sortBy === SortBy.LastPrice && !orderByASC) sorted?.sort((a, b) => a.lastPrice - b.lastPrice)
|
||||||
|
else if (sortBy === SortBy.DateCreated && orderByASC)
|
||||||
|
sorted?.sort(
|
||||||
|
(a, b) => new Date(a.asset_contract.created_date).getTime() - new Date(b.asset_contract.created_date).getTime()
|
||||||
|
)
|
||||||
|
else if (sortBy === SortBy.DateCreated && !orderByASC)
|
||||||
|
sorted?.sort(
|
||||||
|
(a, b) => new Date(b.asset_contract.created_date).getTime() - new Date(a.asset_contract.created_date).getTime()
|
||||||
|
)
|
||||||
|
else if (sortBy === SortBy.DateAcquired && orderByASC)
|
||||||
|
sorted?.sort((a, b) => new Date(a.date_acquired).getTime() - new Date(b.date_acquired).getTime())
|
||||||
|
else if (sortBy === SortBy.DateAcquired && !orderByASC)
|
||||||
|
sorted?.sort((a, b) => new Date(b.date_acquired).getTime() - new Date(a.date_acquired).getTime())
|
||||||
|
else if (sortBy === SortBy.DateListed && orderByASC) sorted?.sort((a, b) => +b.listing_date - +a.listing_date)
|
||||||
|
else if (sortBy === SortBy.DateListed && !orderByASC) sorted?.sort((a, b) => +a.listing_date - +b.listing_date)
|
||||||
|
setDisplayAssets(sorted, listFilter)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [sortBy, orderByASC, listFilter])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ownerCollections?.length && collectionStats?.length) {
|
||||||
|
const ownerCollectionsCopy = [...ownerCollections]
|
||||||
|
for (const collection of ownerCollectionsCopy) {
|
||||||
|
const floorPrice = collectionStats.find((stat) => stat.address === collection.address)?.floorPrice
|
||||||
|
collection.floorPrice = floorPrice ? Math.round(floorPrice * 1000 + Number.EPSILON) / 1000 : 0 //round to at most 3 digits
|
||||||
|
}
|
||||||
|
setWalletCollections(ownerCollectionsCopy)
|
||||||
|
}
|
||||||
|
}, [collectionStats, ownerCollections, setWalletCollections])
|
||||||
|
|
||||||
|
const { gridX, gridWidthOffset } = useSpring({
|
||||||
|
gridX: isFiltersExpanded ? 300 : -16,
|
||||||
|
gridWidthOffset: isFiltersExpanded ? 300 /* right padding */ : 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortDropDownOptions: DropDownOption[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
displayText: 'Floor price',
|
||||||
|
onClick: () => {
|
||||||
|
setOrderBy(false)
|
||||||
|
setSortBy(SortBy.FloorPrice)
|
||||||
|
},
|
||||||
|
icon: <NonRarityIconFilled width="28" height="28" color={vars.color.blue400} />,
|
||||||
|
reverseOnClick: () => setOrderBy(!orderByASC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayText: 'Last price',
|
||||||
|
onClick: () => {
|
||||||
|
setOrderBy(false)
|
||||||
|
setSortBy(SortBy.LastPrice)
|
||||||
|
},
|
||||||
|
icon: <ClockIconFilled width="28" height="28" />,
|
||||||
|
reverseOnClick: () => setOrderBy(!orderByASC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayText: 'Date acquired',
|
||||||
|
onClick: () => {
|
||||||
|
setOrderBy(false)
|
||||||
|
setSortBy(SortBy.DateAcquired)
|
||||||
|
},
|
||||||
|
icon: <BagFillIcon width="28" height="28" color={vars.color.blue400} />,
|
||||||
|
reverseOnClick: () => setOrderBy(!orderByASC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayText: 'Date created',
|
||||||
|
onClick: () => {
|
||||||
|
setOrderBy(false)
|
||||||
|
setSortBy(SortBy.DateCreated)
|
||||||
|
},
|
||||||
|
icon: <PaintPaletteIconFilled width="28" height="28" color={vars.color.blue400} />,
|
||||||
|
reverseOnClick: () => setOrderBy(!orderByASC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayText: 'Date listed',
|
||||||
|
onClick: () => {
|
||||||
|
setOrderBy(false)
|
||||||
|
setSortBy(SortBy.DateListed)
|
||||||
|
},
|
||||||
|
icon: <TagFillIcon width="28" height="28" color={vars.color.blue400} />,
|
||||||
|
reverseOnClick: () => setOrderBy(!orderByASC),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[orderByASC]
|
||||||
|
)
|
||||||
|
|
||||||
|
const SortWalletAssetsDropdown = () => <SortDropdown dropDownOptions={sortDropDownOptions} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Column style is temporary while we move over the filters bar that adjust width
|
<Column width="full">
|
||||||
<Column style={{ width: 'calc(100vw - 32px)' }}>
|
|
||||||
<Row
|
<Row
|
||||||
alignItems="flex-start"
|
alignItems="flex-start"
|
||||||
position="relative"
|
position="relative"
|
||||||
paddingLeft={{ sm: '0', md: '52' }}
|
paddingLeft={{ sm: '16', md: '52' }}
|
||||||
paddingRight={{ sm: '0', md: '72' }}
|
paddingRight={{ sm: '0', md: '72' }}
|
||||||
paddingTop={{ sm: '16', md: '40' }}
|
paddingTop={{ sm: '16', md: '40' }}
|
||||||
>
|
>
|
||||||
<AnimatedBox paddingX="16" flexShrink="0" width="full">
|
<FilterSidebar SortDropdown={SortWalletAssetsDropdown} />
|
||||||
<Row gap="8" flexWrap="nowrap">
|
|
||||||
<CollectionSearch searchText={searchText} setSearchText={setSearchText} />
|
{(!isMobile || !isFiltersExpanded) && (
|
||||||
<SelectAllButton />
|
// @ts-ignore
|
||||||
</Row>
|
<AnimatedBox
|
||||||
<InfiniteScroll
|
paddingLeft={isFiltersExpanded ? '24' : '16'}
|
||||||
next={fetchNextPage}
|
flexShrink="0"
|
||||||
hasMore={hasNextPage ?? false}
|
style={{
|
||||||
loader={
|
transform: gridX.interpolate(
|
||||||
hasNextPage ? (
|
(x) => `translate(${Number(x) - (!isMobile && isFiltersExpanded ? 300 : 0)}px)`
|
||||||
<Center>
|
),
|
||||||
<LoadingSparkle />
|
width: gridWidthOffset.interpolate((x) => `calc(100% - ${x}px)`),
|
||||||
</Center>
|
}}
|
||||||
) : null
|
|
||||||
}
|
|
||||||
dataLength={displayAssets.length}
|
|
||||||
style={{ overflow: 'unset' }}
|
|
||||||
>
|
>
|
||||||
<div className={assetList}>
|
<Row gap="8" flexWrap="nowrap">
|
||||||
{displayAssets && displayAssets.length
|
<FilterButton
|
||||||
? displayAssets.map((asset, index) => <WalletAssetDisplay asset={asset} key={index} />)
|
isMobile={isMobile}
|
||||||
: null}
|
isFiltersExpanded={isFiltersExpanded}
|
||||||
</div>
|
results={displayAssets.length}
|
||||||
</InfiniteScroll>
|
onClick={() => setFiltersExpanded(!isFiltersExpanded)}
|
||||||
</AnimatedBox>
|
/>
|
||||||
|
{!isMobile && <SortDropdown dropDownOptions={sortDropDownOptions} />}
|
||||||
|
<CollectionSearch searchText={searchText} setSearchText={setSearchText} />
|
||||||
|
<SelectAllButton />
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<CollectionFiltersRow
|
||||||
|
collections={walletCollections}
|
||||||
|
collectionFilters={collectionFilters}
|
||||||
|
setCollectionFilters={setCollectionFilters}
|
||||||
|
clearCollectionFilters={clearCollectionFilters}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<InfiniteScroll
|
||||||
|
next={fetchNextPage}
|
||||||
|
hasMore={hasNextPage ?? false}
|
||||||
|
loader={
|
||||||
|
hasNextPage ? (
|
||||||
|
<Center>
|
||||||
|
<LoadingSparkle />
|
||||||
|
</Center>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
dataLength={displayAssets.length}
|
||||||
|
style={{ overflow: 'unset' }}
|
||||||
|
>
|
||||||
|
<div className={assetList}>
|
||||||
|
{displayAssets && displayAssets.length
|
||||||
|
? displayAssets.map((asset, index) => <WalletAssetDisplay asset={asset} key={index} />)
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</InfiniteScroll>
|
||||||
|
</AnimatedBox>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
{sellAssets.length > 0 && (
|
{sellAssets.length > 0 && (
|
||||||
<Row
|
<Row
|
||||||
display={{ sm: 'flex', md: 'none' }}
|
display={{ sm: 'flex', md: 'none' }}
|
||||||
position="fixed"
|
position="fixed"
|
||||||
bottom="60"
|
bottom="24"
|
||||||
left="16"
|
left="16"
|
||||||
height="56"
|
height="56"
|
||||||
borderRadius="12"
|
borderRadius="12"
|
||||||
@ -322,11 +479,13 @@ const SelectAllButton = () => {
|
|||||||
const resetSellAssets = useSellAsset((state) => state.reset)
|
const resetSellAssets = useSellAsset((state) => state.reset)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAllSelected) resetSellAssets()
|
|
||||||
if (isAllSelected) {
|
if (isAllSelected) {
|
||||||
displayAssets.forEach((asset) => selectSellAsset(asset))
|
displayAssets.forEach((asset) => selectSellAsset(asset))
|
||||||
|
} else {
|
||||||
|
resetSellAssets()
|
||||||
}
|
}
|
||||||
}, [displayAssets, isAllSelected, resetSellAssets, selectSellAsset])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isAllSelected, resetSellAssets, selectSellAsset])
|
||||||
|
|
||||||
const toggleAllSelected = () => {
|
const toggleAllSelected = () => {
|
||||||
setIsAllSelected(!isAllSelected)
|
setIsAllSelected(!isAllSelected)
|
||||||
@ -356,6 +515,89 @@ const SelectAllButton = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CollectionFiltersRow = ({
|
||||||
|
collections,
|
||||||
|
collectionFilters,
|
||||||
|
setCollectionFilters,
|
||||||
|
clearCollectionFilters,
|
||||||
|
}: {
|
||||||
|
collections: WalletCollection[]
|
||||||
|
collectionFilters: Array<string>
|
||||||
|
setCollectionFilters: (address: string) => void
|
||||||
|
clearCollectionFilters: Dispatch<SetStateAction<void>>
|
||||||
|
}) => {
|
||||||
|
const getCollection = (collectionAddress: string) => {
|
||||||
|
return collections?.find((collection) => collection.address === collectionAddress)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Row paddingTop="18" gap="8" flexWrap="wrap">
|
||||||
|
{collectionFilters &&
|
||||||
|
collectionFilters.map((collectionAddress, index) => (
|
||||||
|
<CollectionFilterItem
|
||||||
|
collection={getCollection(collectionAddress)}
|
||||||
|
key={index}
|
||||||
|
setCollectionFilters={setCollectionFilters}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{collectionFilters?.length ? (
|
||||||
|
<Box
|
||||||
|
as="button"
|
||||||
|
paddingLeft="8"
|
||||||
|
paddingRight="8"
|
||||||
|
color="genieBlue"
|
||||||
|
background="none"
|
||||||
|
fontSize="16"
|
||||||
|
border="none"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => clearCollectionFilters()}
|
||||||
|
>
|
||||||
|
Clear all
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionFilterItem = ({
|
||||||
|
collection,
|
||||||
|
setCollectionFilters,
|
||||||
|
}: {
|
||||||
|
collection: WalletCollection | undefined
|
||||||
|
setCollectionFilters: (address: string) => void
|
||||||
|
}) => {
|
||||||
|
if (!collection) return null
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
justifyContent="center"
|
||||||
|
paddingRight="4"
|
||||||
|
paddingTop="4"
|
||||||
|
paddingBottom="4"
|
||||||
|
paddingLeft="8"
|
||||||
|
borderRadius="12"
|
||||||
|
background="medGray"
|
||||||
|
fontSize="14"
|
||||||
|
>
|
||||||
|
<Box as="img" borderRadius="round" width="20" height="20" src={collection.image} />
|
||||||
|
<Box marginLeft="6" className={styles.collectionFilterBubbleText}>
|
||||||
|
{collection?.name}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
color="darkGray"
|
||||||
|
background="none"
|
||||||
|
height="28"
|
||||||
|
width="28"
|
||||||
|
padding="0"
|
||||||
|
as="button"
|
||||||
|
border="none"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => setCollectionFilters(collection.address)}
|
||||||
|
>
|
||||||
|
<CrossIcon />
|
||||||
|
</Box>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const CollectionSearch = ({
|
const CollectionSearch = ({
|
||||||
searchText,
|
searchText,
|
||||||
setSearchText,
|
setSearchText,
|
||||||
|
@ -21,7 +21,7 @@ export const mobileSellWrapper = style([
|
|||||||
top: { sm: '0', md: 'unset' },
|
top: { sm: '0', md: 'unset' },
|
||||||
zIndex: { sm: '3', md: 'auto' },
|
zIndex: { sm: '3', md: 'auto' },
|
||||||
height: { sm: 'full', md: 'auto' },
|
height: { sm: 'full', md: 'auto' },
|
||||||
width: { sm: 'full', md: 'auto' },
|
width: 'full',
|
||||||
overflowY: 'scroll',
|
overflowY: 'scroll',
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user