npm package: @liberfi.io/ui-tokens
Description: Token Components for Liberfi React SDK
Author: liberfi.io
Latest version: 0.1.162

# @liberfi.io/ui-tokens

Token-related UI components for the Liberfi React SDK. Provides token lists, search, avatars, and pulse (real-time new/migrated token) widgets consumed by downstream applications.

## Design Philosophy

- **Script / UI / Widget layering** — `.script.ts` files encapsulate data-fetching and business logic as hooks; `.ui.tsx` files are pure presentational components driven by props; `.widget.tsx` files compose scripts and UI together.
- **No built-in global state** — Data flows through props and lightweight React contexts scoped to individual features (e.g. `SearchProvider`). Persistent state uses `atomWithStorage` (jotai) so the storage backend can be swapped.
- **Callbacks over side effects** — Selection, navigation, and feedback (toast, analytics) are surfaced via `onSelectToken`, `onResult`, etc. rather than hard-coded inside the library.
- **Mobile-first search** — The search module targets constrained-width containers (modals, sidebars) and uses a compact mobile layout exclusively.

## Installation

```bash
pnpm add @liberfi.io/ui-tokens
```

### Peer dependencies

The consumer must provide:

- `react` >= 18
- `react-dom` >= 18
- `jotai` >= 2.15.1

## API Reference

### Components — Search

| Export                                | Kind    | Description                                                                                                   |
| ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `SearchTokensButton`                  | Widget  | Button that opens the search modal (supports `/` keyboard shortcut).                                          |
| `SearchModal`                         | Widget  | `AsyncModal` wrapper; renders `SearchWidget` inside a `StyledModal`.                                          |
| `SearchWidget`                        | Widget  | Standalone search experience (input + history + result list). Accepts `chains?: Chain[]` and `onSelectToken`. |
| `SearchInputUI`                       | UI      | Search input with 500ms debounce, paste, and clear buttons.                                                   |
| `SearchContentUI`                     | UI      | Conditional renderer — shows history + hint when idle, search results when a keyword is set.                  |
| `SearchHistoryUI`                     | UI      | Chips displaying persisted search history with a clear button.                                                |
| `SearchResultListWidget`              | Widget  | Virtualized infinite-scroll result list (`react-window` + `react-window-infinite-loader`).                    |
| `SearchResultListHeader`              | UI      | Static column headers (Token/Age/MCap/Liq \| Price/24h%).                                                     |
| `SearchResultItemUI`                  | UI      | Compact token row — avatar, symbol, age, market cap, TVL, price, and price change.                            |
| `SearchResultListSkeleton`            | UI      | Skeleton placeholder for initial loading state.                                                               |
| `SearchProvider` / `useSearchContext` | Context | Manages the current search keyword.                                                                           |
| `useSearchHistory`                    | Hook    | Read/write persisted search history (backed by `atomWithStorage`).                                            |
| `useSearchResultListScript`           | Hook    | Connects `useSearchTokensInfiniteQuery` to the search context.                                                |

#### SearchModal

Wraps `AsyncModal` with `id="search_tokens"`. Open it from anywhere via:

```tsx
import { useAsyncModal } from "@liberfi.io/ui-scaffold";

const { onOpen } = useAsyncModal<SearchModalParams, SearchModalResult>(
  "search_tokens",
);
await onOpen({ params: { chains: [Chain.SOLANA] } });
// resolves with Token on selection, undefined on dismiss
```

#### SearchWidget (standalone)

```tsx
import { SearchWidget } from "@liberfi.io/ui-tokens";

<SearchWidget
  chains={[Chain.SOLANA]}
  onSelectToken={(token) => navigate(`/token/${token.chain}/${token.address}`)}
/>;
```

### Components — Token Avatar

| Export        | Kind | Description                                                                                 |
| ------------- | ---- | ------------------------------------------------------------------------------------------- |
| `TokenAvatar` | UI   | Token avatar with optional protocol family icon, bonding-curve progress, and image preview. |

### Components — Token List

| Export                    | Kind   | Description                                  |
| ------------------------- | ------ | -------------------------------------------- |
| `NewTokenListWidget`      | Widget | New tokens list with real-time subscription. |
| `TrendingTokenListWidget` | Widget | Trending tokens list.                        |
| `StockTokenListWidget`    | Widget | Stock tokens list.                           |
| `TokenListFilterModal`    | UI     | Filter modal for token list columns.         |

### Components — Pulse

| Export                        | Kind   | Description                      |
| ----------------------------- | ------ | -------------------------------- |
| `PulseNewListWidget`          | Widget | Real-time new token pulse feed.  |
| `PulseMigratedListWidget`     | Widget | Migrated tokens pulse feed.      |
| `PulseFinalStretchListWidget` | Widget | Final-stretch tokens pulse feed. |

### Hooks

| Export              | Description                                                                                       |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| `useTokens(params)` | Fetches and subscribes to a set of tokens by chain + addresses, with polling and WebSocket merge. |

## Usage Examples

### Full search modal setup

```tsx
import { SearchModal, SearchTokensButton } from "@liberfi.io/ui-tokens";

function App() {
  return (
    <>
      {/* Trigger button */}
      <SearchTokensButton />

      {/* Modal definition — render once at app root */}
      <SearchModal />
    </>
  );
}
```

### Standalone search widget inside a sidebar

```tsx
import { Chain } from "@liberfi.io/types";
import { SearchWidget } from "@liberfi.io/ui-tokens";

function Sidebar() {
  return (
    <div className="w-80 h-screen">
      <SearchWidget
        chains={[Chain.SOLANA, Chain.ETHEREUM]}
        onSelectToken={(token) => {
          console.log("Selected:", token.chain, token.address);
        }}
      />
    </div>
  );
}
```

## Future Improvements

- Add tab system (Discover / Favorite / View) for the empty-state content when no keyword is entered.
- Support sort and filter controls in search result headers.
- Add chain selector to scope search to a specific chain.
- Support custom `atomWithStorage` storage adapter for search history (e.g. server-side persistence).