Skip to content

Commit

Permalink
feat: Improve UX of token list modal (#330)
Browse files Browse the repository at this point in the history
- Autofocus text input field
- Make appearance more consistent with chain picker
- Hide disabled tokens by default
  • Loading branch information
jmrossy authored Nov 19, 2024
1 parent a323a8a commit 17d53aa
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 25 deletions.
20 changes: 12 additions & 8 deletions src/components/input/TextField.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import clsx from 'clsx';
import { Field, FieldAttributes } from 'formik';
import { ChangeEvent, InputHTMLAttributes } from 'react';
import { ChangeEvent, InputHTMLAttributes, Ref, forwardRef } from 'react';

export function TextField({ classes, ...props }: FieldAttributes<{ classes: string }>) {
return <Field className={`${defaultInputClasses} ${classes}`} {...props} />;
export function TextField({ className, ...props }: FieldAttributes<unknown>) {
return <Field className={clsx(defaultClassName, className)} {...props} />;
}

type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
onChange: (v: string) => void;
classes?: string;
};

export function TextInput({ onChange, classes, ...props }: InputProps) {
export const TextInput = forwardRef(function _TextInput(
{ onChange, className, ...props }: InputProps,
ref: Ref<HTMLInputElement>,
) {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
onChange(e?.target?.value || '');
};
return (
<input
ref={ref}
type="text"
autoComplete="off"
onChange={handleChange}
className={`${defaultInputClasses} ${classes}`}
className={clsx(defaultClassName, className)}
{...props}
/>
);
}
});

const defaultInputClasses =
const defaultClassName =
'mt-1.5 px-2.5 py-2.5 text-sm rounded-lg border border-primary-300 focus:border-primary-500 disabled:bg-gray-150 outline-none transition-all duration-300';
2 changes: 1 addition & 1 deletion src/consts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const config: Config = Object.freeze({
isDevMode,
registryUrl,
registryProxyUrl,
showDisabledTokens: true,
showDisabledTokens: false,
showTipBox: true,
version,
transferBlacklist,
Expand Down
2 changes: 1 addition & 1 deletion src/features/tokens/SelectOrInputTokenIds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function InputTokenId({ disabled }: { disabled: boolean; tokenIndex?: number })
<TextField
name="amount"
placeholder="Input Token Id"
classes="w-full"
className="w-full"
type="number"
step="any"
disabled={disabled}
Expand Down
43 changes: 30 additions & 13 deletions src/features/tokens/TokenListModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IToken } from '@hyperlane-xyz/sdk';
import { Modal } from '@hyperlane-xyz/widgets';
import { Modal, SearchIcon } from '@hyperlane-xyz/widgets';
import Image from 'next/image';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { TokenIcon } from '../../components/icons/TokenIcon';
import { TextInput } from '../../components/input/TextField';
import { config } from '../../consts/config';
Expand Down Expand Up @@ -38,19 +38,10 @@ export function TokenListModal({
return (
<Modal
isOpen={isOpen}
title="Select Token"
close={onClose}
panelClassname="p-4 max-w-100 sm:max-w-[31rem] min-h-[24rem]"
showCloseButton
panelClassname="px-4 py-3 max-w-100 sm:max-w-[31rem] min-h-[24rem]"
>
<TextInput
value={search}
onChange={setSearch}
placeholder="Name, symbol, or address"
name="token-search"
classes="mt-3 mb-4 sm:py-2.5 w-full"
autoComplete="off"
/>
<SearchBar search={search} setSearch={setSearch} />
<TokenList
origin={origin}
destination={destination}
Expand All @@ -61,6 +52,32 @@ export function TokenListModal({
);
}

function SearchBar({ search, setSearch }: { search: string; setSearch: (s: string) => void }) {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);

return (
<div className="relative">
<SearchIcon
width={20}
height={20}
className="absolute left-3 top-1/2 -translate-y-1/2 pb-1 opacity-50"
/>
<TextInput
ref={inputRef}
value={search}
onChange={setSearch}
placeholder="Token name, symbol, or address"
name="token-search"
className="mb-4 mt-3 w-full pl-10 all:border-gray-200 all:py-3 all:focus:border-gray-400"
autoComplete="off"
/>
</div>
);
}

export function TokenList({
origin,
destination,
Expand Down
4 changes: 2 additions & 2 deletions src/features/transfer/TransferTokenForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ function AmountSection({ isNft, isReview }: { isNft: boolean; isReview: boolean
<TextField
name="amount"
placeholder="0.00"
classes="w-full"
className="w-full"
type="number"
step="any"
disabled={isReview}
Expand Down Expand Up @@ -212,7 +212,7 @@ function RecipientSection({ isReview }: { isReview: boolean }) {
<TextField
name="recipient"
placeholder="0x123456..."
classes="w-full"
className="w-full"
disabled={isReview}
/>
<SelfButton disabled={isReview} />
Expand Down

0 comments on commit 17d53aa

Please sign in to comment.