Support metadata_tag type in search (#2559)
* Support metadata_tag type in search * Update ui/searchResults/SearchResultListItem.tsx Co-authored-by: tom goriunov <[email protected]> --------- Co-authored-by: tom goriunov <[email protected]>
@@ -8,6 +8,7 @@ import type {
|
||||
SearchResultUserOp,
|
||||
SearchResultBlob,
|
||||
SearchResultDomain,
|
||||
SearchResultMetadataTag,
|
||||
} from 'types/api/search';
|
||||
|
||||
export const token1: SearchResultToken = {
|
||||
@@ -147,6 +148,42 @@ export const domain1: SearchResultDomain = {
|
||||
url: '/address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
|
||||
};
|
||||
|
||||
export const metatag1: SearchResultMetadataTag = {
|
||||
...address1,
|
||||
type: 'metadata_tag',
|
||||
metadata: {
|
||||
name: 'utko',
|
||||
slug: 'utko',
|
||||
meta: {},
|
||||
tagType: 'name',
|
||||
ordinal: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const metatag2: SearchResultMetadataTag = {
|
||||
...address2,
|
||||
type: 'metadata_tag',
|
||||
metadata: {
|
||||
name: 'utko',
|
||||
slug: 'utko',
|
||||
meta: {},
|
||||
tagType: 'name',
|
||||
ordinal: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const metatag3: SearchResultMetadataTag = {
|
||||
...contract2,
|
||||
type: 'metadata_tag',
|
||||
metadata: {
|
||||
name: 'super utko',
|
||||
slug: 'super-utko',
|
||||
meta: {},
|
||||
tagType: 'protocol',
|
||||
ordinal: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const baseResponse: SearchResult = {
|
||||
items: [
|
||||
token1,
|
||||
@@ -157,6 +194,8 @@ export const baseResponse: SearchResult = {
|
||||
tx1,
|
||||
blob1,
|
||||
domain1,
|
||||
metatag1,
|
||||
|
||||
],
|
||||
next_page_params: null,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import type * as bens from '@blockscout/bens-types';
|
||||
import type { TokenType } from 'types/api/token';
|
||||
|
||||
export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract';
|
||||
import type { AddressMetadataTagApi } from './addressMetadata';
|
||||
|
||||
export const SEARCH_RESULT_TYPES = {
|
||||
token: 'token',
|
||||
address: 'address',
|
||||
block: 'block',
|
||||
transaction: 'transaction',
|
||||
contract: 'contract',
|
||||
ens_domain: 'ens_domain',
|
||||
label: 'label',
|
||||
user_operation: 'user_operation',
|
||||
blob: 'blob',
|
||||
metadata_tag: 'metadata_tag',
|
||||
} as const;
|
||||
|
||||
export type SearchResultType = typeof SEARCH_RESULT_TYPES[keyof typeof SEARCH_RESULT_TYPES];
|
||||
|
||||
export interface SearchResultToken {
|
||||
type: 'token';
|
||||
@@ -20,29 +35,35 @@ export interface SearchResultToken {
|
||||
certified?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchResultAddressOrContract {
|
||||
type: 'address' | 'contract';
|
||||
type SearchResultEnsInfo = {
|
||||
address_hash: string;
|
||||
expiry_date?: string;
|
||||
name: string;
|
||||
names_count: number;
|
||||
} | null;
|
||||
|
||||
interface SearchResultAddressData {
|
||||
name: string | null;
|
||||
address: string;
|
||||
is_smart_contract_verified: boolean;
|
||||
certified?: true;
|
||||
filecoin_robust_address?: string | null;
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
ens_info?: {
|
||||
address_hash: string;
|
||||
expiry_date?: string;
|
||||
name: string;
|
||||
names_count: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SearchResultDomain {
|
||||
export interface SearchResultAddressOrContract extends SearchResultAddressData {
|
||||
type: 'address' | 'contract';
|
||||
ens_info?: SearchResultEnsInfo;
|
||||
}
|
||||
|
||||
export interface SearchResultMetadataTag extends SearchResultAddressData {
|
||||
type: 'metadata_tag';
|
||||
ens_info?: SearchResultEnsInfo;
|
||||
metadata: AddressMetadataTagApi;
|
||||
}
|
||||
|
||||
export interface SearchResultDomain extends SearchResultAddressData {
|
||||
type: 'ens_domain';
|
||||
name: string | null;
|
||||
address: string;
|
||||
filecoin_robust_address?: string | null;
|
||||
is_smart_contract_verified: boolean;
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
ens_info: {
|
||||
address_hash: string;
|
||||
expiry_date?: string;
|
||||
@@ -90,8 +111,16 @@ export interface SearchResultUserOp {
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
}
|
||||
|
||||
export type SearchResultItem = SearchResultToken | SearchResultAddressOrContract | SearchResultBlock | SearchResultTx | SearchResultLabel | SearchResultUserOp |
|
||||
SearchResultBlob | SearchResultDomain;
|
||||
export type SearchResultItem =
|
||||
SearchResultToken |
|
||||
SearchResultAddressOrContract |
|
||||
SearchResultBlock |
|
||||
SearchResultTx |
|
||||
SearchResultLabel |
|
||||
SearchResultUserOp |
|
||||
SearchResultBlob |
|
||||
SearchResultDomain |
|
||||
SearchResultMetadataTag;
|
||||
|
||||
export interface SearchResult {
|
||||
items: Array<SearchResultItem>;
|
||||
|
||||
@@ -20,6 +20,7 @@ test.describe('search by name', () => {
|
||||
searchMock.token2,
|
||||
searchMock.contract1,
|
||||
searchMock.address2,
|
||||
searchMock.metatag1,
|
||||
searchMock.label1,
|
||||
],
|
||||
next_page_params: null,
|
||||
@@ -52,6 +53,23 @@ test('search by address hash +@mobile', async({ render, mockApiResponse }) => {
|
||||
await expect(component.locator('main')).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('search by meta tag +@mobile', async({ render, mockApiResponse }) => {
|
||||
const hooksConfig = {
|
||||
router: {
|
||||
query: { q: 'utko' },
|
||||
},
|
||||
};
|
||||
const data = {
|
||||
items: [ searchMock.metatag1, searchMock.metatag2, searchMock.metatag3 ],
|
||||
next_page_params: null,
|
||||
};
|
||||
await mockApiResponse('search', data, { queryParams: { q: 'utko' } });
|
||||
|
||||
const component = await render(<SearchResults/>, { hooksConfig });
|
||||
|
||||
await expect(component.locator('main')).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('search by block number +@mobile', async({ render, mockApiResponse }) => {
|
||||
const hooksConfig = {
|
||||
router: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
|
||||
import type { FormEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { SEARCH_RESULT_TYPES } from 'types/api/search';
|
||||
import type { SearchResultItem } from 'types/client/search';
|
||||
|
||||
import config from 'configs/app';
|
||||
@@ -95,6 +96,9 @@ const SearchResultsPageContent = () => {
|
||||
|
||||
const displayedItems: Array<SearchResultItem | SearchResultAppItem> = React.useMemo(() => {
|
||||
const apiData = (data?.items || []).filter((item) => {
|
||||
if (!SEARCH_RESULT_TYPES[item.type]) {
|
||||
return false;
|
||||
}
|
||||
if (!config.features.userOps.isEnabled && item.type === 'user_operation') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 66 KiB |
@@ -21,6 +21,7 @@ import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
|
||||
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
|
||||
import EntityTagIcon from 'ui/shared/EntityTags/EntityTagIcon';
|
||||
import { ADDRESS_REGEXP } from 'ui/shared/forms/validators/address';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
@@ -79,6 +80,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
|
||||
);
|
||||
}
|
||||
|
||||
case 'metadata_tag':
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
@@ -357,27 +359,39 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
case 'metadata_tag':
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
const addressName = data.name || data.ens_info?.name;
|
||||
const expiresText = data.ens_info?.expiry_date ? ` (expires ${ dayjs(data.ens_info.expiry_date).fromNow() })` : '';
|
||||
|
||||
return addressName ? (
|
||||
<Flex alignItems="center">
|
||||
<Text
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
|
||||
{ data.ens_info && (
|
||||
data.ens_info.names_count > 1 ?
|
||||
<chakra.span color="text_secondary"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</chakra.span> :
|
||||
<chakra.span color="text_secondary">{ expiresText }</chakra.span>
|
||||
) }
|
||||
</Text>
|
||||
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 } ml={ 1 }/> }
|
||||
return (addressName || data.type === 'metadata_tag') ? (
|
||||
<Flex alignItems="center" gap={ 2 } justifyContent="space-between" flexWrap="wrap">
|
||||
{ addressName && (
|
||||
<Flex alignItems="center">
|
||||
<Text
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
|
||||
{ data.ens_info && (
|
||||
data.ens_info.names_count > 1 ?
|
||||
<chakra.span color="text_secondary"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</chakra.span> :
|
||||
<chakra.span color="text_secondary">{ expiresText }</chakra.span>
|
||||
) }
|
||||
</Text>
|
||||
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 } ml={ 1 }/> }
|
||||
</Flex>
|
||||
) }
|
||||
{ data.type === 'metadata_tag' && (
|
||||
// we show regular tag because we don't need all meta info here, but need to highlight search term
|
||||
<Tag display="flex" alignItems="center">
|
||||
<EntityTagIcon data={ data.metadata }/>
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightText(data.metadata.name, searchTerm) }}/>
|
||||
</Tag>
|
||||
) }
|
||||
</Flex>
|
||||
) :
|
||||
null;
|
||||
|
||||
@@ -21,6 +21,7 @@ import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
|
||||
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
|
||||
import EntityTagIcon from 'ui/shared/EntityTags/EntityTagIcon';
|
||||
import { ADDRESS_REGEXP } from 'ui/shared/forms/validators/address';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
@@ -99,6 +100,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
|
||||
);
|
||||
}
|
||||
|
||||
case 'metadata_tag':
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
@@ -119,7 +121,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
|
||||
|
||||
return (
|
||||
<>
|
||||
<Td fontSize="sm" colSpan={ addressName ? 1 : 3 }>
|
||||
<Td fontSize="sm" colSpan={ (addressName || data.type === 'metadata_tag') ? 1 : 3 } verticalAlign="middle">
|
||||
<AddressEntity.Container>
|
||||
<AddressEntity.Icon address={ address }/>
|
||||
<AddressEntity.Link
|
||||
@@ -138,7 +140,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
|
||||
</AddressEntity.Container>
|
||||
</Td>
|
||||
{ addressName && (
|
||||
<Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
|
||||
<Td colSpan={ data.type === 'metadata_tag' ? 1 : 2 } fontSize="sm" verticalAlign="middle">
|
||||
<Flex alignItems="center">
|
||||
<Text
|
||||
overflow="hidden"
|
||||
@@ -159,6 +161,17 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
|
||||
</Flex>
|
||||
</Td>
|
||||
) }
|
||||
{ data.type === 'metadata_tag' && (
|
||||
<Td colSpan={ addressName ? 1 : 2 } fontSize="sm" verticalAlign="middle">
|
||||
<Flex justifyContent="flex-end">
|
||||
{ /* we show regular tag because we don't need all meta info here, but need to highlight search term */ }
|
||||
<Tag display="flex" alignItems="center">
|
||||
<EntityTagIcon data={ data.metadata } iconColor="gray.400"/>
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightText(data.metadata.name, searchTerm) }}/>
|
||||
</Tag>
|
||||
</Flex>
|
||||
</Td>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { ResponsiveValue } from '@chakra-ui/react';
|
||||
import { chakra, Image, Tag } from '@chakra-ui/react';
|
||||
import { Tag } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { EntityTag as TEntityTag } from './types';
|
||||
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import TruncatedValue from 'ui/shared/TruncatedValue';
|
||||
|
||||
import EntityTagIcon from './EntityTagIcon';
|
||||
import EntityTagLink from './EntityTagLink';
|
||||
import EntityTagPopover from './EntityTagPopover';
|
||||
import { getTagLinkParams } from './utils';
|
||||
@@ -26,7 +26,6 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
|
||||
}
|
||||
|
||||
const hasLink = !noLink && Boolean(getTagLinkParams(data));
|
||||
const iconColor = data.meta?.textColor ?? 'gray.400';
|
||||
|
||||
const name = (() => {
|
||||
if (data.meta?.warpcastHandle) {
|
||||
@@ -36,22 +35,6 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
|
||||
return data.name;
|
||||
})();
|
||||
|
||||
const icon = (() => {
|
||||
if (data.meta?.tagIcon) {
|
||||
return <Image boxSize={ 3 } mr={ 1 } flexShrink={ 0 } src={ data.meta.tagIcon } alt={ `${ data.name } icon` }/>;
|
||||
}
|
||||
|
||||
if (data.tagType === 'name') {
|
||||
return <IconSvg name="publictags_slim" boxSize={ 3 } mr={ 1 } flexShrink={ 0 } color={ iconColor }/>;
|
||||
}
|
||||
|
||||
if (data.tagType === 'protocol' || data.tagType === 'generic') {
|
||||
return <chakra.span color={ iconColor } whiteSpace="pre"># </chakra.span>;
|
||||
}
|
||||
|
||||
return null;
|
||||
})();
|
||||
|
||||
return (
|
||||
<EntityTagPopover data={ data }>
|
||||
<Tag
|
||||
@@ -66,7 +49,7 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
|
||||
_hover={ hasLink ? { opacity: 0.76 } : undefined }
|
||||
>
|
||||
<EntityTagLink data={ data } noLink={ noLink }>
|
||||
{ icon }
|
||||
<EntityTagIcon data={ data } iconColor={ data.meta?.textColor }/>
|
||||
<TruncatedValue value={ name } tooltipPlacement="top"/>
|
||||
</EntityTagLink>
|
||||
</Tag>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { chakra, Image } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { EntityTag as TEntityTag } from './types';
|
||||
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
interface Props {
|
||||
data: TEntityTag;
|
||||
iconColor?: string;
|
||||
}
|
||||
|
||||
const EntityTagIcon = ({ data, iconColor = 'gray.400' }: Props) => {
|
||||
|
||||
if (data.meta?.tagIcon) {
|
||||
return <Image boxSize={ 3 } mr={ 1 } flexShrink={ 0 } src={ data.meta.tagIcon } alt={ `${ data.name } icon` }/>;
|
||||
}
|
||||
|
||||
if (data.tagType === 'name') {
|
||||
return <IconSvg name="publictags_slim" boxSize={ 3 } mr={ 1 } flexShrink={ 0 } color={ iconColor }/>;
|
||||
}
|
||||
|
||||
if (data.tagType === 'protocol' || data.tagType === 'generic') {
|
||||
return <chakra.span color={ iconColor } whiteSpace="pre"># </chakra.span>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default React.memo(EntityTagIcon);
|
||||
@@ -53,7 +53,8 @@ export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleSh
|
||||
export function getItemCategory(item: SearchResultItem | SearchResultAppItem): Category | undefined {
|
||||
switch (item.type) {
|
||||
case 'address':
|
||||
case 'contract': {
|
||||
case 'contract':
|
||||
case 'metadata_tag': {
|
||||
return 'address';
|
||||
}
|
||||
case 'token': {
|
||||
|
||||
@@ -76,6 +76,19 @@ test('search by address hash +@mobile', async({ render, page, mockApiResponse })
|
||||
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
|
||||
});
|
||||
|
||||
test('search by meta tag +@mobile +@dark-mode', async({ render, page, mockApiResponse }) => {
|
||||
const apiUrl = await mockApiResponse('quick_search', [
|
||||
searchMock.metatag1,
|
||||
searchMock.metatag2,
|
||||
searchMock.metatag3,
|
||||
], { queryParams: { q: 'utko' } });
|
||||
await render(<SearchBar/>);
|
||||
await page.getByPlaceholder(/search/i).fill('utko');
|
||||
await page.waitForResponse(apiUrl);
|
||||
|
||||
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
|
||||
});
|
||||
|
||||
test('search by block number +@mobile', async({ render, page, mockApiResponse }) => {
|
||||
const apiUrl = await mockApiResponse('quick_search', [
|
||||
searchMock.block1,
|
||||
|
||||
@@ -142,7 +142,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
|
||||
return (
|
||||
<>
|
||||
{ resultCategories.length > 1 && (
|
||||
<Box position="sticky" top="0" width="100%" background={ bgColor } py={ 5 } my={ -5 } ref={ tabsRef }>
|
||||
<Box position="sticky" top="0" width="100%" background={ bgColor } py={ 5 } my={ -5 } ref={ tabsRef } zIndex={ 1 }>
|
||||
<Tabs variant="outline" colorScheme="gray" size="sm" index={ tabIndex }>
|
||||
<TabList columnGap={ 3 } rowGap={ 2 } flexWrap="wrap">
|
||||
{ resultCategories.map((cat, index) => (
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { chakra, Box, Text, Flex } from '@chakra-ui/react';
|
||||
import { chakra, Box, Text, Flex, Tag, Grid } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { ItemsProps } from './types';
|
||||
import type { SearchResultAddressOrContract } from 'types/api/search';
|
||||
import type { SearchResultAddressOrContract, SearchResultMetadataTag } from 'types/api/search';
|
||||
|
||||
import { toBech32Address } from 'lib/address/bech32';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import highlightText from 'lib/highlightText';
|
||||
import ContractCertifiedLabel from 'ui/shared/ContractCertifiedLabel';
|
||||
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import EntityTagIcon from 'ui/shared/EntityTags/EntityTagIcon';
|
||||
import { ADDRESS_REGEXP } from 'ui/shared/forms/validators/address';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
|
||||
const SearchBarSuggestAddress = ({ data, isMobile, searchTerm, addressFormat }: ItemsProps<SearchResultAddressOrContract>) => {
|
||||
type Props = ItemsProps<SearchResultAddressOrContract | SearchResultMetadataTag>;
|
||||
|
||||
const SearchBarSuggestAddress = ({ data, isMobile, searchTerm, addressFormat }: Props) => {
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
const hash = data.filecoin_robust_address || (addressFormat === 'bech32' ? toBech32Address(data.address) : data.address);
|
||||
|
||||
@@ -49,6 +52,13 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm, addressFormat }:
|
||||
{ data.certified && <ContractCertifiedLabel boxSize={ 4 } iconSize={ 4 } ml={ 1 }/> }
|
||||
</Flex>
|
||||
);
|
||||
const tagEl = data.type === 'metadata_tag' ? (
|
||||
// we show regular tag because we don't need all meta info here, but need to highlight search term
|
||||
<Tag display="flex" alignItems="center" ml={{ base: 0, lg: 'auto' }}>
|
||||
<EntityTagIcon data={ data.metadata } iconColor="gray.400"/>
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightText(data.metadata.name, searchTerm) }}/>
|
||||
</Tag>
|
||||
) : null;
|
||||
const addressEl = <HashStringShortenDynamic hash={ hash } isTooltipDisabled/>;
|
||||
|
||||
if (isMobile) {
|
||||
@@ -66,14 +76,17 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm, addressFormat }:
|
||||
{ addressEl }
|
||||
</Box>
|
||||
</Flex>
|
||||
{ nameEl }
|
||||
<Flex alignItems="center" justifyContent="space-between" flexWrap="wrap" gap={ 2 }>
|
||||
{ nameEl }
|
||||
{ tagEl }
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex alignItems="center">
|
||||
<Flex alignItems="center" w="450px" mr={ 2 }>
|
||||
<Grid templateColumns="repeat(2, minmax(0, 1fr))" gap={ 2 }>
|
||||
<Flex alignItems="center" mr={ 2 } minWidth={ 0 }>
|
||||
{ icon }
|
||||
<Box
|
||||
as={ shouldHighlightHash ? 'mark' : 'span' }
|
||||
@@ -81,12 +94,16 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm, addressFormat }:
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
fontWeight={ 700 }
|
||||
minWidth={ 0 }
|
||||
>
|
||||
{ addressEl }
|
||||
</Box>
|
||||
</Flex>
|
||||
{ nameEl }
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="space-between" gap={ 2 } minWidth={ 0 }>
|
||||
{ nameEl }
|
||||
{ tagEl }
|
||||
</Flex>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick, addressForm
|
||||
}
|
||||
case 'contract':
|
||||
case 'address':
|
||||
case 'label': {
|
||||
case 'label':
|
||||
case 'metadata_tag': {
|
||||
return route({ pathname: '/address/[hash]', query: { hash: data.address } });
|
||||
}
|
||||
case 'transaction': {
|
||||
@@ -73,6 +74,7 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick, addressForm
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'metadata_tag':
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
return (
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 40 KiB |