Token Command
A command palette component for token selection with optional grouping
Command Palette
Search for a command to run...
import { TokenCommand } from "@/components/sol/token-command";
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";
const BONK_ICON =
"https://arweave.net/hQiPZOsRZXGXBJd_82PhVdlM_hACsT_q6wqwf5cSY7I";
const JITOSOL_ICON =
"https://storage.googleapis.com/token-metadata/JitoSOL-256.png";
export function TokenCommandDemo() {
return (
<TokenCommand
groups={[
{
heading: "Global Pool",
tokens: [
{ icon: SOL_ICON, symbol: "SOL" },
{ icon: USDC_ICON, symbol: "USDC" },
{ icon: BONK_ICON, symbol: "BONK" },
],
},
{
heading: "Isolated Pools",
tokens: [
{ icon: JITOSOL_ICON, symbol: "JitoSOL" },
],
},
]}
/>
);
}Installation
pnpm dlx shadcn@latest add @solanaui/token-command
npx shadcn@latest add @solanaui/token-command
yarn dlx shadcn@latest add @solanaui/token-command
Usage
Flat Token List
<TokenCommand
tokens={[
{ icon: SOL_ICON, symbol: "SOL" },
{ icon: USDC_ICON, symbol: "USDC" },
]}
/>Grouped Tokens
Use the groups prop to organize tokens into labeled sections. Pass either tokens or groups, not both.
<TokenCommand
groups={[
{
heading: "Global Pool",
tokens: [
{ icon: SOL_ICON, symbol: "SOL" },
{ icon: USDC_ICON, symbol: "USDC" },
],
},
{
heading: "Isolated Pools",
tokens: [
{ icon: JITOSOL_ICON, symbol: "JitoSOL" },
],
},
]}
/>With Selection Callback
<TokenCommand
tokens={[
{ icon: SOL_ICON, symbol: "SOL" },
{ icon: USDC_ICON, symbol: "USDC" },
]}
onSelect={(token) => console.log("Selected:", token.symbol)}
/>Source Code
"use client";import { ChevronsUpDown } from "lucide-react";import React from "react";import { TokenIcon } from "@/registry/sol/token-icon";import { Button } from "@/components/ui/button";import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList,} from "@/components/ui/command";import { cn } from "@/lib/utils";interface TokenCommandToken { icon: string; symbol: string;}interface TokenCommandGroup { heading: string; tokens: TokenCommandToken[];}type TokenCommandProps = { onSelect?: (token: TokenCommandToken) => void; className?: string;} & ( | { tokens: TokenCommandToken[]; groups?: never } | { groups: TokenCommandGroup[]; tokens?: never });const TokenCommand = ({ tokens, groups, onSelect, className,}: TokenCommandProps) => { const [open, setOpen] = React.useState(false); const [value, setValue] = React.useState(""); const allTokens = React.useMemo(() => { if (tokens) return tokens; if (groups) return groups.flatMap((g) => g.tokens); return []; }, [tokens, groups]); const activeToken = allTokens.find( (token) => token.symbol.toLowerCase() === value.toLowerCase(), ); React.useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((open) => !open); } }; document.addEventListener("keydown", down); return () => document.removeEventListener("keydown", down); }, []); const renderItem = (token: TokenCommandToken) => ( <CommandItem key={token.symbol} value={token.symbol} onSelect={(currentValue) => { const newValue = currentValue === value ? "" : currentValue; setValue(newValue); setOpen(false); if (newValue) { onSelect?.(token); } }} > <TokenIcon src={token.icon} alt={token.symbol} width={20} height={20} /> {token.symbol} </CommandItem> ); return ( <> <Button variant="outline" className={cn("w-[200px] justify-between", className)} onClick={() => setOpen(true)} > {activeToken ? ( <div className="flex items-center gap-2.5"> <TokenIcon src={activeToken.icon} alt={activeToken.symbol} width={20} height={20} /> {activeToken.symbol} </div> ) : ( "Select token..." )} <ChevronsUpDown className="opacity-50" /> </Button> <CommandDialog open={open} onOpenChange={setOpen}> <CommandInput placeholder="Search token..." /> <CommandList> <CommandEmpty>No tokens found.</CommandEmpty> {groups ? ( groups.map((group) => ( <CommandGroup key={group.heading} heading={group.heading}> {group.tokens.map(renderItem)} </CommandGroup> )) ) : ( <CommandGroup heading="Tokens"> {allTokens.map(renderItem)} </CommandGroup> )} </CommandList> </CommandDialog> </> );};export type { TokenCommandProps, TokenCommandToken, TokenCommandGroup };export { TokenCommand };