From 5bf09c83dfe503b30a8093cc1793a021608dfa15 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 3 Oct 2024 14:38:24 -0400 Subject: [PATCH] Redesign search chain filters --- package.json | 1 + src/components/search/SearchFilterBar.tsx | 187 +++++++++++------- .../chains/queries/useScrapedChains.ts | 2 +- src/features/chains/utils.ts | 4 +- src/store.ts | 15 +- src/styles/Color.ts | 4 +- yarn.lock | 3 +- 7 files changed, 135 insertions(+), 81 deletions(-) diff --git a/package.json b/package.json index e352dc6..858e646 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@tanstack/react-query": "^5.35.5", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", + "clsx": "^2.1.1", "ethers": "^5.7.2", "formik": "^2.2.9", "graphql": "^16.6.0", diff --git a/src/components/search/SearchFilterBar.tsx b/src/components/search/SearchFilterBar.tsx index e4223c2..f825afd 100644 --- a/src/components/search/SearchFilterBar.tsx +++ b/src/components/search/SearchFilterBar.tsx @@ -1,12 +1,20 @@ -import Image from 'next/image'; +import clsx from 'clsx'; import Link from 'next/link'; import { useState } from 'react'; import { ChainMetadata } from '@hyperlane-xyz/sdk'; -import { ChainSearchMenu, Modal, Popover } from '@hyperlane-xyz/widgets'; +import { trimToLength } from '@hyperlane-xyz/utils'; +import { + ChainSearchMenu, + GearIcon, + IconButton, + Modal, + Popover, + XIcon, +} from '@hyperlane-xyz/widgets'; import { useScrapedEvmChains } from '../../features/chains/queries/useScrapedChains'; -import GearIcon from '../../images/icons/gear.svg'; +import { getChainDisplayName } from '../../features/chains/utils'; import { useMultiProvider } from '../../store'; import { Color } from '../../styles/Color'; import { SolidButton } from '../buttons/SolidButton'; @@ -37,19 +45,11 @@ export function SearchFilterBar({ }: Props) { return (
- + - -
- Settings + +
+
@@ -68,52 +68,64 @@ export function SearchFilterBar({ function ChainSelector({ text, - header, value, onChangeValue, - position, }: { text: string; - header: string; - value: string | null; // comma separated list of checked chains + value: ChainId | null; onChangeValue: (value: string | null) => void; - position?: string; }) { const multiProvider = useMultiProvider(); const { chains } = useScrapedEvmChains(multiProvider); - // const [checkedChain, setCheckedChain] = useState(value); + const [showModal, setShowModal] = useState(false); + const closeModal = () => { + setShowModal(false); + }; const onClickChain = (c: ChainMetadata) => { - // setCheckedChain(c.chainId); onChangeValue(c.chainId.toString()); + closeModal(); }; - const [showModal, setShowModal] = useState(false); - const closeModal = () => { - setShowModal(false); + const onClear = () => { + onChangeValue(null); }; + const chainName = value + ? trimToLength(getChainDisplayName(multiProvider, value, true), 12) + : undefined; + return ( - <> +
- + {value && } + - +
); } @@ -137,50 +149,79 @@ function DatetimeSelector({ setEndTime(null); }; + const onClickDirectClear = () => { + onClickClear(); + onChangeStartValue(null); + onChangeEndValue(null); + }; + const onClickApply = (closeDropdown?: () => void) => { onChangeStartValue(startTime); onChangeEndValue(endTime); if (closeDropdown) closeDropdown(); }; + const hasValue = !!startTime || !!endTime; + return ( - - Time - - - } - buttonClassname="text-sm px-1 sm:px-2.5 py-0.5 flex items-center justify-center rounded-full bg-pink-500 hover:opacity-80 active:opacity-70 transition-all" - panelClassname="w-60" - > - {({ close }) => ( -
-
-

Time Range

-
- - Clear - +
+ + Time + {!hasValue && ( + + )} + + } + buttonClassname={clsx( + 'text-sm px-2 sm:px-3 py-1 flex items-center justify-center font-medium border border-pink-500 rounded-full hover:opacity-80 active:opacity-70 transition-all', + hasValue ? ' bg-pink-500 text-white pr-7 sm:pr-8' : 'text-pink-500', + )} + panelClassname="w-60" + > + {({ close }) => ( +
+
+

Time Range

+
+ + Clear + +
+
+

Start Time

+ +

End Time

+ +
+ onClickApply(close)} + > + Apply +
-
-

Start Time

- -

End Time

- -
- onClickApply(close)}> - Apply - -
- )} - + )} + + {hasValue && } +
+ ); +} + +function ClearButton({ onClick }: { onClick: () => void }) { + return ( +
+ + + +
); } diff --git a/src/features/chains/queries/useScrapedChains.ts b/src/features/chains/queries/useScrapedChains.ts index b4040d7..51283a9 100644 --- a/src/features/chains/queries/useScrapedChains.ts +++ b/src/features/chains/queries/useScrapedChains.ts @@ -42,7 +42,7 @@ export function useScrapedEvmChains(multiProvider: MultiProvider) { // https://github.com/hyperlane-xyz/hyperlane-explorer/issues/61 const scrapedEvmChains = objFilter( multiProvider.metadata, - (chainName, chainMetadata): chainMetadata is ChainMetadata => + (_, chainMetadata): chainMetadata is ChainMetadata => isEvmChain(multiProvider, chainMetadata.chainId) && !isPiChain(multiProvider, scrapedChains, chainMetadata.chainId) && !isUnscrapedDbChain(multiProvider, chainMetadata.chainId), diff --git a/src/features/chains/utils.ts b/src/features/chains/utils.ts index 8469455..7f97cba 100644 --- a/src/features/chains/utils.ts +++ b/src/features/chains/utils.ts @@ -23,9 +23,9 @@ export function getChainDisplayName( chainOrDomainId?: ChainId | DomainId, shortName = false, fallbackToId = true, -) { +): string { const metadata = multiProvider.tryGetChainMetadata(chainOrDomainId || 0); - if (!metadata) return fallbackToId && chainOrDomainId ? chainOrDomainId : 'Unknown'; + if (!metadata) return fallbackToId && chainOrDomainId ? chainOrDomainId.toString() : 'Unknown'; const displayName = shortName ? metadata.displayNameShort : metadata.displayName; return toTitleCase(displayName || metadata.displayName || metadata.name); } diff --git a/src/store.ts b/src/store.ts index bad9208..9e2e476 100644 --- a/src/store.ts +++ b/src/store.ts @@ -2,7 +2,8 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { GithubRegistry, IRegistry } from '@hyperlane-xyz/registry'; -import { ChainMap, MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; +import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { config } from './consts/config'; import { ChainConfig } from './features/chains/chainConfig'; @@ -91,5 +92,15 @@ async function buildMultiProvider(registry: IRegistry, customChainConfigs: Chain // TODO improve interface so this pre-cache isn't required await registry.listRegistryContent(); const registryChainMetadata = await registry.getMetadata(); - return new MultiProvider({ ...registryChainMetadata, ...customChainConfigs }); + // TODO have the registry do this automatically + const metadataWithLogos = await promiseObjAll( + objMap( + registryChainMetadata, + async (chainName, metadata): Promise => ({ + ...metadata, + logoURI: (await registry.getChainLogoUri(chainName)) || undefined, + }), + ), + ); + return new MultiProvider({ ...metadataWithLogos, ...customChainConfigs }); } diff --git a/src/styles/Color.ts b/src/styles/Color.ts index e507117..bf4db16 100644 --- a/src/styles/Color.ts +++ b/src/styles/Color.ts @@ -10,7 +10,7 @@ export const Color = { lightGray: themeColors.gray[200], primary: themeColors.blue[500], accent: themeColors.pink[500], - blue: themeColors.blue[200], - pink: themeColors.pink[200], + blue: themeColors.blue[500], + pink: themeColors.pink[500], red: themeColors.red[500], } as const; diff --git a/yarn.lock b/yarn.lock index cb40ddf..7fc31bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2189,6 +2189,7 @@ __metadata: autoprefixer: "npm:^10.4.15" bignumber.js: "npm:^9.1.2" buffer: "npm:^6.0.3" + clsx: "npm:^2.1.1" eslint: "npm:^8.41.0" eslint-config-next: "npm:^13.4.19" eslint-config-prettier: "npm:^8.8.0" @@ -5679,7 +5680,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^2.0.0": +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" checksum: cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919