SolanaUISolanaUI

Token Input

An input field for entering token amounts with a token selector, balance display, and automatic number formatting

24.58
$3,996.73
import { TokenInput } from "@/components/sol/token-input";

const SOL_ICON =
  "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png";
const USDC_ICON =
  "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png";

export function TokenInputDemo() {
  return (
    <TokenInput
      tokens={[
        { icon: SOL_ICON, symbol: "SOL" },
        { icon: USDC_ICON, symbol: "USDC" },
      ]}
      defaultToken="SOL"
      balance="24.58"
      usdValue="$3,996.73"
    />
  );
}

Installation

pnpm dlx shadcn@latest add @solanaui/token-input
npx shadcn@latest add @solanaui/token-input
yarn dlx shadcn@latest add @solanaui/token-input

Usage

const SOL_ICON =
  "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png";
const USDC_ICON =
  "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png";

<TokenInput
  tokens={[
    { icon: SOL_ICON, symbol: "SOL" },
    { icon: USDC_ICON, symbol: "USDC" },
  ]}
  defaultToken="SOL"
  balance="24.58"
/>

Source Code

"use client";import { WalletIcon } from "lucide-react";import React from "react";import { NumericFormat } from "react-number-format";import { TokenCombobox } from "@/registry/sol/token-combobox";import { Button } from "@/components/ui/button";import { Input } from "@/components/ui/input";import { cn } from "@/lib/utils";interface TokenInputProps {  tokens: { icon: string; symbol: string }[];  defaultToken?: string;  balance?: string;  value?: string;  usdValue?: string;  onValueChange?: (value: string) => void;  onTokenSelect?: (token: { icon: string; symbol: string }) => void;  className?: string;}const TokenInput = ({  tokens,  defaultToken,  balance,  value,  usdValue,  onValueChange,  onTokenSelect,  className,}: TokenInputProps) => {  const [internalValue, setInternalValue] = React.useState(value ?? "");  const currentValue = value ?? internalValue;  const handleValueChange = (values: { value: string }) => {    setInternalValue(values.value);    onValueChange?.(values.value);  };  const handleQuickAmount = (fraction: number) => {    if (!balance) return;    const numericBalance = Number.parseFloat(balance.replace(/,/g, ""));    if (Number.isNaN(numericBalance)) return;    const newValue = (numericBalance * fraction).toString();    setInternalValue(newValue);    onValueChange?.(newValue);  };  return (    <div      className={cn(        "flex flex-col gap-3 border p-4 rounded-lg w-full",        className,      )}    >      {balance && (        <div className="flex items-center justify-between">          <span className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">            <WalletIcon className="size-3.5" />            {balance}          </span>          <div className="flex items-center gap-2">            <Button              variant="outline"              size="sm"              className="text-xs h-6 px-2 rounded-sm"              onClick={() => handleQuickAmount(0.5)}            >              Half            </Button>            <Button              variant="outline"              size="sm"              className="text-xs h-6 px-2 rounded-sm"              onClick={() => handleQuickAmount(1)}            >              Max            </Button>          </div>        </div>      )}      <div className="flex items-center gap-2 w-full">        <TokenCombobox          tokens={tokens}          defaultValue={defaultToken}          onSelect={onTokenSelect}        />        <div className="flex flex-col flex-1 min-w-0 items-end">          <NumericFormat            value={currentValue}            onValueChange={handleValueChange}            thousandSeparator=","            decimalSeparator="."            allowNegative={false}            placeholder="0"            inputMode="decimal"            customInput={Input}            className="text-right bg-transparent pr-1 dark:bg-transparent shadow-none border-none focus:ring-0 focus-visible:ring-0 w-full md:text-xl"          />          {usdValue && (            <span className="text-xs text-muted-foreground pr-1">              {usdValue}            </span>          )}        </div>      </div>    </div>  );};export type { TokenInputProps };export { TokenInput };