mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
highlight search term
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export default function escapeRegExp(string: string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import escapeRegExp from 'lib/escapeRegExp';
|
||||
|
||||
export default function highlightText(text: string, query: string) {
|
||||
const regex = new RegExp('(' + escapeRegExp(query) + ')', 'gi');
|
||||
return text.replace(regex, '<mark>$1</mark>');
|
||||
}
|
||||
@@ -9,6 +9,10 @@ const global = (props: StyleFunctionProps) => ({
|
||||
...getDefaultTransitionProps(),
|
||||
'-webkit-tap-highlight-color': 'transparent',
|
||||
},
|
||||
mark: {
|
||||
bgColor: 'yellow.200',
|
||||
color: 'inherit',
|
||||
},
|
||||
'svg *::selection': {
|
||||
color: 'none',
|
||||
background: 'none',
|
||||
|
||||
+11
-21
@@ -2,7 +2,6 @@ import { Box, chakra, Table, Tbody, Tr, Th, Skeleton, Show, Hide } from '@chakra
|
||||
import type { FormEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import link from 'lib/link/link';
|
||||
import SearchResultListItem from 'ui/searchResults/SearchResultListItem';
|
||||
import SearchResultTableItem from 'ui/searchResults/SearchResultTableItem';
|
||||
@@ -21,7 +20,6 @@ import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery';
|
||||
const SearchResultsPageContent = () => {
|
||||
const { query, searchTerm, handleSearchTermChange } = useSearchQuery(true);
|
||||
const { data, isError, isLoading, pagination, isPaginationVisible } = query;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleSubmit = React.useCallback((event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -40,10 +38,10 @@ const SearchResultsPageContent = () => {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={ 6 } w="280px" borderRadius="full" mb={ 6 }/>
|
||||
<Show below="lg" ssr={ false }>
|
||||
<Show below="lg">
|
||||
<SkeletonList/>
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
<Hide below="lg">
|
||||
<SkeletonTable columns={ [ '33%', '34%', '33%' ] }/>
|
||||
</Hide>
|
||||
</Box>
|
||||
@@ -65,22 +63,14 @@ const SearchResultsPageContent = () => {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<>
|
||||
{ text }
|
||||
<ActionBar>
|
||||
<Pagination { ...pagination } ml="auto"/>
|
||||
</ActionBar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionBar mt={ -6 }>
|
||||
{ text }
|
||||
<Pagination { ...pagination }/>
|
||||
</ActionBar>
|
||||
<>
|
||||
<Box display={{ base: 'block', lg: 'none' }}>{ text }</Box>
|
||||
<ActionBar mt={{ base: 0, lg: -6 }}>
|
||||
<Box display={{ base: 'none', lg: 'block' }}>{ text }</Box>
|
||||
<Pagination { ...pagination }/>
|
||||
</ActionBar>
|
||||
</>
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -90,7 +80,7 @@ const SearchResultsPageContent = () => {
|
||||
{ data.items.length > 0 && (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }>
|
||||
{ data.items.map((item, index) => <SearchResultListItem key={ index } data={ item }/>) }
|
||||
{ data.items.map((item, index) => <SearchResultListItem key={ index } data={ item } searchTerm={ searchTerm }/>) }
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
<Table variant="simple" size="md" fontWeight={ 500 }>
|
||||
@@ -102,7 +92,7 @@ const SearchResultsPageContent = () => {
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.items.map((item, index) => <SearchResultTableItem key={ index } data={ item }/>) }
|
||||
{ data.items.map((item, index) => <SearchResultTableItem key={ index } data={ item } searchTerm={ searchTerm }/>) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Hide>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Text, Link, Flex, Icon } from '@chakra-ui/react';
|
||||
import { Text, Link, Flex, Icon, Box, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { SearchResultItem } from 'types/api/search';
|
||||
|
||||
import blockIcon from 'icons/block.svg';
|
||||
import txIcon from 'icons/transactions.svg';
|
||||
import highlightText from 'lib/highlightText';
|
||||
import link from 'lib/link/link';
|
||||
import Address from 'ui/shared/address/Address';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
@@ -15,18 +16,21 @@ import TokenLogo from 'ui/shared/TokenLogo';
|
||||
|
||||
interface Props {
|
||||
data: SearchResultItem;
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
const SearchResultListItem = ({ data }: Props) => {
|
||||
const SearchResultListItem = ({ data, searchTerm }: Props) => {
|
||||
|
||||
const firstRow = (() => {
|
||||
switch (data.type) {
|
||||
case 'token': {
|
||||
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
|
||||
|
||||
return (
|
||||
<Flex alignItems="center">
|
||||
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name }/>
|
||||
<Link ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 }>
|
||||
{ data.name }{ data.symbol ? ` (${ data.symbol })` : '' }
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
|
||||
</Link>
|
||||
</Flex>
|
||||
);
|
||||
@@ -34,20 +38,24 @@ const SearchResultListItem = ({ data }: Props) => {
|
||||
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<Address>
|
||||
<AddressIcon hash={ data.address }/>
|
||||
<AddressLink hash={ data.address } ml={ 2 } fontWeight={ 700 }/>
|
||||
<AddressIcon hash={ data.address } mr={ 2 }/>
|
||||
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" whiteSpace="nowrap" overflow="hidden">
|
||||
<AddressLink hash={ data.address } fontWeight={ 700 } display="block" w="100%"/>
|
||||
</Box>
|
||||
</Address>
|
||||
);
|
||||
}
|
||||
|
||||
case 'block': {
|
||||
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<Flex alignItems="center">
|
||||
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<Link fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }>
|
||||
{ data.block_number }
|
||||
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
|
||||
</Link>
|
||||
</Flex>
|
||||
);
|
||||
@@ -57,9 +65,9 @@ const SearchResultListItem = ({ data }: Props) => {
|
||||
return (
|
||||
<Flex alignItems="center" overflow="hidden">
|
||||
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<Address>
|
||||
<AddressLink hash={ data.tx_hash } type="transaction" fontWeight={ 700 }/>
|
||||
</Address>
|
||||
<chakra.mark display="block" overflow="hidden">
|
||||
<AddressLink hash={ data.tx_hash } type="transaction" fontWeight={ 700 } display="block"/>
|
||||
</chakra.mark>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -74,13 +82,17 @@ const SearchResultListItem = ({ data }: Props) => {
|
||||
);
|
||||
}
|
||||
case 'block': {
|
||||
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<HashStringShortenDynamic hash={ data.block_hash }/>
|
||||
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" w="100%" whiteSpace="nowrap" overflow="hidden">
|
||||
<HashStringShortenDynamic hash={ data.block_hash }/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
return data.name ? <Text>{ data.name }</Text> : null;
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
return data.name ? <span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/> : null;
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SearchResultItem } from 'types/api/search';
|
||||
|
||||
import blockIcon from 'icons/block.svg';
|
||||
import txIcon from 'icons/transactions.svg';
|
||||
import highlightText from 'lib/highlightText';
|
||||
import link from 'lib/link/link';
|
||||
import Address from 'ui/shared/address/Address';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
@@ -14,22 +15,22 @@ import TokenLogo from 'ui/shared/TokenLogo';
|
||||
|
||||
interface Props {
|
||||
data: SearchResultItem;
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
const SearchResultTableItem = ({ data }: Props) => {
|
||||
const SearchResultTableItem = ({ data, searchTerm }: Props) => {
|
||||
|
||||
const content = (() => {
|
||||
switch (data.type) {
|
||||
case 'token': {
|
||||
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
|
||||
return (
|
||||
<>
|
||||
<Td fontSize="sm">
|
||||
<Flex alignItems="center">
|
||||
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name }/>
|
||||
<Link ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 }>
|
||||
<span>
|
||||
{ data.name }{ data.symbol ? ` (${ data.symbol })` : '' }
|
||||
</span>
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Td>
|
||||
@@ -45,16 +46,21 @@ const SearchResultTableItem = ({ data }: Props) => {
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
if (data.name) {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<>
|
||||
<Td fontSize="sm">
|
||||
<Address>
|
||||
<AddressIcon hash={ data.address }/>
|
||||
<AddressLink fontWeight={ 700 } ml={ 2 } hash={ data.address }/>
|
||||
</Address>
|
||||
<Flex alignItems="center" overflow="hidden">
|
||||
<AddressIcon hash={ data.address } mr={ 2 }/>
|
||||
<Link href={ link('address_index', { id: data.address }) } fontWeight={ 700 } overflow="hidden" whiteSpace="nowrap">
|
||||
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
|
||||
<HashStringShortenDynamic hash={ data.address }/>
|
||||
</Box>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td fontSize="sm" verticalAlign="middle">
|
||||
{ data.name }
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/>
|
||||
</Td>
|
||||
</>
|
||||
);
|
||||
@@ -63,26 +69,30 @@ const SearchResultTableItem = ({ data }: Props) => {
|
||||
return (
|
||||
<Td colSpan={ 2 } fontSize="sm">
|
||||
<Address>
|
||||
<AddressIcon hash={ data.address }/>
|
||||
<AddressLink hash={ data.address } ml={ 2 } type="address" fontWeight={ 700 }/>
|
||||
<AddressIcon hash={ data.address } mr={ 2 }/>
|
||||
<mark>
|
||||
<AddressLink hash={ data.address } type="address" fontWeight={ 700 }/>
|
||||
</mark>
|
||||
</Address>
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
|
||||
case 'block': {
|
||||
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Td fontSize="sm">
|
||||
<Flex alignItems="center">
|
||||
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<Link fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }>
|
||||
{ data.block_number }
|
||||
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td fontSize="sm" verticalAlign="middle">
|
||||
<Box overflow="hidden" whiteSpace="nowrap">
|
||||
<Box overflow="hidden" whiteSpace="nowrap" as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
|
||||
<HashStringShortenDynamic hash={ data.block_hash }/>
|
||||
</Box>
|
||||
</Td>
|
||||
@@ -95,9 +105,9 @@ const SearchResultTableItem = ({ data }: Props) => {
|
||||
<Td colSpan={ 2 } fontSize="sm">
|
||||
<Flex alignItems="center">
|
||||
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<Address>
|
||||
<mark>
|
||||
<AddressLink hash={ data.tx_hash } type="transaction" fontWeight={ 700 }/>
|
||||
</Address>
|
||||
</mark>
|
||||
</Flex>
|
||||
</Td>
|
||||
);
|
||||
|
||||
@@ -77,7 +77,7 @@ const SearchBar = ({ isHomepage, withShadow }: Props) => {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent w={ `${ menuWidth.current }px` } maxH={{ base: '300px', lg: '500px' }} overflowY="scroll" ref={ menuRef }>
|
||||
<PopoverBody py={ 6 }>
|
||||
<SearchBarSuggest query={ query }/>
|
||||
<SearchBarSuggest query={ query } searchTerm={ searchTerm }/>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -11,9 +11,10 @@ import SearchBarSuggestItem from './SearchBarSuggestItem';
|
||||
|
||||
interface Props {
|
||||
query: UseQueryResult<SearchResult>;
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
const SearchBarSuggest = ({ query }: Props) => {
|
||||
const SearchBarSuggest = ({ query, searchTerm }: Props) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const content = (() => {
|
||||
@@ -31,7 +32,7 @@ const SearchBarSuggest = ({ query }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Box fontWeight={ 500 } fontSize="sm">Found <Text fontWeight={ 700 } as="span">{ num }</Text> matching results</Box>
|
||||
{ query.data.items.map((item, index) => <SearchBarSuggestItem key={ index } data={ item } isMobile={ isMobile }/>) }
|
||||
{ query.data.items.map((item, index) => <SearchBarSuggestItem key={ index } data={ item } isMobile={ isMobile } searchTerm={ searchTerm }/>) }
|
||||
</>
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { chakra, Text, Flex, useColorModeValue, Icon } from '@chakra-ui/react';
|
||||
import { chakra, Text, Flex, useColorModeValue, Icon, Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { SearchResultItem } from 'types/api/search';
|
||||
|
||||
import blockIcon from 'icons/block.svg';
|
||||
import txIcon from 'icons/transactions.svg';
|
||||
import highlightText from 'lib/highlightText';
|
||||
import link from 'lib/link/link';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
@@ -13,9 +14,10 @@ import TokenLogo from 'ui/shared/TokenLogo';
|
||||
interface Props {
|
||||
data: SearchResultItem;
|
||||
isMobile: boolean | undefined;
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
const SearchBarSuggestItem = ({ data, isMobile }: Props) => {
|
||||
const SearchBarSuggestItem = ({ data, isMobile, searchTerm }: Props) => {
|
||||
|
||||
const url = (() => {
|
||||
switch (data.type) {
|
||||
@@ -38,12 +40,13 @@ const SearchBarSuggestItem = ({ data, isMobile }: Props) => {
|
||||
const firstRow = (() => {
|
||||
switch (data.type) {
|
||||
case 'token': {
|
||||
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
|
||||
|
||||
return (
|
||||
<>
|
||||
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name } flexShrink={ 0 }/>
|
||||
<Text fontWeight={ 700 } ml={ 2 } w="200px" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" flexShrink={ 0 }>
|
||||
<span>{ data.name }</span>
|
||||
{ data.symbol && <span> ({ data.symbol })</span> }
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
|
||||
</Text>
|
||||
{ !isMobile && (
|
||||
<Text overflow="hidden" whiteSpace="nowrap" ml={ 2 } variant="secondary">
|
||||
@@ -55,27 +58,29 @@ const SearchBarSuggestItem = ({ data, isMobile }: Props) => {
|
||||
}
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<>
|
||||
<AddressIcon hash={ data.address } mr={ 2 }/>
|
||||
<chakra.span overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
|
||||
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
|
||||
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
|
||||
</chakra.span>
|
||||
{ !isMobile && (
|
||||
</Box>
|
||||
{ !isMobile && data.name && (
|
||||
<Text variant="secondary" ml={ 2 }>
|
||||
{ data.name }
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/>
|
||||
</Text>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'block': {
|
||||
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<>
|
||||
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<chakra.span fontWeight={ 700 }>{ data.block_number }</chakra.span>
|
||||
<Box fontWeight={ 700 } as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
|
||||
{ !isMobile && (
|
||||
<Text variant="secondary" overflow="hidden" whiteSpace="nowrap" ml={ 2 }>
|
||||
<Text variant="secondary" overflow="hidden" whiteSpace="nowrap" ml={ 2 } as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
|
||||
<HashStringShortenDynamic hash={ data.block_hash } isTooltipDisabled/>
|
||||
</Text>
|
||||
) }
|
||||
@@ -86,9 +91,9 @@ const SearchBarSuggestItem = ({ data, isMobile }: Props) => {
|
||||
return (
|
||||
< >
|
||||
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<chakra.span overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
|
||||
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
|
||||
<HashStringShortenDynamic hash={ data.tx_hash } isTooltipDisabled/>
|
||||
</chakra.span>
|
||||
</chakra.mark>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -109,17 +114,23 @@ const SearchBarSuggestItem = ({ data, isMobile }: Props) => {
|
||||
);
|
||||
}
|
||||
case 'block': {
|
||||
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden">
|
||||
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden" as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
|
||||
<HashStringShortenDynamic hash={ data.block_hash } isTooltipDisabled/>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
if (!data.name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden">
|
||||
{ data.name }
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user