More de-deduplication with widgets lib

pull/114/head
J M Rossy 2 months ago
parent 5bf09c83df
commit ae86d66061
  1. 7
      src/components/buttons/BackButton.tsx
  2. 49
      src/components/buttons/IconButton.tsx
  3. 21
      src/components/buttons/XIconButton.tsx
  4. 134
      src/components/icons/Chevron.tsx
  5. 26
      src/components/icons/HelpIcon.tsx
  6. 38
      src/components/icons/HyperlaneLogo.tsx
  7. 31
      src/components/icons/XIcon.tsx
  8. 33
      src/components/input/Checkbox.module.css
  9. 28
      src/components/input/Checkbox.tsx
  10. 11
      src/components/input/TextField.tsx
  11. 15
      src/components/nav/Footer.tsx
  12. 15
      src/components/nav/Header.tsx
  13. 13
      src/components/search/MiniSearchBar.tsx
  14. 15
      src/components/search/SearchBar.tsx
  15. 10
      src/components/search/SearchFilterBar.tsx
  16. 11
      src/features/chains/ConfigureChains.tsx
  17. 5
      src/features/messages/cards/ContentDetailsCard.tsx
  18. 5
      src/features/messages/cards/GasDetailsCard.tsx
  19. 2
      src/features/messages/cards/IcaDetailsCard.tsx
  20. 5
      src/features/messages/cards/IsmDetailsCard.tsx
  21. 2
      src/features/messages/cards/TransactionCard.tsx

@ -1,7 +0,0 @@
import LeftArrow from '../../images/icons/arrow-left-circle.svg';
import { IconButton, IconButtonProps } from './IconButton';
export function BackButton(props: IconButtonProps) {
return <IconButton imgSrc={LeftArrow} title="Go back" {...props} />;
}

@ -1,49 +0,0 @@
import Image from 'next/image';
import { PropsWithChildren } from 'react';
export interface IconButtonProps {
width?: number;
height?: number;
classes?: string;
onClick?: () => void;
disabled?: boolean;
imgSrc?: any;
title?: string;
type?: 'button' | 'submit';
passThruProps?: any;
}
export function IconButton(props: PropsWithChildren<IconButtonProps>) {
const {
width,
height,
classes,
onClick,
imgSrc,
disabled,
title,
type,
children,
passThruProps,
} = props;
const base = 'flex items-center justify-center transition-all';
const onHover = 'hover:opacity-70';
const onDisabled = 'disabled:opacity-50';
const onActive = 'active:opacity-60';
const allClasses = `${base} ${onHover} ${onDisabled} ${onActive} ${classes}`;
return (
<button
onClick={onClick}
type={type || 'button'}
disabled={disabled ?? false}
title={title}
className={allClasses}
{...passThruProps}
>
<Image src={imgSrc} alt={title?.substring(0, 4) || ''} width={width} height={height} />
{children}
</button>
);
}

@ -1,21 +0,0 @@
import { memo } from 'react';
import X from '../../images/icons/x.svg';
import { IconButton } from './IconButton';
function _XIconButton({
onClick,
title,
size = 20,
}: {
onClick: () => void;
title?: string;
size?: number;
}) {
return (
<IconButton imgSrc={X} title={title || 'Close'} width={size} height={size} onClick={onClick} />
);
}
export const XIconButton = memo(_XIconButton);

@ -1,134 +0,0 @@
import { memo } from 'react';
import { Color } from '../../styles/Color';
interface Props {
width?: string | number;
height?: string | number;
direction: 'n' | 'e' | 's' | 'w';
color?: string;
classes?: string;
}
function _ChevronIcon({ width, height, direction, color, classes }: Props) {
let directionClass;
switch (direction) {
case 'n':
directionClass = 'rotate-180';
break;
case 'e':
directionClass = '-rotate-90';
break;
case 's':
directionClass = '';
break;
case 'w':
directionClass = 'rotate-90';
break;
default:
throw new Error(`Invalid chevron direction ${direction}`);
}
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 14 8"
className={`${directionClass} ${classes}`}
>
<path
d="M1 1l6 6 6-6"
strokeWidth="2"
stroke={color || Color.black}
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export const ChevronIcon = memo(_ChevronIcon);
interface Props {
width?: string | number;
height?: string | number;
direction: 'n' | 'e' | 's' | 'w';
color?: string;
classes?: string;
}
function _HyperlaneChevron({ width, height, direction, color, classes }: Props) {
let directionClass;
switch (direction) {
case 'n':
directionClass = '-rotate-90';
break;
case 'e':
directionClass = '';
break;
case 's':
directionClass = 'rotate-90';
break;
case 'w':
directionClass = 'rotate-180';
break;
default:
throw new Error(`Invalid chevron direction ${direction}`);
}
return (
<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="1.2 1 139.1 322"
width={width}
height={height}
className={`${directionClass} ${classes}`}
>
<path
d="M6.3 1h61.3a20 20 0 0 1 18.7 13L140 158.3a5 5 0 0 1 0 3.4l-.3.9-53.5 147.2A20 20 0 0 1 67.4 323H6.2a5 5 0 0 1-4.7-6.6l55.2-158.1L1.7 7.7A5 5 0 0 1 6.2 1Z"
fill={color || Color.blue}
></path>
</svg>
);
}
export const HyperlaneChevron = memo(_HyperlaneChevron);
function _HyperlaneWideChevron({ width, height, direction, color, classes }: Props) {
let directionClass;
switch (direction) {
case 'n':
directionClass = '-rotate-90';
break;
case 'e':
directionClass = '';
break;
case 's':
directionClass = 'rotate-90';
break;
case 'w':
directionClass = 'rotate-180';
break;
default:
throw new Error(`Invalid chevron direction ${direction}`);
}
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 120.3 190"
width={width}
height={height}
className={`${directionClass} ${classes}`}
fill={color || Color.blue}
>
<path d="M4.4 0h53c7.2 0 13.7 3 16.2 7.7l46.5 85.1a2 2 0 0 1 0 2l-.2.5-46.3 87c-2.5 4.6-9 7.7-16.3 7.7h-53c-3 0-5-2-4-4L48 92.9.4 4c-1-2 1-4 4-4Z" />
</svg>
);
}
export const HyperlaneWideChevron = memo(_HyperlaneWideChevron);

@ -1,22 +1,26 @@
import { memo } from 'react';
import Question from '../../images/icons/question-circle.svg';
import { IconButton } from '../buttons/IconButton';
import { IconButton, QuestionMarkIcon } from '@hyperlane-xyz/widgets';
function _HelpIcon({ text, size = 20 }: { text: string; size?: number }) {
import { Color } from '../../styles/Color';
function _HelpIcon({ text, size = 16 }: { text: string; size?: number }) {
const tooltipProps = {
'data-tooltip-content': text,
'data-tooltip-id': 'root-tooltip',
'data-tooltip-place': 'top-start',
};
return (
// @ts-ignore allow pass-thru tooltip props
<IconButton
imgSrc={Question}
title="Help"
width={size}
height={size}
classes="opacity-50"
passThruProps={{
'data-tooltip-content': text,
'data-tooltip-id': 'root-tooltip',
'data-tooltip-place': 'top-start',
}}
/>
className="border border-gray-400 rounded-full p-px"
{...tooltipProps}
>
<QuestionMarkIcon height={size} width={size} color={Color.lightGray} className="opacity-50" />
</IconButton>
);
}

@ -1,38 +0,0 @@
import { memo } from 'react';
function _HyperlaneLogo({
width,
height,
fill,
className = '',
}: {
width?: number | string;
height?: number | string;
fill?: string;
className?: string;
}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
className={className}
viewBox="0 0 117 118"
>
<path
d="M64.4787 0H88.4134C91.6788 0 94.6004 1.89614 95.7403 4.7553L116.749 57.4498C116.911 57.8563 116.913 58.3035 116.754 58.7112L116.637 59.014L116.635 59.017L95.7152 112.81C94.5921 115.698 91.6551 117.62 88.3666 117.62H64.4355C63.0897 117.62 62.1465 116.379 62.59 115.192L84.1615 57.4498L62.6428 2.45353C62.1766 1.26188 63.1208 0 64.4787 0Z"
fill={fill}
/>
<path
d="M1.99945 0H25.9342C29.1996 0 32.1211 1.89614 33.261 4.7553L54.2696 57.4498C54.4316 57.8563 54.4336 58.3035 54.275 58.7112L54.1573 59.014L54.1561 59.017L33.236 112.81C32.1129 115.698 29.1759 117.62 25.8874 117.62H1.95626C0.610483 117.62 -0.332722 116.379 0.110804 115.192L21.6823 57.4498L0.163626 2.45353C-0.302638 1.26188 0.641544 0 1.99945 0Z"
fill={fill}
/>
<path
d="M80.7202 46.2178H46.9324V71.7089H80.7202L86.2411 58.5992L80.7202 46.2178Z"
fill={fill}
/>
</svg>
);
}
export const HyperlaneLogo = memo(_HyperlaneLogo);

@ -1,31 +0,0 @@
import { memo } from 'react';
function _XIcon({
width,
height,
fill,
className = '',
}: {
width?: number | string;
height?: number | string;
fill?: string;
className?: string;
}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
className={className}
viewBox="20.24 19.95 56.33 56.24"
>
<path
fill={fill}
d="M27.73 76.19a7.5 7.5 0 0 1-5.3-12.8l41.34-41.34a7.5 7.5 0 0 1 10.6 10.61L33 74a7.48 7.48 0 0 1-5.27 2.19Z"
/>
<path d="M69.07 76.19a7.48 7.48 0 0 1-5.3-2.2L22.43 32.66A7.5 7.5 0 0 1 33 22.05l41.37 41.34a7.5 7.5 0 0 1-5.3 12.8Z" />
</svg>
);
}
export const XIcon = memo(_XIcon);

@ -1,33 +0,0 @@
.checkbox {
appearance: none;
background-color: #fff;
margin: 0;
width: 0.9rem;
height: 0.9rem;
border: 2px solid #1f2937;
border-radius: 0.2rem;
transform: translateY(-0.075em);
display: grid;
place-content: center;
cursor: pointer;
transition: 200ms all ease-in-out;
}
.checkbox::before {
content: '';
width: 0.9rem;
height: 0.9rem;
border-radius: 0.2rem;
transform: scale(0);
transition: 200ms all ease-in-out;
background-color: #2362c0;
}
.checkbox:checked::before {
transform: scale(1);
}
.checkbox:disabled {
border-color: #bbb;
cursor: not-allowed;
}

@ -1,28 +0,0 @@
import React from 'react';
import styles from './Checkbox.module.css';
interface Props {
checked: boolean;
onToggle: (c: boolean) => void;
name?: string;
}
export function CheckBox({ checked, onToggle, name, children }: React.PropsWithChildren<Props>) {
const onChange = () => {
onToggle(!checked);
};
return (
<label className="flex items-center cursor-pointer hover:opacity-80">
<input
type="checkbox"
name={name}
checked={checked}
onChange={onChange}
className={styles.checkbox}
/>
{children}
</label>
);
}

@ -1,11 +0,0 @@
import { Field } from 'formik';
import { ComponentProps } from 'react';
export function TextField(props: ComponentProps<typeof Field>) {
return (
<Field
className="w-100 mt-2 p-2 text-sm border border-color-gray-800 rounded focus:outline-none"
{...props}
/>
);
}

@ -2,15 +2,14 @@
import Image from 'next/image';
import Link from 'next/link';
import { HyperlaneLogo } from '@hyperlane-xyz/widgets';
import { docLinks, links } from '../../consts/links';
// import FooterLine from '../../images/backgrounds/footer-line-desktop.svg';
// import FooterLineMobile from '../../images/backgrounds/footer-line-mobile.svg';
import FooterBg from '../../images/backgrounds/footer-bg.svg';
import FooterTopBorder from '../../images/backgrounds/footer-top-border.svg';
import { Color } from '../../styles/Color';
import { Discord } from '../icons/Discord';
import { Github } from '../icons/Github';
import { HyperlaneLogo } from '../icons/HyperlaneLogo';
import { Twitter } from '../icons/Twitter';
const footerLinks1 = [
@ -50,20 +49,12 @@ export function Footer() {
<div className="flex flex-col sm:flex-row gap-10 items-center justify-between">
<div className="flex items-center justify-center">
<div className="ml-2 w-14 sm:w-16 h-14 sm:h-16">
<HyperlaneLogo fill={Color.white} />
<HyperlaneLogo color={Color.white} />
</div>
<div className="text-xl sm:text-2xl font-medium ml-6 space-y-1 ">
<div>Go interchain</div>
<div>with Hyperlane</div>
</div>
{/* <div className="absolute">
<div className="hidden sm:block">
<Image src={FooterLine} alt="" />
</div>
<div className="sm:hidden">
<Image src={FooterLineMobile} alt="" />
</div>
</div> */}
</div>
<nav className="flex text-lg font-medium">
<ul className={`${styles.linkCol} mr-14`}>

@ -2,7 +2,7 @@ import Image from 'next/image';
import Link from 'next/link';
import { PropsWithChildren } from 'react';
import { DropdownMenu } from '@hyperlane-xyz/widgets';
import { DropdownMenu, WideChevron } from '@hyperlane-xyz/widgets';
import { docLinks, links } from '../../consts/links';
import Explorer from '../../images/logos/hyperlane-explorer.svg';
@ -10,7 +10,6 @@ import Logo from '../../images/logos/hyperlane-logo.svg';
import Name from '../../images/logos/hyperlane-name.svg';
import { Color } from '../../styles/Color';
import { useScrollThresholdListener } from '../../utils/useScrollListener';
import { HyperlaneWideChevron } from '../icons/Chevron';
import { MiniSearchBar } from '../search/MiniSearchBar';
const PAGES_EXCLUDING_SEARCH = ['/', '/debugger'];
@ -112,26 +111,26 @@ export function Header({ pathName }: { pathName: string }) {
function DropdownButton() {
return (
<div className="px-4 py-1 flex flex-col items-center border border-white bg-pink-500 rounded-lg">
<HyperlaneWideChevron
<WideChevron
width={10}
height={14}
direction="s"
color={Color.white}
classes="transition-all"
className="transition-all"
/>
<HyperlaneWideChevron
<WideChevron
width={10}
height={14}
direction="s"
color={Color.white}
classes="-mt-1 transition-all"
className="-mt-1 transition-all"
/>
<HyperlaneWideChevron
<WideChevron
width={10}
height={14}
direction="s"
color={Color.white}
classes="-mt-1 transition-all"
className="-mt-1 transition-all"
/>
</div>
);

@ -1,8 +1,7 @@
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import SearchIcon from '../../images/icons/search.svg';
import { IconButton } from '../buttons/IconButton';
import { IconButton, SearchIcon } from '@hyperlane-xyz/widgets';
interface FormValues {
search: string;
@ -31,13 +30,9 @@ export function MiniSearchBar() {
className="w-32 focus:w-64 py-2 px-2.5 h-8 text-sm font-light placeholder:text-gray-600 rounded-full focus:outline-none transition-[width] ease-in-out duration-500"
/>
<div className="h-8 w-8 flex items-center justify-center rounded-full bg-pink-500">
<IconButton
type="submit"
imgSrc={SearchIcon}
width={14}
height={14}
title="Search"
></IconButton>
<IconButton type="submit" title="Search">
<SearchIcon width={14} height={14} />
</IconButton>
</div>
</div>
</Form>

@ -1,10 +1,10 @@
import Image from 'next/image';
import { ChangeEvent } from 'react';
import { IconButton, XIcon } from '@hyperlane-xyz/widgets';
import SearchIcon from '../../images/icons/search.svg';
import XIcon from '../../images/icons/x.svg';
import { Spinner } from '../animations/Spinner';
import { IconButton } from '../buttons/IconButton';
interface Props {
value: string;
@ -36,14 +36,9 @@ export function SearchBar({ value, placeholder, onChangeValue, isFetching }: Pro
)}
{!isFetching && !value && <Image src={SearchIcon} width={20} height={20} alt="" />}
{!isFetching && value && (
<IconButton
imgSrc={XIcon}
title="Clear search"
width={16}
height={16}
onClick={() => onChange(null)}
classes="invert"
/>
<IconButton title="Clear search" onClick={() => onChange(null)}>
<XIcon width={16} height={16} color="white" />
</IconButton>
)}
</div>
</div>

@ -6,6 +6,7 @@ import { ChainMetadata } from '@hyperlane-xyz/sdk';
import { trimToLength } from '@hyperlane-xyz/utils';
import {
ChainSearchMenu,
ChevronIcon,
GearIcon,
IconButton,
Modal,
@ -19,7 +20,6 @@ import { useMultiProvider } from '../../store';
import { Color } from '../../styles/Color';
import { SolidButton } from '../buttons/SolidButton';
import { TextButton } from '../buttons/TextButton';
import { ChevronIcon } from '../icons/Chevron';
import { DatetimeField } from '../input/DatetimeField';
interface Props {
@ -101,7 +101,7 @@ function ChainSelector({
<button
type="button"
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',
'text-sm sm:min-w-[5.8rem] px-1.5 sm:px-2.5 py-1 flex items-center justify-center font-medium rounded-lg 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)}
@ -112,7 +112,7 @@ function ChainSelector({
direction="s"
width={9}
height={5}
classes="ml-2 opacity-80"
className="ml-2 opacity-80"
color={Color.pink}
/>
)}
@ -174,14 +174,14 @@ function DatetimeSelector({
direction="s"
width={9}
height={5}
classes="ml-2 opacity-80"
className="ml-2 opacity-80"
color={Color.pink}
/>
)}
</>
}
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',
'text-sm px-2 sm:px-3 py-1 flex items-center justify-center font-medium border border-pink-500 rounded-lg 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"

@ -1,11 +1,10 @@
import { ChangeEventHandler, useState } from 'react';
import { ChainName } from '@hyperlane-xyz/sdk';
import { Modal } from '@hyperlane-xyz/widgets';
import { IconButton, Modal, XIcon } from '@hyperlane-xyz/widgets';
import { CopyButton } from '../../components/buttons/CopyButton';
import { SolidButton } from '../../components/buttons/SolidButton';
import { XIconButton } from '../../components/buttons/XIconButton';
import { ChainLogo } from '../../components/icons/ChainLogo';
import { Card } from '../../components/layout/Card';
import { docLinks } from '../../consts/links';
@ -110,11 +109,9 @@ export function ConfigureChains() {
{chain.blockExplorers?.[0]?.url || 'Unknown'}
</td>
<td>
<XIconButton
onClick={() => onClickRemoveChain(chain.name)}
title="Remove"
size={10}
/>
<IconButton onClick={() => onClickRemoveChain(chain.name)} title="Remove">
<XIcon width={10} height={10} />
</IconButton>
</td>
</tr>
))}

@ -73,10 +73,7 @@ export function ContentDetailsCard({
<Image src={EnvelopeInfo} width={28} height={28} alt="" className="opacity-80" />
<div className="flex items-center pb-1">
<h3 className="text-blue-500 font-medium text-md mr-2">Message Details</h3>
<HelpIcon
size={16}
text="Immutable information about the message itself such as its contents."
/>
<HelpIcon text="Immutable information about the message itself such as its contents." />
</div>
</div>
<div className="flex flex-wrap gap-x-6 gap-y-4">

@ -75,10 +75,7 @@ export function GasDetailsCard({ message, blur, igpPayments = {} }: Props) {
<Image src={FuelPump} width={24} height={24} alt="" className="opacity-80" />
<div className="flex items-center pb-1">
<h3 className="text-blue-500 font-medium text-md mr-2">Interchain Gas Payments</h3>
<HelpIcon
size={16}
text="Amounts paid to the Interchain Gas Paymaster for message delivery."
/>
<HelpIcon text="Amounts paid to the Interchain Gas Paymaster for message delivery." />
</div>
</div>
<p className="text-sm font-light">

@ -31,7 +31,7 @@ export function IcaDetailsCard({ message: { originDomainId, body }, blur }: Prop
</div>
<div className="flex items-center pb-1">
<h3 className="text-blue-500 font-medium text-md mr-2">ICA Details</h3>
<HelpIcon size={16} text="Extra information for messages from/to Interchain Accounts." />
<HelpIcon text="Extra information for messages from/to Interchain Accounts." />
</div>
</div>
{decodeResult ? (

@ -22,10 +22,7 @@ export function IsmDetailsCard({ ismDetails, blur }: Props) {
<Image src={ShieldLock} width={24} height={24} alt="" className="opacity-80" />
<div className="flex items-center pb-1">
<h3 className="text-blue-500 font-medium text-md mr-2">Interchain Security Modules</h3>
<HelpIcon
size={16}
text="Details about the Interchain Security Modules (ISM) that must verify this message."
/>
<HelpIcon text="Details about the Interchain Security Modules (ISM) that must verify this message." />
</div>
</div>
<p className="text-sm font-light">

@ -182,7 +182,7 @@ function TransactionCard({
</div>
<div className="flex items-center pb-1">
<h3 className="text-blue-500 font-medium text-md mr-2">{title}</h3>
<HelpIcon size={16} text={helpText} />
<HelpIcon text={helpText} />
</div>
</div>
{children}

Loading…
Cancel
Save