diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index ea628a029..fdd3a614d 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2538,6 +2538,9 @@ "noNFTs": { "message": "No NFTs yet" }, + "noNetworksFound": { + "message": "No networks found for the given search query" + }, "noSnaps": { "message": "You don't have any snaps installed." }, diff --git a/ui/components/multichain/network-list-item/network-list-item.js b/ui/components/multichain/network-list-item/network-list-item.js index 442568a69..2ff07e9b2 100644 --- a/ui/components/multichain/network-list-item/network-list-item.js +++ b/ui/components/multichain/network-list-item/network-list-item.js @@ -47,6 +47,7 @@ export const NetworkListItem = ({ name, iconSrc, selected = false, + focus = true, onClick, onDeleteClick, }) => { @@ -54,10 +55,10 @@ export const NetworkListItem = ({ const networkRef = useRef(); useEffect(() => { - if (networkRef.current && selected) { + if (networkRef.current && focus) { networkRef.current.focus(); } - }, [networkRef, selected]); + }, [networkRef, focus]); return ( { const lineaMainnetReleased = useSelector(isLineaMainnetNetworkReleased); + const showSearch = nonTestNetworks.length > 3; + useEffect(() => { if (currentlyOnTestNetwork) { dispatch(setShowTestNetworks(currentlyOnTestNetwork)); } }, [dispatch, currentlyOnTestNetwork]); + const [searchQuery, setSearchQuery] = useState(''); + + let searchResults = [...nonTestNetworks]; + const isSearching = searchQuery !== ''; + + if (isSearching) { + const fuse = new Fuse(searchResults, { + threshold: 0.2, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + shouldSort: true, + keys: ['nickname', 'chainId', 'ticker'], + }); + fuse.setCollection(searchResults); + const fuseResults = fuse.search(searchQuery); + // Ensure order integrity with original list + searchResults = searchResults.filter((network) => + fuseResults.includes(network), + ); + } const generateMenuItems = (desiredNetworks) => { return desiredNetworks.map((network, index) => { @@ -98,6 +127,7 @@ export const NetworkListMenu = ({ onClose }) => { iconSrc={network?.rpcPrefs?.imageUrl} key={`${network.id || network.chainId}-${index}`} selected={isCurrentNetwork} + focus={isCurrentNetwork && !showSearch} onClick={() => { dispatch(toggleNetworkMenu()); if (network.providerType) { @@ -162,8 +192,40 @@ export const NetworkListMenu = ({ onClose }) => { {t('networkMenuHeading')} <> + {showSearch ? ( + + setSearchQuery(e.target.value)} + clearButtonOnClick={() => setSearchQuery('')} + clearButtonProps={{ + size: Size.SM, + }} + inputProps={{ autoFocus: true }} + /> + + ) : null} - {generateMenuItems(nonTestNetworks)} + {searchResults.length === 0 && isSearching ? ( + + {t('noNetworksFound')} + + ) : ( + generateMenuItems(searchResults) + )} { it('displays important controls', () => { - const { getByText } = render(); + const { getByText, getByPlaceholderText } = render(); expect(getByText('Add network')).toBeInTheDocument(); expect(getByText('Show test networks')).toBeInTheDocument(); + expect(getByPlaceholderText('Search')).toBeInTheDocument(); }); it('renders mainnet item', () => { @@ -99,4 +100,15 @@ describe('NetworkListMenu', () => { ).textContent; expect(selectedNodeText).toStrictEqual('Custom Mainnet RPC'); }); + + it('narrows down search results', () => { + const { queryByText, getByPlaceholderText } = render(); + + expect(queryByText('Chain 5')).toBeInTheDocument(); + + const searchBox = getByPlaceholderText('Search'); + fireEvent.change(searchBox, { target: { value: 'Main' } }); + + expect(queryByText('Chain 5')).not.toBeInTheDocument(); + }); });