Commit f4f1b772 authored by tom's avatar tom

style menu

parent 10a1d410
...@@ -11,7 +11,7 @@ export interface SearchResultToken { ...@@ -11,7 +11,7 @@ export interface SearchResultToken {
export interface SearchResultAddress { export interface SearchResultAddress {
type: 'address'; type: 'address';
name: string; name: string | null;
address: string; address: string;
url: string; url: string;
} }
...@@ -29,8 +29,10 @@ export interface SearchResultTx { ...@@ -29,8 +29,10 @@ export interface SearchResultTx {
url: string; url: string;
} }
export type SearchResultItem = SearchResultToken | SearchResultAddress | SearchResultBlock | SearchResultTx;
export interface SearchResult { export interface SearchResult {
items: Array<SearchResultToken | SearchResultAddress | SearchResultBlock | SearchResultTx>; items: Array<SearchResultItem>;
next_page_params: { next_page_params: {
'address_hash': string | null; 'address_hash': string | null;
'block_hash': string | null; 'block_hash': string | null;
...@@ -41,5 +43,5 @@ export interface SearchResult { ...@@ -41,5 +43,5 @@ export interface SearchResult {
'name': string; 'name': string;
'q': string; 'q': string;
'tx_hash': string | null; 'tx_hash': string | null;
}; } | null;
} }
import { Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, Box, Text } from '@chakra-ui/react'; import { Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import _groupBy from 'lodash/groupBy';
import type { ChangeEvent, FormEvent, FocusEvent } from 'react'; import type { ChangeEvent, FormEvent, FocusEvent } from 'react';
import React from 'react'; import React from 'react';
...@@ -7,6 +6,7 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -7,6 +6,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link'; import link from 'lib/link/link';
import SearchBarInput from './SearchBarInput'; import SearchBarInput from './SearchBarInput';
import SearchBarSuggest from './SearchBarSuggest';
type Props = { type Props = {
withShadow?: boolean; withShadow?: boolean;
...@@ -20,7 +20,7 @@ const data = [ ...@@ -20,7 +20,7 @@ const data = [
name: 'Toms NFT', name: 'Toms NFT',
symbol: 'TNT', symbol: 'TNT',
token_url: '/token/0x377c5F2B300B25a534d4639177873b7fEAA56d4B', token_url: '/token/0x377c5F2B300B25a534d4639177873b7fEAA56d4B',
type: 'token', type: 'token' as const,
}, },
{ {
address: '0xC35Cc7223B0175245E9964f2E3119c261E8e21F9', address: '0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
...@@ -28,23 +28,31 @@ const data = [ ...@@ -28,23 +28,31 @@ const data = [
name: 'TomToken', name: 'TomToken',
symbol: 'pdE1B', symbol: 'pdE1B',
token_url: '/token/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9', token_url: '/token/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
type: 'token', type: 'token' as const,
},
{
address: '0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
address_url: '/address/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
name: 'TomToken',
symbol: 'pdE1B',
token_url: '/token/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
type: 'token' as const,
}, },
{ {
block_hash: '0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1', block_hash: '0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
block_number: 8198536, block_number: 8198536,
type: 'block', type: 'block' as const,
url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1', url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
}, },
{ {
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
name: null, name: null,
type: 'address', type: 'address' as const,
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
}, },
{ {
tx_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', tx_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
type: 'transaction', type: 'transaction' as const,
url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
}, },
]; ];
...@@ -53,6 +61,7 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => { ...@@ -53,6 +61,7 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => {
const [ value, setValue ] = React.useState(''); const [ value, setValue ] = React.useState('');
const { isOpen, onClose, onOpen } = useDisclosure(); const { isOpen, onClose, onOpen } = useDisclosure();
const inputRef = React.useRef<HTMLFormElement>(null); const inputRef = React.useRef<HTMLFormElement>(null);
const menuRef = React.useRef<HTMLDivElement>(null);
const menuWidth = React.useRef<number>(0); const menuWidth = React.useRef<number>(0);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -71,7 +80,7 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => { ...@@ -71,7 +80,7 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => {
}, [ onOpen ]); }, [ onOpen ]);
const handleBlur = React.useCallback((event: FocusEvent<HTMLFormElement>) => { const handleBlur = React.useCallback((event: FocusEvent<HTMLFormElement>) => {
const isFocusInMenu = event.relatedTarget?.classList.contains('chakra-popover__content'); const isFocusInMenu = menuRef.current?.contains(event.relatedTarget);
if (!isFocusInMenu) { if (!isFocusInMenu) {
onClose(); onClose();
} }
...@@ -90,8 +99,6 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => { ...@@ -90,8 +99,6 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => {
}; };
}, [ calculateMenuWidth ]); }, [ calculateMenuWidth ]);
const groupedData = _groupBy(data, 'type');
return ( return (
<Popover <Popover
isOpen={ isOpen } isOpen={ isOpen }
...@@ -111,18 +118,9 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => { ...@@ -111,18 +118,9 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => {
withShadow={ withShadow } withShadow={ withShadow }
/> />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent w={ `${ menuWidth.current }px` } maxH={{ base: '300px', lg: '500px' }} overflowY="scroll" ref={ menuRef }>
w={ `${ menuWidth.current }px` } <PopoverBody display="flex" flexDirection="column" rowGap="6">
> <SearchBarSuggest data={{ items: data, next_page_params: null }}/>
<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> </PopoverBody>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
......
import { Box, Text } from '@chakra-ui/react';
import _groupBy from 'lodash/groupBy';
import React from 'react';
import type { SearchResult, SearchResultType } from 'types/api/search';
import useIsMobile from 'lib/hooks/useIsMobile';
import SearchBarSuggestItem from './SearchBarSuggestItem';
interface Props {
data: SearchResult;
}
interface Group {
type: SearchResultType;
title: string;
}
const GROUPS: Array<Group> = [
{ type: 'block', title: 'Blocks' },
{ type: 'token', title: 'Tokens' },
{ type: 'address', title: 'Address' },
{ type: 'transaction', title: 'Transactions' },
];
const SearchBarSuggest = ({ data }: Props) => {
const isMobile = useIsMobile();
const groupedData = _groupBy(data.items, 'type');
return (
<>
{ Object.entries(groupedData).map(([ group, data ]) => {
const groupName = GROUPS.find(({ type }) => type === group)?.title;
return (
<Box key={ group }>
<Text variant="secondary" fontSize="sm" fontWeight={ 600 } mb={ 3 }>{ groupName } ({ data.length })</Text>
{ data.map((item, index) => <SearchBarSuggestItem key={ index } data={ item } isMobile={ isMobile }/>) }
</Box>
);
}) }
</>
);
};
export default SearchBarSuggest;
import { chakra, Text, Flex, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
import link from 'lib/link/link';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenLogo from 'ui/shared/TokenLogo';
interface Props {
data: SearchResultItem;
isMobile: boolean | undefined;
}
const SearchBarSuggestItem = ({ data, isMobile }: Props) => {
const url = (() => {
switch (data.type) {
case 'token': {
return link('token_index', { hash: data.address });
}
case 'address': {
return link('address_index', { id: data.address });
}
case 'transaction': {
return link('tx', { id: data.tx_hash });
}
case 'block': {
return link('block', { id: String(data.block_number) });
}
}
})();
const content = (() => {
switch (data.type) {
case 'token': {
return (
<>
<Flex>
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name }/>
<Text fontWeight={ 700 } ml={ 2 }>
<span>{ data.name }</span>
{ data.symbol && <span> ({ data.symbol })</span> }
</Text>
</Flex>
<Text variant="secondary" mt={ 2 } overflow="hidden" whiteSpace="nowrap">
{ isMobile ? <HashStringShorten hash={ data.address } isTooltipDisabled/> : data.address }
</Text>
</>
);
}
case 'address': {
return (
<Address>
<AddressIcon hash={ data.address }/>
<Text fontWeight={ 700 } ml={ 2 }>{ data.name || data.address }</Text>
</Address>
);
}
case 'block': {
return (
<Text>
{ data.block_number }
</Text>
);
}
case 'transaction': {
return (
<Text>
{ data.tx_hash }
</Text>
);
}
}
})();
return (
<chakra.a
py={ 3 }
px={ 1 }
display="block"
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderBottomWidth="1px"
_last={{
borderBottomWidth: '0',
}}
_hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'),
}}
fontSize="sm"
href={ url }
>
{ content }
</chakra.a>
);
};
export default React.memo(SearchBarSuggestItem);
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