feat: Add animations for desktop, tablet, and mobile searchbar (#4584)
* animations working pre cleanup * all sprinkles * fix bug with phase1 search content * new isTablet hook * add conditional vars * typeof window * remove unneeded clsx usage Co-authored-by: Charlie <charlie@uniswap.org>
This commit is contained in:
parent
eb95cedd72
commit
cfee80ce3c
@ -4,6 +4,7 @@ import { buttonTextSmall, subhead, subheadSmall } from 'nft/css/common.css'
|
||||
import { breakpoints, sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
const DESKTOP_NAVBAR_WIDTH = 360
|
||||
const MAGNIFYING_GLASS_ICON_WIDTH = 28
|
||||
|
||||
const baseSearchStyle = style([
|
||||
sprinkles({
|
||||
@ -31,11 +32,8 @@ export const searchBarContainer = style([
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
top: '-24px',
|
||||
},
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2}px`,
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2 - MAGNIFYING_GLASS_ICON_WIDTH}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -44,7 +42,6 @@ export const searchBarContainer = style([
|
||||
export const searchBar = style([
|
||||
baseSearchStyle,
|
||||
sprinkles({
|
||||
height: 'full',
|
||||
color: 'placeholder',
|
||||
paddingX: '16',
|
||||
cursor: 'pointer',
|
||||
@ -60,8 +57,11 @@ export const searchBarInput = style([
|
||||
color: { default: 'blackBlue', placeholder: 'placeholder' },
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
width: 'full',
|
||||
}),
|
||||
{ lineHeight: '24px' },
|
||||
{
|
||||
lineHeight: '24px',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarDropdown = style([
|
||||
@ -70,6 +70,7 @@ export const searchBarDropdown = style([
|
||||
borderBottomLeftRadius: '12',
|
||||
borderBottomRightRadius: '12',
|
||||
background: 'lightGray',
|
||||
height: { sm: 'viewHeight', md: 'auto' },
|
||||
}),
|
||||
{
|
||||
borderTop: 'none',
|
||||
@ -84,7 +85,6 @@ export const suggestionRow = style([
|
||||
justifyContent: 'space-between',
|
||||
paddingY: '8',
|
||||
paddingX: '16',
|
||||
transition: '250',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
@ -167,3 +167,50 @@ export const notFoundContainer = style([
|
||||
paddingLeft: '16',
|
||||
}),
|
||||
])
|
||||
|
||||
const visibilityTransition = `visibility ${vars.time[125]}, opacity ${vars.time[125]}`
|
||||
const delayedTransitionProperties = `padding 0s ${vars.time[125]}, height 0s ${vars.time[125]}`
|
||||
|
||||
export const hidden = style([
|
||||
sprinkles({
|
||||
visibility: 'hidden',
|
||||
opacity: '0',
|
||||
padding: '0',
|
||||
height: '0',
|
||||
}),
|
||||
{
|
||||
transition: `${visibilityTransition}, ${delayedTransitionProperties}`,
|
||||
transitionTimingFunction: 'ease-in',
|
||||
},
|
||||
])
|
||||
export const visible = style([
|
||||
sprinkles({
|
||||
visibility: 'visible',
|
||||
opacity: '1',
|
||||
height: 'full',
|
||||
}),
|
||||
{
|
||||
transition: `${visibilityTransition}`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchContentCentered = style({
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
transform: `translateX(${DESKTOP_NAVBAR_WIDTH / 4}px)`,
|
||||
transition: `transform ${vars.time[125]}`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const searchContentLeftAlign = style({
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
transform: 'translateX(0)',
|
||||
transition: `transform ${vars.time[125]}`,
|
||||
transitionTimingFunction: 'ease-in',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -9,7 +9,7 @@ import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
import { magicalGradientOnHover, subheadSmall } from 'nft/css/common.css'
|
||||
import { useIsMobile, useSearchHistory } from 'nft/hooks'
|
||||
import { useIsMobile, useIsTablet, useSearchHistory } from 'nft/hooks'
|
||||
import { fetchSearchCollections, fetchTrendingCollections } from 'nft/queries'
|
||||
import { fetchSearchTokens } from 'nft/queries/genie/SearchTokensFetcher'
|
||||
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
|
||||
@ -273,6 +273,7 @@ export const SearchBar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const phase1Flag = useNftFlag()
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
@ -330,53 +331,56 @@ export const SearchBar = () => {
|
||||
}, [isOpen])
|
||||
|
||||
const placeholderText = phase1Flag === NftVariant.Enabled ? t`Search tokens and NFT collections` : t`Search tokens`
|
||||
const isMobileOrTablet = isMobile || isTablet
|
||||
const showCenteredSearchContent = !isOpen && phase1Flag !== NftVariant.Enabled && !isMobileOrTablet
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position={isOpen ? { sm: 'fixed', md: 'absolute' } : 'static'}
|
||||
position={{ sm: 'fixed', md: 'absolute' }}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
className={styles.searchBarContainer}
|
||||
display={{ sm: isOpen ? 'inline-block' : 'none', xl: 'inline-block' }}
|
||||
>
|
||||
<Row
|
||||
className={clsx(`${styles.searchBar} ${!isOpen && magicalGradientOnHover}`)}
|
||||
borderRadius={isOpen ? undefined : '12'}
|
||||
className={clsx(
|
||||
` ${styles.searchBar} ${!isOpen && !isMobile && magicalGradientOnHover} ${
|
||||
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
|
||||
}`
|
||||
)}
|
||||
borderRadius={isOpen || isMobileOrTablet ? undefined : '12'}
|
||||
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderTopLeftRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderBottomWidth={isOpen ? '0px' : '1px'}
|
||||
display={{ sm: isOpen ? 'flex' : 'none', xl: 'flex' }}
|
||||
justifyContent={isOpen || phase1Flag === NftVariant.Enabled ? 'flex-start' : 'center'}
|
||||
borderBottomWidth={isOpen || isMobileOrTablet ? '0px' : '1px'}
|
||||
onFocus={() => !isOpen && toggleOpen()}
|
||||
onClick={() => !isOpen && toggleOpen()}
|
||||
gap="12"
|
||||
>
|
||||
<Box display={{ sm: 'none', md: 'flex' }}>
|
||||
<MagnifyingGlassIcon />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', md: 'none' }} color="placeholder" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon />
|
||||
<Box className={showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign}>
|
||||
<Box display={{ sm: 'none', md: 'flex' }}>
|
||||
<MagnifyingGlassIcon />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', md: 'none' }} color="placeholder" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={placeholderText}
|
||||
width={isOpen || phase1Flag === NftVariant.Enabled ? 'full' : '120'}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
className={styles.searchBarInput}
|
||||
className={`${styles.searchBarInput} ${
|
||||
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
|
||||
}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</Row>
|
||||
<Box display={{ sm: isOpen ? 'none' : 'flex', xl: 'none' }}>
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<NavMagnifyingGlassIcon width={28} height={28} />
|
||||
</NavIcon>
|
||||
</Box>
|
||||
{isOpen &&
|
||||
(debouncedSearchValue.length > 0 && (tokensAreLoading || collectionsAreLoading) ? (
|
||||
<Box className={clsx(isOpen ? styles.visible : styles.hidden)}>
|
||||
{debouncedSearchValue.length > 0 && (tokensAreLoading || collectionsAreLoading) ? (
|
||||
<SkeletonRow />
|
||||
) : (
|
||||
<SearchBarDropdown
|
||||
@ -385,8 +389,12 @@ export const SearchBar = () => {
|
||||
collections={reducedCollections}
|
||||
hasInput={debouncedSearchValue.length > 0}
|
||||
/>
|
||||
))}
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<NavMagnifyingGlassIcon width={28} height={28} />
|
||||
</NavIcon>
|
||||
{isOpen && <Overlay />}
|
||||
</Box>
|
||||
)
|
||||
|
@ -209,6 +209,7 @@ export const vars = createGlobalTheme(':root', {
|
||||
black: '900',
|
||||
},
|
||||
time: {
|
||||
'125': '125ms',
|
||||
'250': '250ms',
|
||||
'500': '500ms',
|
||||
},
|
||||
@ -297,6 +298,7 @@ const layoutStyles = defineProperties({
|
||||
position: ['absolute', 'fixed', 'relative', 'sticky', 'static'],
|
||||
objectFit: ['contain', 'cover'],
|
||||
order: [0, 1],
|
||||
opacity: ['auto', '0', '1'],
|
||||
} as const,
|
||||
shorthands: {
|
||||
paddingX: ['paddingLeft', 'paddingRight'],
|
||||
|
@ -3,6 +3,7 @@ export * from './useCollectionFilters'
|
||||
export * from './useFiltersExpanded'
|
||||
export * from './useGenieList'
|
||||
export * from './useIsMobile'
|
||||
export * from './useIsTablet'
|
||||
export * from './useMarketplaceSelect'
|
||||
export * from './useNFTSelect'
|
||||
export * from './useSearchHistory'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { breakpoints } from 'nft/css/sprinkles.css'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const isClient = typeof window === 'object'
|
||||
const isClient = typeof window !== 'undefined'
|
||||
|
||||
function getIsMobile() {
|
||||
return isClient ? window.innerWidth < breakpoints.sm : false
|
||||
|
28
src/nft/hooks/useIsTablet.ts
Normal file
28
src/nft/hooks/useIsTablet.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { breakpoints } from 'nft/css/sprinkles.css'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const isClient = typeof window !== 'undefined'
|
||||
|
||||
function getIsTablet() {
|
||||
return isClient ? window.innerWidth < breakpoints.lg && window.innerWidth >= breakpoints.sm : false
|
||||
}
|
||||
|
||||
export function useIsTablet(): boolean {
|
||||
const [isTablet, setIsTablet] = useState(getIsTablet)
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setIsTablet(getIsTablet())
|
||||
}
|
||||
|
||||
if (isClient) {
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [])
|
||||
|
||||
return isTablet
|
||||
}
|
Loading…
Reference in New Issue
Block a user