Commit 10a1d410 authored by tom's avatar tom

one search bar for all cases and dummy dropdown

parent e176fd03
...@@ -14,7 +14,7 @@ const $arrowBg = cssVar('popper-arrow-bg'); ...@@ -14,7 +14,7 @@ const $arrowBg = cssVar('popper-arrow-bg');
const $arrowShadowColor = cssVar('popper-arrow-shadow-color'); const $arrowShadowColor = cssVar('popper-arrow-shadow-color');
const baseStylePopper = defineStyle({ const baseStylePopper = defineStyle({
zIndex: 20, zIndex: 'popover',
}); });
const baseStyleContent = defineStyle((props) => { const baseStyleContent = defineStyle((props) => {
......
export type SearchResultType = 'token' | 'address' | 'block' | 'transaction';
export interface SearchResultToken {
type: 'token';
name: string;
symbol: string;
address: string;
token_url: string;
address_url: string;
}
export interface SearchResultAddress {
type: 'address';
name: string;
address: string;
url: string;
}
export interface SearchResultBlock {
type: 'block';
block_number: number;
block_hash: string;
url: string;
}
export interface SearchResultTx {
type: 'transaction';
tx_hash: string;
url: string;
}
export interface SearchResult {
items: Array<SearchResultToken | SearchResultAddress | SearchResultBlock | SearchResultTx>;
next_page_params: {
'address_hash': string | null;
'block_hash': string | null;
'holder_count': number | null;
'inserted_at': string | null;
'item_type': SearchResultType;
'items_count': number;
'name': string;
'q': string;
'tx_hash': string | null;
};
}
import type { ChangeEvent, FormEvent } from 'react'; import { Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, Box, Text } from '@chakra-ui/react';
import _groupBy from 'lodash/groupBy';
import type { ChangeEvent, FormEvent, FocusEvent } from 'react';
import React from 'react'; import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link'; import link from 'lib/link/link';
import SearchBarDesktop from './SearchBarDesktop'; import SearchBarInput from './SearchBarInput';
import SearchBarMobile from './SearchBarMobile';
import SearchBarMobileHome from './SearchBarMobileHome';
type Props = { type Props = {
withShadow?: boolean; withShadow?: boolean;
isHomepage?: boolean; isHomepage?: boolean;
} }
const data = [
{
address: '0x377c5F2B300B25a534d4639177873b7fEAA56d4B',
address_url: '/address/0x377c5F2B300B25a534d4639177873b7fEAA56d4B',
name: 'Toms NFT',
symbol: 'TNT',
token_url: '/token/0x377c5F2B300B25a534d4639177873b7fEAA56d4B',
type: 'token',
},
{
address: '0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
address_url: '/address/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
name: 'TomToken',
symbol: 'pdE1B',
token_url: '/token/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
type: 'token',
},
{
block_hash: '0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
block_number: 8198536,
type: 'block',
url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
},
{
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
name: null,
type: 'address',
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
},
{
tx_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
type: 'transaction',
url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
},
];
const SearchBar = ({ isHomepage, withShadow }: Props) => { const SearchBar = ({ isHomepage, withShadow }: Props) => {
const [ value, setValue ] = React.useState(''); const [ value, setValue ] = React.useState('');
const { isOpen, onClose, onOpen } = useDisclosure();
const inputRef = React.useRef<HTMLFormElement>(null);
const menuWidth = React.useRef<number>(0);
const isMobile = useIsMobile();
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value); setValue(event.target.value);
...@@ -25,23 +66,66 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => { ...@@ -25,23 +66,66 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => {
window.location.assign(url); window.location.assign(url);
}, [ value ]); }, [ value ]);
const handleFocus = React.useCallback(() => {
onOpen();
}, [ onOpen ]);
const handleBlur = React.useCallback((event: FocusEvent<HTMLFormElement>) => {
const isFocusInMenu = event.relatedTarget?.classList.contains('chakra-popover__content');
if (!isFocusInMenu) {
onClose();
}
}, [ onClose ]);
const menuPaddingX = isMobile && !isHomepage ? 32 : 0;
const calculateMenuWidth = React.useCallback(() => {
menuWidth.current = (inputRef.current?.getBoundingClientRect().width || 0) - menuPaddingX;
}, [ menuPaddingX ]);
React.useEffect(() => {
calculateMenuWidth();
window.addEventListener('resize', calculateMenuWidth);
return function cleanup() {
window.removeEventListener('resize', calculateMenuWidth);
};
}, [ calculateMenuWidth ]);
const groupedData = _groupBy(data, 'type');
return ( return (
<> <Popover
<SearchBarDesktop onChange={ handleChange } onSubmit={ handleSubmit } isHomepage={ isHomepage }/> isOpen={ isOpen }
{ !isHomepage && ( autoFocus={ false }
<SearchBarMobile onClose={ onClose }
placement="bottom-start"
offset={ isMobile && !isHomepage ? [ 16, -12 ] : undefined }
>
<PopoverTrigger>
<SearchBarInput
ref={ inputRef }
onChange={ handleChange } onChange={ handleChange }
onSubmit={ handleSubmit } onSubmit={ handleSubmit }
onFocus={ handleFocus }
onBlur={ handleBlur }
isHomepage={ isHomepage }
withShadow={ withShadow } withShadow={ withShadow }
/> />
) } </PopoverTrigger>
{ isHomepage && ( <PopoverContent
<SearchBarMobileHome w={ `${ menuWidth.current }px` }
onChange={ handleChange } >
onSubmit={ handleSubmit } <PopoverBody>
/> { Object.entries(groupedData).map(([ group, data ]) => {
) } return (
</> <Box key={ group }>
<Text>{ group }</Text>
{ data.map((item, index) => <Box key={ index }>{ item.name || item.address || item.block_number || item.tx_hash }</Box>) }
</Box>
);
}) }
</PopoverBody>
</PopoverContent>
</Popover>
); );
}; };
......
import { InputGroup, Input, InputLeftElement, Icon, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { ChangeEvent, FormEvent } from 'react';
import searchIcon from 'icons/search.svg';
interface Props {
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
isHomepage?: boolean;
}
const SearchBarDesktop = ({ onChange, onSubmit, isHomepage }: Props) => {
return (
<chakra.form
noValidate
onSubmit={ onSubmit }
display={{ base: 'none', lg: 'block' }}
w="100%"
backgroundColor={ isHomepage ? 'white' : 'none' }
borderRadius="base"
>
<InputGroup>
<InputLeftElement w={ 6 } ml={ 4 }>
<Icon as={ searchIcon } boxSize={ 6 } color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/>
</InputLeftElement>
<Input
// paddingInlineStart="50px"
pl="50px"
placeholder="Search by addresses / transactions / block / token... "
ml="1px"
onChange={ onChange }
border={ isHomepage ? 'none' : '2px solid' }
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') }
_focusWithin={{ _placeholder: { color: 'gray.300' } }}
color={ useColorModeValue('black', 'white') }
/>
</InputGroup>
</chakra.form>
);
};
export default React.memo(SearchBarDesktop);
import { InputGroup, Input, InputLeftElement, Icon, useColorModeValue, chakra } from '@chakra-ui/react'; import { InputGroup, Input, InputLeftElement, Icon, chakra, useColorModeValue, forwardRef } from '@chakra-ui/react';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import React from 'react'; import React from 'react';
import type { ChangeEvent, FormEvent } from 'react'; import type { ChangeEvent, FormEvent, FocusEvent } from 'react';
import searchIcon from 'icons/search.svg'; import searchIcon from 'icons/search.svg';
import { useScrollDirection } from 'lib/contexts/scrollDirection'; import { useScrollDirection } from 'lib/contexts/scrollDirection';
import useIsMobile from 'lib/hooks/useIsMobile';
const TOP = 55;
interface Props { interface Props {
onChange: (event: ChangeEvent<HTMLInputElement>) => void; onChange: (event: ChangeEvent<HTMLInputElement>) => void;
onSubmit: (event: FormEvent<HTMLFormElement>) => void; onSubmit: (event: FormEvent<HTMLFormElement>) => void;
onBlur: (event: FocusEvent<HTMLFormElement>) => void;
onFocus: () => void;
isHomepage?: boolean;
withShadow?: boolean; withShadow?: boolean;
} }
const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => { const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, withShadow }: Props, ref: React.ForwardedRef<HTMLFormElement>) => {
const [ isSticky, setIsSticky ] = React.useState(false); const [ isSticky, setIsSticky ] = React.useState(false);
const scrollDirection = useScrollDirection(); const scrollDirection = useScrollDirection();
const isMobile = useIsMobile();
const handleScroll = React.useCallback(() => { const handleScroll = React.useCallback(() => {
if (window.pageYOffset !== 0) { if (window.pageYOffset !== 0) {
...@@ -28,6 +30,9 @@ const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => { ...@@ -28,6 +30,9 @@ const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => {
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
if (!isMobile || isHomepage) {
return;
}
const throttledHandleScroll = throttle(handleScroll, 300); const throttledHandleScroll = throttle(handleScroll, 300);
window.addEventListener('scroll', throttledHandleScroll); window.addEventListener('scroll', throttledHandleScroll);
...@@ -37,45 +42,54 @@ const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => { ...@@ -37,45 +42,54 @@ const SearchBarMobile = ({ onChange, onSubmit, withShadow }: Props) => {
}; };
// replicate componentDidMount // replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [ isMobile ]);
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
const bgColor = useColorModeValue('white', 'black'); const bgColor = useColorModeValue('white', 'black');
const transformMobile = scrollDirection !== 'down' ? 'translateY(0)' : 'translateY(-100%)';
return ( return (
<chakra.form <chakra.form
ref={ ref }
noValidate noValidate
onSubmit={ onSubmit } onSubmit={ onSubmit }
paddingX={ 4 } onBlur={ onBlur }
paddingTop={ 1 } onFocus={ onFocus }
paddingBottom={ 2 } w="100%"
position="fixed" backgroundColor={ isHomepage ? 'white' : bgColor }
top={ `${ TOP }px` } borderRadius={{ base: isHomepage ? 'base' : 'none', lg: 'base' }}
position={{ base: isHomepage ? 'static' : 'fixed', lg: 'static' }}
top={{ base: isHomepage ? 0 : 55, lg: 0 }}
left="0" left="0"
zIndex="sticky1" zIndex={{ base: isHomepage ? 'auto' : 'sticky1', lg: 'auto' }}
bgColor={ bgColor } paddingX={{ base: isHomepage ? 0 : 4, lg: 0 }}
transform={ scrollDirection !== 'down' ? 'translateY(0)' : 'translateY(-100%)' } paddingTop={{ base: isHomepage ? 0 : 1, lg: 0 }}
paddingBottom={{ base: isHomepage ? 0 : 4, lg: 0 }}
boxShadow={ withShadow && scrollDirection !== 'down' && isSticky ? 'md' : 'none' }
transform={{ base: isHomepage ? 'none' : transformMobile, lg: 'none' }}
transitionProperty="transform,box-shadow" transitionProperty="transform,box-shadow"
transitionDuration="slow" transitionDuration="slow"
display={{ base: 'block', lg: 'none' }}
w="100%"
boxShadow={ withShadow && scrollDirection !== 'down' && isSticky ? 'md' : 'none' }
> >
<InputGroup size="sm"> <InputGroup size={{ base: isHomepage ? 'md' : 'sm', lg: 'md' }}>
<InputLeftElement > <InputLeftElement w={{ base: isHomepage ? 6 : 4, lg: 6 }} ml={{ base: isHomepage ? 4 : 3, lg: 4 }} h="100%">
<Icon as={ searchIcon } boxSize={ 4 } color={ searchIconColor }/> <Icon as={ searchIcon } boxSize={{ base: isHomepage ? 6 : 4, lg: 6 }} color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/>
</InputLeftElement> </InputLeftElement>
<Input <Input
paddingInlineStart="38px" pl={{ base: isHomepage ? '50px' : '38px', lg: '50px' }}
placeholder="Search by addresses / ... " sx={{
ml="1px" '@media screen and (max-width: 999px)': {
paddingLeft: isHomepage ? '50px' : '38px',
},
}}
placeholder={ isMobile ? 'Search by addresses / ... ' : 'Search by addresses / transactions / block / token... ' }
onChange={ onChange } onChange={ onChange }
borderColor={ inputBorderColor } border={ isHomepage ? 'none' : '2px solid' }
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') }
_focusWithin={{ _placeholder: { color: 'gray.300' } }}
color={ useColorModeValue('black', 'white') }
/> />
</InputGroup> </InputGroup>
</chakra.form> </chakra.form>
); );
}; };
export default React.memo(SearchBarMobile); export default React.memo(forwardRef(SearchBarInput));
import { InputGroup, Input, InputLeftElement, Icon, LightMode, chakra } from '@chakra-ui/react';
import React from 'react';
import type { ChangeEvent, FormEvent } from 'react';
import searchIcon from 'icons/search.svg';
interface Props {
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
backgroundColor?: string;
}
const SearchBarMobileHome = ({ onChange, onSubmit }: Props) => {
const commonProps = {
noValidate: true,
onSubmit: onSubmit,
width: '100%',
display: { base: 'block', lg: 'none' },
};
return (
<LightMode>
<chakra.form
{ ...commonProps }
bgColor="white"
h="60px"
borderRadius="10px"
>
<InputGroup size="md">
<InputLeftElement >
<Icon as={ searchIcon } boxSize={ 6 } color="blackAlpha.600"/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by addresses / ... "
ml="1px"
onChange={ onChange }
border="none"
color="black"
/>
</InputGroup>
</chakra.form>
</LightMode>
);
};
export default SearchBarMobileHome;
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