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:
Charles Bachmeier 2022-09-12 10:09:59 -07:00 committed by GitHub
parent eb95cedd72
commit cfee80ce3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 30 deletions

@ -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

@ -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
}