|
|
|
@ -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 ( |
|
|
|
|
<div className="flex items-center space-x-2 md:space-x-4"> |
|
|
|
|
<ChainSelector |
|
|
|
|
text="Origin" |
|
|
|
|
header="Origin Chains" |
|
|
|
|
value={originChain} |
|
|
|
|
onChangeValue={onChangeOrigin} |
|
|
|
|
position="-right-32" |
|
|
|
|
/> |
|
|
|
|
<ChainSelector text="Origin" value={originChain} onChangeValue={onChangeOrigin} /> |
|
|
|
|
<ChainSelector |
|
|
|
|
text="Destination" |
|
|
|
|
header="Destination Chains" |
|
|
|
|
value={destinationChain} |
|
|
|
|
onChangeValue={onChangeDestination} |
|
|
|
|
position="-right-28" |
|
|
|
|
/> |
|
|
|
|
<DatetimeSelector |
|
|
|
|
startValue={startTimestamp} |
|
|
|
@ -57,9 +57,9 @@ export function SearchFilterBar({ |
|
|
|
|
endValue={endTimestamp} |
|
|
|
|
onChangeEndValue={onChangeEndTimestamp} |
|
|
|
|
/> |
|
|
|
|
<Link href="/settings" title="View explorer settings"> |
|
|
|
|
<div className="p-1.5 bg-pink-500 rounded-full active:opacity-90 hover:rotate-90 transition-all"> |
|
|
|
|
<Image src={GearIcon} width={16} height={16} className="invert" alt="Settings" /> |
|
|
|
|
<Link href="/settings" title="View explorer settings" className="hidden sm:block"> |
|
|
|
|
<div className="active:opacity-90 hover:rotate-90 transition-all"> |
|
|
|
|
<GearIcon color={Color.pink} height={18} width={18} /> |
|
|
|
|
</div> |
|
|
|
|
</Link> |
|
|
|
|
</div> |
|
|
|
@ -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<ChainId|null>(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 ( |
|
|
|
|
<> |
|
|
|
|
<div className="relative"> |
|
|
|
|
<button |
|
|
|
|
type="button" |
|
|
|
|
className="text-sm sm:min-w-[5.8rem] 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" |
|
|
|
|
className={clsx( |
|
|
|
|
'text-sm sm:min-w-[5.8rem] px-1.5 sm:px-2.5 py-1 flex items-center justify-center font-medium rounded-full border border-pink-500 hover:opacity-80 active:opacity-70 transition-all', |
|
|
|
|
value ? 'bg-pink-500 text-white pr-7 sm:pr-8' : 'text-pink-500', |
|
|
|
|
)} |
|
|
|
|
onClick={() => setShowModal(!showModal)} |
|
|
|
|
> |
|
|
|
|
<span className="text-white font-medium py-px">{text}</span> |
|
|
|
|
<span>{chainName || text} </span> |
|
|
|
|
{!value && ( |
|
|
|
|
<ChevronIcon |
|
|
|
|
direction="s" |
|
|
|
|
width={9} |
|
|
|
|
height={5} |
|
|
|
|
classes="ml-2 opacity-80" |
|
|
|
|
color={Color.white} |
|
|
|
|
color={Color.pink} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
</button> |
|
|
|
|
<Modal isOpen={showModal} close={closeModal} panelClassname="max-w-lg p-4 sm:p-5"> |
|
|
|
|
{value && <ClearButton onClick={onClear} />} |
|
|
|
|
<Modal |
|
|
|
|
isOpen={showModal} |
|
|
|
|
close={closeModal} |
|
|
|
|
panelClassname="p-4 sm:p-5 max-w-lg min-h-[50vh]" |
|
|
|
|
> |
|
|
|
|
<ChainSearchMenu chainMetadata={chains} onClickChain={onClickChain} /> |
|
|
|
|
</Modal> |
|
|
|
|
</> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -137,27 +149,41 @@ 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 ( |
|
|
|
|
<div className="relative"> |
|
|
|
|
<Popover |
|
|
|
|
button={ |
|
|
|
|
<> |
|
|
|
|
<span className="text-white font-medium py-px px-2">Time</span> |
|
|
|
|
<span>Time</span> |
|
|
|
|
{!hasValue && ( |
|
|
|
|
<ChevronIcon |
|
|
|
|
direction="s" |
|
|
|
|
width={9} |
|
|
|
|
height={5} |
|
|
|
|
classes="ml-2 opacity-80" |
|
|
|
|
color={Color.white} |
|
|
|
|
color={Color.pink} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
</> |
|
|
|
|
} |
|
|
|
|
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" |
|
|
|
|
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 }) => ( |
|
|
|
@ -176,11 +202,26 @@ function DatetimeSelector({ |
|
|
|
|
<h4 className="mt-3 mb-1 text-gray-500 text-sm font-medium">End Time</h4> |
|
|
|
|
<DatetimeField timestamp={endTime} onChange={setEndTime} /> |
|
|
|
|
</div> |
|
|
|
|
<SolidButton classes="mt-4 text-sm px-2 py-1 w-full" onClick={() => onClickApply(close)}> |
|
|
|
|
<SolidButton |
|
|
|
|
classes="mt-4 text-sm px-2 py-1 w-full" |
|
|
|
|
onClick={() => onClickApply(close)} |
|
|
|
|
> |
|
|
|
|
Apply |
|
|
|
|
</SolidButton> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</Popover> |
|
|
|
|
{hasValue && <ClearButton onClick={onClickDirectClear} />} |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function ClearButton({ onClick }: { onClick: () => void }) { |
|
|
|
|
return ( |
|
|
|
|
<div className="absolute right-1.5 top-1/2 -translate-y-1/2"> |
|
|
|
|
<IconButton onClick={onClick} className="bg-pink-300 p-1.5 rounded-full"> |
|
|
|
|
<XIcon color="white" height={9} width={9} /> |
|
|
|
|
</IconButton> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|