Commit e43c48b0 authored by isstuev's avatar isstuev

add apps to search results

parent b38b5a8e
import { useQuery } from '@tanstack/react-query';
import _pickBy from 'lodash/pickBy';
import _unique from 'lodash/uniq';
import { useRouter } from 'next/router';
import React from 'react';
import type { MarketplaceAppOverview } from 'types/client/marketplace';
import { MarketplaceCategory } from 'types/client/marketplace';
import appConfig from 'configs/app/config';
import type { ResourceError } from 'lib/api/resources';
import useDebounce from 'lib/hooks/useDebounce';
import useApiFetch from 'lib/hooks/useFetch';
import getQueryParamString from 'lib/router/getQueryParamString';
import { MARKETPLACE_APP } from 'stubs/marketplace';
import useMarketplaceApps from './useMarketplaceApps';
const favoriteAppsLocalStorageKey = 'favoriteApps';
......@@ -24,16 +20,6 @@ function getFavoriteApps() {
}
}
function isAppNameMatches(q: string, app: MarketplaceAppOverview) {
return app.title.toLowerCase().includes(q.toLowerCase());
}
function isAppCategoryMatches(category: string, app: MarketplaceAppOverview, favoriteApps: Array<string>) {
return category === MarketplaceCategory.ALL ||
(category === MarketplaceCategory.FAVORITES && favoriteApps.includes(app.id)) ||
app.categories.includes(category);
}
export default function useMarketplace() {
const router = useRouter();
const defaultCategoryId = getQueryParamString(router.query.category);
......@@ -44,16 +30,6 @@ export default function useMarketplace() {
const [ filterQuery, setFilterQuery ] = React.useState(defaultFilterQuery);
const [ favoriteApps, setFavoriteApps ] = React.useState<Array<string>>([]);
const apiFetch = useApiFetch();
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>(
[ 'marketplace-apps' ],
async() => apiFetch(appConfig.marketplace.configUrl || ''),
{
select: (data) => (data as Array<MarketplaceAppOverview>).sort((a, b) => a.title.localeCompare(b.title)),
placeholderData: Array(9).fill(MARKETPLACE_APP),
staleTime: Infinity,
});
const handleFavoriteClick = React.useCallback((id: string, isFavorite: boolean) => {
const favoriteApps = getFavoriteApps();
......@@ -79,9 +55,7 @@ export default function useMarketplace() {
setSelectedCategoryId(newCategory);
}, []);
const displayedApps = React.useMemo(() => {
return data?.filter(app => isAppNameMatches(debouncedFilterQuery, app) && isAppCategoryMatches(selectedCategoryId, app, favoriteApps)) || [];
}, [ selectedCategoryId, data, debouncedFilterQuery, favoriteApps ]);
const { isPlaceholderData, isError, error, data, displayedApps } = useMarketplaceApps(debouncedFilterQuery, selectedCategoryId, favoriteApps);
const categories = React.useMemo(() => {
return _unique(data?.map(app => app.categories).flat()) || [];
......
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { MarketplaceAppOverview } from 'types/client/marketplace';
import { MarketplaceCategory } from 'types/client/marketplace';
import appConfig from 'configs/app/config';
import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/hooks/useFetch';
import { MARKETPLACE_APP } from 'stubs/marketplace';
function isAppNameMatches(q: string, app: MarketplaceAppOverview) {
return app.title.toLowerCase().includes(q.toLowerCase());
}
function isAppCategoryMatches(category: string, app: MarketplaceAppOverview, favoriteApps: Array<string>) {
return category === MarketplaceCategory.ALL ||
(category === MarketplaceCategory.FAVORITES && favoriteApps.includes(app.id)) ||
app.categories.includes(category);
}
export default function useMarketplaceApps(filter: string, selectedCategoryId: string = MarketplaceCategory.ALL, favoriteApps: Array<string> = []) {
const apiFetch = useApiFetch();
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>(
[ 'marketplace-apps' ],
async() => apiFetch(appConfig.marketplace.configUrl || ''),
{
select: (data) => (data as Array<MarketplaceAppOverview>).sort((a, b) => a.title.localeCompare(b.title)),
placeholderData: Array(9).fill(MARKETPLACE_APP),
staleTime: Infinity,
});
const displayedApps = React.useMemo(() => {
return data?.filter(app => isAppNameMatches(filter, app) && isAppCategoryMatches(selectedCategoryId, app, favoriteApps)) || [];
}, [ selectedCategoryId, data, filter, favoriteApps ]);
return React.useMemo(() => ({
data,
displayedApps,
error,
isError,
isPlaceholderData,
}), [
data,
displayedApps,
error,
isError,
isPlaceholderData,
]);
}
......@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import type { FormEvent } from 'react';
import React from 'react';
import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps';
import SearchResultListItem from 'ui/searchResults/SearchResultListItem';
import SearchResultsInput from 'ui/searchResults/SearchResultsInput';
import SearchResultTableItem from 'ui/searchResults/SearchResultTableItem';
......@@ -22,6 +23,8 @@ const SearchResultsPageContent = () => {
const { data, isError, isPlaceholderData, pagination } = query;
const [ showContent, setShowContent ] = React.useState(false);
const marketplaceApps = useMarketplaceApps(debouncedSearchTerm);
React.useEffect(() => {
if (showContent) {
return;
......@@ -61,14 +64,23 @@ const SearchResultsPageContent = () => {
return <DataFetchAlert/>;
}
if (!data?.items.length) {
const hasData = data?.items.length || (pagination.page === 1 && marketplaceApps.displayedApps.length);
if (!hasData) {
return null;
}
return (
<>
<Show below="lg" ssr={ false }>
{ data.items.map((item, index) => (
{ marketplaceApps.displayedApps.map((item, index) => (
<SearchResultListItem
key={ 'actual_' + index }
data={{ type: 'app', app: item }}
searchTerm={ debouncedSearchTerm }
/>
)) }
{ data && data.items.map((item, index) => (
<SearchResultListItem
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
data={ item }
......@@ -88,7 +100,14 @@ const SearchResultsPageContent = () => {
</Tr>
</Thead>
<Tbody>
{ data.items.map((item, index) => (
{ marketplaceApps.displayedApps.map((item, index) => (
<SearchResultTableItem
key={ 'actual_' + index }
data={{ type: 'app', app: item }}
searchTerm={ debouncedSearchTerm }
/>
)) }
{ data && data.items.map((item, index) => (
<SearchResultTableItem
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
data={ item }
......@@ -108,6 +127,8 @@ const SearchResultsPageContent = () => {
return null;
}
const resultsCount = pagination.page === 1 && !data?.next_page_params ? (data?.items.length || 0) + marketplaceApps.displayedApps.length : '50+';
const text = isPlaceholderData && pagination.page === 1 ? (
<Skeleton h={ 6 } w="280px" borderRadius="full" mb={ pagination.isVisible ? 0 : 6 }/>
) : (
......@@ -115,10 +136,10 @@ const SearchResultsPageContent = () => {
<Box mb={ pagination.isVisible ? 0 : 6 } lineHeight="32px">
<span>Found </span>
<chakra.span fontWeight={ 700 }>
{ pagination.page > 1 ? 50 : data?.items.length }{ data?.next_page_params || pagination.page > 1 ? '+' : '' }
{ resultsCount }
</chakra.span>
<span> matching result{ (data?.items && data.items.length > 1) || pagination.page > 1 ? 's' : '' } for </span>
<chakra.span fontWeight={ 700 }>{ debouncedSearchTerm }</chakra.span>
<chakra.span fontWeight={ 700 }>{ debouncedSearchTerm }</chakra.span>
</Box>
)
);
......
import { Flex, Grid, Icon, Box, Text, chakra, Skeleton } from '@chakra-ui/react';
import { Flex, Grid, Icon, Image, Box, Text, chakra, Skeleton, useColorMode } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -16,13 +16,15 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import type { SearchResultAppItem } from 'ui/shared/search/utils';
import { getItemCategory, searchItemTitles } from 'ui/shared/search/utils';
import TokenLogo from 'ui/shared/TokenLogo';
interface Props {
data: SearchResultItem;
data: SearchResultItem | SearchResultAppItem;
searchTerm: string;
isLoading?: boolean;
}
......@@ -38,6 +40,8 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
});
}, [ searchTerm ]);
const { colorMode } = useColorMode();
const firstRow = (() => {
switch (data.type) {
case 'token': {
......@@ -84,7 +88,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
case 'label': {
return (
<Flex alignItems="flex-start">
<Flex alignItems="center">
<Icon as={ labelIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<LinkInternal
href={ route({ pathname: '/address/[hash]', query: { hash: data.address } }) }
......@@ -99,6 +103,42 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
);
}
case 'app': {
const title = <span dangerouslySetInnerHTML={{ __html: highlightText(data.app.title, searchTerm) }}/>;
return (
<Flex alignItems="center">
<Image
borderRadius="base"
boxSize={ 6 }
mr={ 2 }
src={ colorMode === 'dark' && data.app.logoDarkMode ? data.app.logoDarkMode : data.app.logo }
alt={ `${ data.app.title } app icon` }
/>
{ data.app.external ? (
<LinkExternal
href={ data.app.url }
fontWeight={ 700 }
wordBreak="break-all"
isLoading={ isLoading }
onClick={ handleLinkClick }
>
{ title }
</LinkExternal>
) : (
<LinkInternal
href={ route({ pathname: '/apps/[id]', query: { id: data.app.id } }) }
fontWeight={ 700 }
wordBreak="break-all"
isLoading={ isLoading }
onClick={ handleLinkClick }
>
{ title }
</LinkInternal>
) }
</Flex>
);
}
case 'block': {
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
return (
......@@ -138,7 +178,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
return (
<Grid templateColumns={ templateCols } alignItems="center" gap={ 2 }>
<Skeleton isLoaded={ !isLoading } overflow="hidden" display="flex" alignItems="center">
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden">
<Text whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 1 }/> }
......@@ -176,6 +216,21 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
</Flex>
);
}
case 'app': {
return (
<Text
overflow="hidden"
textOverflow="ellipsis"
sx={{
display: '-webkit-box',
'-webkit-box-orient': 'vertical',
'-webkit-line-clamp': '3',
}}
>
{ data.app.description }
</Text>
);
}
case 'contract':
case 'address': {
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
......@@ -198,7 +253,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
</Skeleton>
</Flex>
{ Boolean(secondRow) && (
<Box w="100%" overflow="hidden" whiteSpace="nowrap">
<Box w="100%" overflow="hidden">
{ secondRow }
</Box>
) }
......
import { chakra, Tr, Td, Text, Flex, Icon, Box, Skeleton } from '@chakra-ui/react';
import { chakra, Tr, Td, Text, Flex, Icon, Image, Box, Skeleton, useColorMode } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -16,12 +16,14 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import type { SearchResultAppItem } from 'ui/shared/search/utils';
import { getItemCategory, searchItemTitles } from 'ui/shared/search/utils';
import TokenLogo from 'ui/shared/TokenLogo';
interface Props {
data: SearchResultItem;
data: SearchResultItem | SearchResultAppItem;
searchTerm: string;
isLoading?: boolean;
}
......@@ -37,6 +39,8 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
});
}, [ searchTerm ]);
const { colorMode } = useColorMode();
const content = (() => {
switch (data.type) {
case 'token': {
......@@ -160,6 +164,55 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
);
}
case 'app': {
const title = <span dangerouslySetInnerHTML={{ __html: highlightText(data.app.title, searchTerm) }}/>;
return (
<>
<Td fontSize="sm">
<Flex alignItems="center">
<Image
borderRadius="base"
boxSize={ 6 }
mr={ 2 }
src={ colorMode === 'dark' && data.app.logoDarkMode ? data.app.logoDarkMode : data.app.logo }
alt={ `${ data.app.title } app icon` }
/>
{ data.app.external ? (
<LinkExternal
href={ data.app.url }
fontWeight={ 700 }
wordBreak="break-all"
isLoading={ isLoading }
onClick={ handleLinkClick }
>
{ title }
</LinkExternal>
) : (
<LinkInternal
href={ route({ pathname: '/apps/[id]', query: { id: data.app.id } }) }
fontWeight={ 700 }
wordBreak="break-all"
isLoading={ isLoading }
onClick={ handleLinkClick }
>
{ title }
</LinkInternal>
) }
</Flex>
</Td>
<Td fontSize="sm" verticalAlign="middle" colSpan={ 2 }>
<Text
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{ data.app.description }
</Text>
</Td>
</>
);
}
case 'block': {
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
......
import type { SearchResultItem } from 'types/api/search';
import type { MarketplaceAppOverview } from 'types/client/marketplace';
export type Category = 'token' | 'nft' | 'address' | 'app' | 'public_tag' | 'transaction' | 'block';
export type ApiCategory = 'token' | 'nft' | 'address' | 'public_tag' | 'transaction' | 'block';
export type Category = ApiCategory | 'app';
export type ItemsCategoriesMap =
Record<ApiCategory, Array<SearchResultItem>> &
Record<'app', Array<MarketplaceAppOverview>>;
export type SearchResultAppItem = {
type: 'app';
app: MarketplaceAppOverview;
}
export const searchCategories: Array<{id: Category; title: string }> = [
{ id: 'app', title: 'Apps' },
{ id: 'token', title: 'Tokens (ERC-20)' },
{ id: 'nft', title: 'NFTs (ERC-721 & 1155)' },
{ id: 'address', title: 'Addresses' },
{ id: 'app', title: 'Apps' },
{ id: 'public_tag', title: 'Public tags' },
{ id: 'transaction', title: 'Transactions' },
{ id: 'block', title: 'Blocks' },
];
export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleShort: string }> = {
app: { itemTitle: 'App', itemTitleShort: 'App' },
token: { itemTitle: 'Token', itemTitleShort: 'Token' },
nft: { itemTitle: 'NFT', itemTitleShort: 'NFT' },
address: { itemTitle: 'Address', itemTitleShort: 'Address' },
app: { itemTitle: 'App', itemTitleShort: 'App' },
public_tag: { itemTitle: 'Public tag', itemTitleShort: 'Tag' },
transaction: { itemTitle: 'Transaction', itemTitleShort: 'Txn' },
block: { itemTitle: 'Block', itemTitleShort: 'Block' },
};
export function getItemCategory(item: SearchResultItem): Category | undefined {
export function getItemCategory(item: SearchResultItem | SearchResultAppItem): Category | undefined {
switch (item.type) {
case 'address':
case 'contract': {
......@@ -43,5 +54,8 @@ export function getItemCategory(item: SearchResultItem): Category | undefined {
case 'transaction': {
return 'transaction';
}
case 'app': {
return 'app';
}
}
}
......@@ -33,7 +33,7 @@ const SearchBar = ({ isHomepage }: Props) => {
const recentSearchKeywords = getRecentSearchKeywords();
const { searchTerm, handleSearchTermChange, query, pathname } = useSearchQuery();
const { searchTerm, debouncedSearchTerm, handleSearchTermChange, query, pathname } = useSearchQuery();
const handleSubmit = React.useCallback((event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
......@@ -142,7 +142,12 @@ const SearchBar = ({ isHomepage }: Props) => {
<SearchBarRecentKeywords onClick={ handleSearchTermChange } onClear={ onClose }/>
) }
{ searchTerm.trim().length > 0 && (
<SearchBarSuggest query={ query } searchTerm={ searchTerm } onItemClick={ handleItemClick } containerId={ SCROLL_CONTAINER_ID }/>
<SearchBarSuggest
query={ query }
searchTerm={ debouncedSearchTerm }
onItemClick={ handleItemClick }
containerId={ SCROLL_CONTAINER_ID }
/>
) }
</Box>
</PopoverBody>
......
......@@ -3,15 +3,15 @@ import throttle from 'lodash/throttle';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import type { SearchResultItem } from 'types/api/search';
import useIsMobile from 'lib/hooks/useIsMobile';
import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps';
import TextAd from 'ui/shared/ad/TextAd';
import ContentLoader from 'ui/shared/ContentLoader';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import type { Category } from 'ui/shared/search/utils';
import type { ItemsCategoriesMap } from 'ui/shared/search/utils';
import { getItemCategory, searchCategories } from 'ui/shared/search/utils';
import SearchBarSuggestApp from './SearchBarSuggestApp';
import SearchBarSuggestItem from './SearchBarSuggestItem';
interface Props {
......@@ -24,6 +24,8 @@ interface Props {
const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props) => {
const isMobile = useIsMobile();
const marketplaceApps = useMarketplaceApps(searchTerm);
const categoriesRefs = React.useRef<Array<HTMLParagraphElement>>([]);
const tabsRef = React.useRef<HTMLDivElement>(null);
......@@ -61,10 +63,10 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
}, [ containerId, handleScroll ]);
const itemsGroups = React.useMemo(() => {
if (!query.data?.items) {
if (!query.data?.items && !marketplaceApps.displayedApps) {
return {};
}
const map: Partial<Record<Category, Array<SearchResultItem>>> = {};
const map: Partial<ItemsCategoriesMap> = {};
query.data?.items.forEach(item => {
const cat = getItemCategory(item);
if (cat) {
......@@ -75,8 +77,11 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
}
}
});
if (marketplaceApps.displayedApps.length) {
map.app = marketplaceApps.displayedApps;
}
return map;
}, [ query.data?.items ]);
}, [ query.data?.items, marketplaceApps.displayedApps ]);
const scrollToCategory = React.useCallback((index: number) => () => {
setTabIndex(index);
......@@ -91,7 +96,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
const bgColor = useColorModeValue('white', 'gray.900');
const content = (() => {
if (query.isLoading) {
if (query.isLoading || marketplaceApps.isPlaceholderData) {
return <ContentLoader text="We are searching, please wait... " fontSize="sm"/>;
}
......@@ -129,9 +134,12 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
>
{ cat.title }
</Text>
{ itemsGroups[cat.id]?.map((item, index) =>
{ cat.id !== 'app' && itemsGroups[cat.id]?.map((item, index) =>
<SearchBarSuggestItem key={ index } data={ item } isMobile={ isMobile } searchTerm={ searchTerm } onClick={ onItemClick }/>,
) }
{ cat.id === 'app' && itemsGroups[cat.id]?.map((item, index) =>
<SearchBarSuggestApp key={ index } data={ item } isMobile={ isMobile } searchTerm={ searchTerm } onClick={ onItemClick }/>,
) }
</Element>
);
}) }
......
import { Link, Icon, Image, Flex, Text, useColorModeValue } from '@chakra-ui/react';
import NextLink from 'next/link';
import React from 'react';
import type { MarketplaceAppOverview } from 'types/client/marketplace';
import arrowIcon from 'icons/arrows/north-east.svg';
import highlightText from 'lib/highlightText';
import SearchBarSuggestItemLink from './SearchBarSuggestItemLink';
interface Props {
data: MarketplaceAppOverview;
isMobile: boolean | undefined;
searchTerm: string;
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
}
const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) => {
const logo = (
<Image
borderRadius="base"
boxSize={ 6 }
src={ useColorModeValue(data.logo, data.logoDarkMode || data.logo) }
alt={ `${ data.title } app icon` }
/>
);
const content = (() => {
if (isMobile) {
return (
<>
<Flex alignItems="center">
{ logo }
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
ml={ 2 }
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.title, searchTerm) }}/>
</Text>
{ data.external && <Icon as={ arrowIcon } boxSize={ 4 } verticalAlign="middle"/> }
</Flex>
<Text
variant="secondary"
overflow="hidden"
textOverflow="ellipsis"
sx={{
display: '-webkit-box',
'-webkit-box-orient': 'vertical',
'-webkit-line-clamp': '3',
}}
>
{ data.description }
</Text>
</>
);
}
return (
<Flex gap={ 2 } alignItems="center">
{ logo }
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
w="200px"
flexShrink={ 0 }
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.title, searchTerm) }}/>
</Text>
<Text
variant="secondary"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
flexGrow={ 1 }
>
{ data.description }
</Text>
{ data.external && <Icon as={ arrowIcon } boxSize={ 4 } verticalAlign="middle"/> }
</Flex>
);
})();
if (data.external) {
return (
<Link href={ data.url } target="_blank" cursor="auto" _hover={{ textDecoration: 'none' }}>
<SearchBarSuggestItemLink onClick={ onClick }>
{ content }
</SearchBarSuggestItemLink>
</Link>
);
}
return (
<NextLink href={{ pathname: '/apps/[id]', query: { id: data.id } }} passHref legacyBehavior>
<SearchBarSuggestItemLink onClick={ onClick }>
{ content }
</SearchBarSuggestItemLink>
</NextLink>
);
};
export default React.memo(SearchBarSuggestItem);
import { chakra, useColorModeValue } from '@chakra-ui/react';
import type { LinkProps as NextLinkProps } from 'next/link';
import NextLink from 'next/link';
import { route } from 'nextjs-routes';
......@@ -8,6 +7,7 @@ import type { SearchResultItem } from 'types/api/search';
import SearchBarSuggestAddress from './SearchBarSuggestAddress';
import SearchBarSuggestBlock from './SearchBarSuggestBlock';
import SearchBarSuggestItemLink from './SearchBarSuggestItemLink';
import SearchBarSuggestLabel from './SearchBarSuggestLabel';
import SearchBarSuggestToken from './SearchBarSuggestToken';
import SearchBarSuggestTx from './SearchBarSuggestTx';
......@@ -64,28 +64,9 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) =>
return (
<NextLink href={ url as NextLinkProps['href'] } passHref legacyBehavior>
<chakra.a
py={ 3 }
px={ 1 }
display="flex"
flexDir="column"
rowGap={ 2 }
borderColor="divider"
borderBottomWidth="1px"
_last={{
borderBottomWidth: '0',
}}
_hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'),
}}
fontSize="sm"
_first={{
mt: 2,
}}
onClick={ onClick }
>
<SearchBarSuggestItemLink onClick={ onClick }>
{ content }
</chakra.a>
</SearchBarSuggestItemLink>
</NextLink>
);
};
......
import { chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
type Props = {
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
children: React.ReactNode;
}
const SearchBarSuggestItemLink = ({ onClick, children }: Props) => {
return (
<chakra.a
py={ 3 }
px={ 1 }
display="flex"
flexDir="column"
rowGap={ 2 }
borderColor="divider"
borderBottomWidth="1px"
_last={{
borderBottomWidth: '0',
}}
_hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'),
}}
fontSize="sm"
_first={{
mt: 2,
}}
onClick={ onClick }
>
{ children }
</chakra.a>
);
};
export default SearchBarSuggestItemLink;
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