npm package: @liberfi.io/ui
Description: Base UI library for Liberfi React SDK
Author: liberfi.io
Latest version: 0.1.151

# @liberfi.io/ui

Base UI component library for the Liberfi React SDK monorepo. Built on top of [HeroUI](https://heroui.com) (formerly NextUI), it provides project-branded component variants, react-hook-form integrations, SVG icons, responsive hooks, and a Tailwind CSS theme plugin. All other `@liberfi.io/ui-*` packages depend on this package for their foundational UI primitives.

## Design Philosophy

- **Extend, don't fork** — Base components are re-exported directly from `@heroui/react`. Project-specific styling is applied via `extendVariants()`, keeping the upstream API intact while adding custom color/size/variant options (e.g. `StyledButton`, `StyledInput`).
- **Presentational vs container** — `base/` components are pure presentational (props in, JSX out). `form/` components are container wrappers that wire react-hook-form state via `useFormContext`. This split keeps UI reusable without a form context.
- **Slot-based style customization** — Custom components expose a `classNames` object prop with named slots (e.g. `root`, `content`, `leftArrow`), following HeroUI's established pattern for granular style overrides.
- **Separate theme distribution** — The Tailwind plugin and CSS are exported via a dedicated sub-path (`@liberfi.io/ui/tailwind`), so consumers can import the theme independently from the components.

## Installation

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

### Peer Dependencies

The consumer must provide:

```json
{
  "react": ">=18",
  "react-dom": ">=18",
  "jotai": ">=2.15.1",
  "framer-motion": ">=12.0.0",
  "react-hook-form": ">=7.50.0"
}
```

Optional peer dependencies (only needed if using the corresponding features):

- `tailwindcss` (>=4.0.0) — Required when using the Tailwind plugin sub-path

## API Reference

### Base Components

Re-exported from `@heroui/react` with `displayName` assignments. All original HeroUI props are preserved.

| Component      | Sub-components                                                                                   | Source            |
| -------------- | ------------------------------------------------------------------------------------------------ | ----------------- |
| `Accordion`    | `AccordionItem`                                                                                  | `@heroui/react`   |
| `Alert`        | —                                                                                                | `@heroui/react`   |
| `Autocomplete` | `AutocompleteSection`, `AutocompleteItem`                                                        | `@heroui/react`   |
| `Avatar`       | `AvatarGroup`                                                                                    | `@heroui/react`   |
| `Badge`        | —                                                                                                | `@heroui/react`   |
| `Breadcrumbs`  | `BreadcrumbItem`                                                                                 | `@heroui/react`   |
| `Button`       | `ButtonGroup`                                                                                    | `@heroui/react`   |
| `Calendar`     | `RangeCalendar`                                                                                  | `@heroui/react`   |
| `Card`         | `CardBody`, `CardFooter`, `CardHeader`                                                           | `@heroui/react`   |
| `Checkbox`     | `CheckboxGroup`                                                                                  | `@heroui/react`   |
| `Chip`         | —                                                                                                | `@heroui/react`   |
| `Code`         | —                                                                                                | `@heroui/react`   |
| `DatePicker`   | `DateRangePicker`                                                                                | `@heroui/react`   |
| `Divider`      | —                                                                                                | `@heroui/react`   |
| `Drawer`       | `DrawerContent`, `DrawerHeader`, `DrawerBody`, `DrawerFooter`                                    | `@heroui/react`   |
| `Dropdown`     | `DropdownTrigger`, `DropdownMenu`, `DropdownSection`, `DropdownItem`                             | `@heroui/react`   |
| `Image`        | —                                                                                                | `@heroui/react`   |
| `Input`        | `NumberInput`, `DateInput`, `Textarea`, `TimeInput`, `InputOtp`                                  | `@heroui/react`   |
| `Kbd`          | —                                                                                                | `@heroui/react`   |
| `Link`         | —                                                                                                | `@heroui/react`   |
| `Listbox`      | `ListboxSection`, `ListboxItem`                                                                  | `@heroui/react`   |
| `Modal`        | `ModalContent`, `ModalHeader`, `ModalBody`, `ModalFooter`                                        | `@heroui/react`   |
| `Navbar`       | `NavbarBrand`, `NavbarContent`, `NavbarItem`, `NavbarMenu`, `NavbarMenuToggle`, `NavbarMenuItem` | `@heroui/react`   |
| `Pagination`   | `PaginationItem`, `PaginationCursor`                                                             | `@heroui/react`   |
| `Popover`      | `PopoverContent`, `PopoverTrigger`                                                               | `@heroui/react`   |
| `Progress`     | `CircularProgress`                                                                               | `@heroui/react`   |
| `Radio`        | `RadioGroup`                                                                                     | `@heroui/react`   |
| `ScrollShadow` | —                                                                                                | `@heroui/react`   |
| `Select`       | `SelectItem`, `SelectSection`                                                                    | `@heroui/react`   |
| `Skeleton`     | —                                                                                                | `@heroui/react`   |
| `Slider`       | —                                                                                                | `@heroui/react`   |
| `Snippet`      | —                                                                                                | `@heroui/react`   |
| `Spacer`       | —                                                                                                | `@heroui/react`   |
| `Spinner`      | —                                                                                                | `@heroui/react`   |
| `Switch`       | —                                                                                                | `@heroui/react`   |
| `Table`        | `TableBody`, `TableCell`, `TableColumn`, `TableHeader`, `TableRow`                               | `@heroui/react`   |
| `Tabs`         | `Tab`                                                                                            | `@heroui/react`   |
| `Toaster`      | —                                                                                                | `react-hot-toast` |
| `Tooltip`      | —                                                                                                | `@heroui/react`   |

Also re-exported: `useDisclosure` hook, `toast` function (from `react-hot-toast`, extended with `.progress()` and `.update()`), and `ProgressToast` component.

### Styled Variants

Extended versions of base components with project-specific default styling via `extendVariants()`.

| Component           | Base          | Extra Variants                                                    | Description                                                                                          |
| ------------------- | ------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `StyledButton`      | `Button`      | `color`: `transparent`, `content1`–`content4`; `size`: `xs`       | Adds content-layer background colors and extra-small size. Disables ripple and animation by default. |
| `StyledInput`       | `Input`       | `variant.bordered`: custom border colors; `size.sm`: smaller text | Bordered variant with theme-consistent borders.                                                      |
| `StyledNumberInput` | `NumberInput` | `variant.bordered`, `size.sm`                                     | Bordered variant with hidden stepper by default.                                                     |
| `StyledTextArea`    | `Textarea`    | `variant.bordered`, `size.sm`                                     | Bordered variant with theme-consistent borders.                                                      |
| `StyledBadge`       | `Badge`       | `color.primary`: transparent border                               | Primary badge without visible border.                                                                |
| `StyledDivider`     | `Divider`     | `variant`: `default`, `light`                                     | Divider with `light` (80% opacity border) as default.                                                |
| `StyledModal`       | `Modal`       | `variant.default`: border, compact padding, hidden mobile close   | Opinionated modal layout with border and compact spacing.                                            |
| `StyledPopover`     | `Popover`     | `color.default`: border, no padding                               | Bordered popover with zero inner padding.                                                            |
| `StyledTable`       | `Table`       | `variant.default`: full custom layout                             | Styled table with borders, hover rows, header/row separators, responsive text sizes.                 |
| `StyledTabs`        | `Tabs`        | `variant`: `solid`, `plain`, `primaryPlain`, `light`              | Four custom tab styles. Disables animation by default.                                               |
| `StyledTooltip`     | `Tooltip`     | `variant.default`, `layout`: `xs`/`sm`/`md`/`lg`                  | Themed tooltip with size variants via `layout` prop.                                                 |
| `StyledToaster`     | `Toaster`     | —                                                                 | Pre-styled toaster with theme-consistent colors and borders.                                         |

### Form Components

Container components that integrate with `react-hook-form` via `useFormContext`. Must be used inside an `RHForm` (or a `FormProvider`).

| Component       | Props                                                                                                                                                                                                        | Description                                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| `RHForm<T>`     | `methods: UseFormReturn<T>`, `onSubmit?: (data: T) => Promise<void>`, `className?`                                                                                                                           | Wraps children in `FormProvider` + HeroUI `Form`. Handles `handleSubmit` wiring.                            |
| `RHInput`       | `name: string`, `errorMessage?: (error: FieldError) => ReactNode` + all `InputProps`                                                                                                                         | Controlled text input bound to form field. Falls back to `error.message` when `errorMessage` is omitted.    |
| `RHNumberInput` | `name: string`, `errorMessage?: (error: FieldError) => ReactNode` + all `NumberInputProps`                                                                                                                   | Controlled number input bound to form field.                                                                |
| `RHTextArea`    | `name: string`, `errorMessage?: (error: FieldError) => ReactNode` + all `TextAreaProps`                                                                                                                      | Controlled textarea bound to form field.                                                                    |
| `RHUpload`      | `name`, `label?`, `placeholder?`, `hint?`, `icon?`, `errorMessage?`, `upload?`, `onUploadError?`, `submitText?`, `resubmitText?`, `accept?`, `maxSize?`, `maxFiles?`, `radius?`, `className?`, `classNames?` | File upload field with drag-and-drop, image preview, loading state. Defaults to 10MB max, image types only. |

### Custom Components

| Component                   | Props                                                                                                                                                                          | Description                                                                                                      |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `HorizontalScrollContainer` | `scrollAmount?: number` (default 150), `className?`, `classNames?: { root, content, leftArrow, leftArrowButton, leftArrowIcon, rightArrow, rightArrowButton, rightArrowIcon }` | Horizontally scrollable container with hover-activated arrow buttons and gradient edge masks.                    |
| `Sortable`                  | `sort?: "asc" \| "desc"`, `onSortChange?: (sort?: "asc" \| "desc") => void`                                                                                                    | Sort toggle indicator. Cycles: undefined → desc → asc → undefined. Renders children with up/down triangle icons. |
| `Upload`                    | All `DropzoneOptions` (from `react-dropzone`), `children`                                                                                                                      | Thin wrapper around `useDropzone`. Exposes `UploadRef` with `open()` via `forwardRef`.                           |

### Hooks

| Hook                 | Signature                                                          | Returns          | Description                                                                                                             |
| -------------------- | ------------------------------------------------------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `useCopyToClipboard` | `() => (text: string, onSuccess?: (text: string) => void) => void` | Copy callback    | Copies text to clipboard. When `onSuccess` is provided, it is called after a successful copy. No built-in side effects. |
| `useScreen`          | `() => { isMobile, isTablet, isDesktop }`                          | Breakpoint flags | Mobile: `<=640px`, Tablet: `641–1024px`, Desktop: `>1024px`. Mutually exclusive.                                        |
| `useMediaQuery`      | `(query: string) => boolean`                                       | Match result     | Reactive CSS media query match. SSR-safe (returns `false` on server).                                                   |
| `useDisclosure`      | `(props?) => { isOpen, onOpen, onClose, onOpenChange, ... }`       | Disclosure state | Re-exported from `@heroui/react`. Controls open/close state for modals, popovers, etc.                                  |

### Icons

70+ SVG icon components exported from `src/icons/`. All accept standard `SVGProps<SVGSVGElement>` (aliased as `SvgIconProps`).

**Chain icons:** `BinanceIcon`, `EthereumIcon`, `SolanaIcon`

**UI icons (partial list):** `BundlesIcon`, `CameraIcon`, `CheckIcon`, `ChevronDownIcon`, `ChevronLeftIcon`, `ChevronRightIcon`, `ChevronUpIcon`, `CoinsIcon`, `CookIcon`, `CopyIcon`, `CrownIcon`, `DiscordIcon`, `DraggableIcon`, `EditIcon`, `EmptyIcon`, `ExclamationIcon`, `FAQIcon`, `FilterIcon`, `GiftIcon`, `HomeIcon`, `InfoIcon`, `LightningIcon`, `LogoIcon`, `MaximizeIcon`, `MenuIcon`, `MiniLogoIcon`, `MinimizeIcon`, `PauseIcon`, `PeopleIcon`, `PlusIcon`, `PulseIcon`, `RefreshIcon`, `RocketIcon`, `SearchIcon`, `SettingsIcon`, `ShieldIcon`, `SignalIcon`, `SignInIcon`, `SignOutIcon`, `SniperIcon`, `StarIcon`, `TelegramIcon`, `TimerIcon`, `TradeIcon`, `TranslateIcon`, `TrashIcon`, `TriangleDownIcon`, `TriangleUpIcon`, `TwitterIcon`, `UserIcon`, `WalletIcon`, `XCloseIcon`, `ZapFastIcon`

**Kline chart icons:** `KlineAreaIcon`, `KlineBarsIcon`, `KlineBaseLineIcon`, `KlineCandlesIcon`, `KlineColumnsIcon`, `KlineHelkinAshiIcon`, `KlineHighLowIcon`, `KlineHollowCandlesIcon`, `KlineLineIcon`

### Types

| Type                             | Definition                                                                                                                                                                         | Source                                            |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| `SvgIconProps`                   | `SVGProps<SVGSVGElement>`                                                                                                                                                          | `src/icons/index.tsx`                             |
| `SvgIcon`                        | `FC<SvgIconProps>`                                                                                                                                                                 | `src/icons/index.tsx`                             |
| `ExtendedColor`                  | `ButtonProps["color"] \| "transparent" \| "content1" \| "content2" \| "content3" \| "content4"`                                                                                    | `src/components/base/button`                      |
| `ExtendedButtonProps`            | `Omit<ButtonProps, "color" \| "size"> & { color?: ExtendedColor; size?: ExtendedSize }`                                                                                            | `src/components/base/button`                      |
| `RHFormProps<T>`                 | `{ methods, onSubmit?, className? }`                                                                                                                                               | `src/components/form/RHForm`                      |
| `RHInputProps`                   | `Omit<InputProps, "errorMessage"> & { name, errorMessage? }`                                                                                                                       | `src/components/form/RHInput`                     |
| `RHNumberInputProps`             | `Omit<NumberInputProps, "errorMessage"> & { name, errorMessage? }`                                                                                                                 | `src/components/form/RHNumberInput`               |
| `RHTextAreaProps`                | `Omit<TextAreaProps, "errorMessage"> & { name, errorMessage? }`                                                                                                                    | `src/components/form/RHTextArea`                  |
| `RHUploadProps`                  | `{ name, label?, placeholder?, hint?, icon?, errorMessage?, upload?, onUploadError?, submitText?, resubmitText?, accept?, maxSize?, maxFiles?, radius?, className?, classNames? }` | `src/components/form/RHUpload`                    |
| `SortableProps`                  | `{ sort?, onSortChange?, children }`                                                                                                                                               | `src/components/sortable/Sortable`                |
| `UploadProps`                    | `PropsWithChildren<DropzoneOptions>`                                                                                                                                               | `src/components/upload/Upload`                    |
| `UploadRef`                      | `{ open: () => void }`                                                                                                                                                             | `src/components/upload/Upload`                    |
| `HorizontalScrollContainerProps` | `{ scrollAmount?, className?, classNames?, children }`                                                                                                                             | `src/components/scroll/HorizontalScrollContainer` |
| `ProgressToastProps`             | `{ id?, type?, duration?, message, action?, progress? }`                                                                                                                           | `src/components/base/toast/progress-toast`        |

All HeroUI component prop types (e.g. `ButtonProps`, `InputProps`, `ModalProps`, `TableProps`, etc.) are also re-exported.

### Utilities

| Export    | Signature                                                            | Description                                                                                             |
| --------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `cn`      | `(...inputs: ClassValue[]) => string`                                | Merges class names using `clsx` + `tailwind-merge`. Deduplicates and resolves Tailwind class conflicts. |
| `toast`   | `toast.success(msg)`, `toast.error(msg)`, `.progress()`, `.update()` | Extended from `react-hot-toast`. See **Toast API** below.                                               |
| `version` | `string` (`"0.1.33"`)                                                | Current package version. Also registers on `window.__LIBERFI_VERSION__` at import time.                 |

#### Toast API

The `toast` object extends `react-hot-toast` with two additional methods:

| Method           | Signature                                                         | Description                                                                  |
| ---------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `toast.progress` | `(options: ProgressToastProps) => string`                         | Create a toast with an optional animated progress bar. Returns the toast id. |
| `toast.update`   | `(id: string, options: Omit<ProgressToastProps, "id">) => string` | Update an existing toast in-place (same id, new content/type).               |

`ProgressToastProps`:

| Prop       | Type                              | Default | Description                                                                |
| ---------- | --------------------------------- | ------- | -------------------------------------------------------------------------- |
| `id`       | `string?`                         | —       | Stable id. Pass the same id to `toast.update()` to replace in-place.       |
| `type`     | `"success" \| "error" \| "blank"` | —       | Controls the leading icon (checkmark or error).                            |
| `duration` | `number`                          | `5000`  | Auto-dismiss duration in milliseconds.                                     |
| `message`  | `ReactNode`                       | —       | Main content.                                                              |
| `action`   | `ReactNode?`                      | —       | Trailing slot for action buttons (e.g. explorer link).                     |
| `progress` | `boolean?`                        | —       | When `true`, a background bar animates from left to right over `duration`. |

### Tailwind Plugin

Exported via the `@liberfi.io/ui/tailwind` sub-path.

**Plugin (`src/tailwind/plugin.ts`):** Configures HeroUI's Tailwind plugin with custom layout (font sizes, line heights, border radii, opacity) and a full color palette for both light and dark themes, including `primary`, `secondary`, `default`, `success`, `warning`, `danger`, `content1`–`content4`, `background`, `foreground`, `focus`, `overlay`, and `divider`.

**CSS (`src/tailwind/tailwind.css`):** Extends Tailwind with:

- Custom font size variables (`--text-xxs` through `--text-lg`)
- Semantic colors: `--color-bullish`, `--color-bearish`, `--color-neutral`, `--color-placeholder`
- All HeroUI color variables mapped to Tailwind `--color-*` custom properties
- Layout tokens: `--opacity-disabled`, `--opacity-hover`, `--radius-sm/md/lg`
- Custom animations: `modal-shrink`, `toast-progress`
- Base styles: scrollbar hiding on mobile, custom scrollbar on desktop, number input spin button removal
- Utility classes: `.no-scrollbar`, token brand colors (`.bg-pump`, `.text-pump`, etc.)

## Usage Examples

### Basic Component Usage

```tsx
import {
  StyledButton,
  StyledInput,
  StyledModal,
  ModalContent,
  ModalBody,
  useDisclosure,
} from "@liberfi.io/ui";

function MyComponent() {
  const { isOpen, onOpen, onClose } = useDisclosure();

  return (
    <>
      <StyledButton color="primary" size="sm" onPress={onOpen}>
        Open Modal
      </StyledButton>

      <StyledModal isOpen={isOpen} onClose={onClose}>
        <ModalContent>
          <ModalBody>
            <StyledInput
              variant="bordered"
              label="Name"
              placeholder="Enter your name"
            />
          </ModalBody>
        </ModalContent>
      </StyledModal>
    </>
  );
}
```

### Form with Validation

```tsx
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { RHForm, RHInput, RHNumberInput, StyledButton } from "@liberfi.io/ui";

const schema = z.object({
  name: z.string().min(1, "validation.name.required"),
  amount: z.number().min(0),
});

type FormData = z.infer<typeof schema>;

function MyForm() {
  const methods = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const handleSubmit = async (data: FormData) => {
    console.log(data);
  };

  return (
    <RHForm methods={methods} onSubmit={handleSubmit}>
      <RHInput name="name" label="Name" variant="bordered" />
      <RHNumberInput name="amount" label="Amount" variant="bordered" />
      <StyledButton type="submit" color="primary">
        Submit
      </StyledButton>
    </RHForm>
  );
}
```

### Tailwind Setup

In your app's CSS entry point:

```css
@import "@liberfi.io/ui/tailwind/tailwind.css";
```

Or use the plugin directly in your Tailwind config:

```ts
// tailwind.config.ts
import uiPlugin from "@liberfi.io/ui/tailwind";

export default {
  plugins: [uiPlugin],
};
```

### Progress Toast (Transaction Confirmation)

```tsx
import { toast } from "@liberfi.io/ui";

// 1. Show a progress toast when a transaction is submitted
toast.progress({
  id: txHash,
  type: "success",
  message: "Transaction submitted",
  progress: true,
  duration: 45_000,
});

// 2. Update the same toast when the transaction confirms
toast.update(txHash, {
  type: "success",
  message: "Transaction confirmed",
  action: (
    <a href={explorerUrl} target="_blank">
      View
    </a>
  ),
  duration: 5_000,
});

// 3. Or update with an error if it fails
toast.update(txHash, {
  type: "error",
  message: "Transaction failed",
  duration: 5_000,
});
```

### Sortable Table Header

```tsx
import { useState } from "react";
import {
  Sortable,
  StyledTable,
  TableHeader,
  TableColumn,
  TableBody,
  TableRow,
  TableCell,
} from "@liberfi.io/ui";

function SortableTable() {
  const [sort, setSort] = useState<"asc" | "desc" | undefined>();

  return (
    <StyledTable>
      <TableHeader>
        <TableColumn>
          <Sortable sort={sort} onSortChange={setSort}>
            Price
          </Sortable>
        </TableColumn>
      </TableHeader>
      <TableBody>
        <TableRow>
          <TableCell>$100</TableCell>
        </TableRow>
      </TableBody>
    </StyledTable>
  );
}
```

## Future Improvements

- **Extract domain-specific code** — Move token brand colors to a dedicated package or the consuming application.
- **Differentiate light/dark themes** — Current plugin defines identical colors for both themes; remove the `light` theme if only dark mode is supported.
- **Guard `version.ts` side effect** — Replace the import-time `window.__LIBERFI_VERSION__` assignment with an explicit `registerVersion()` function.
- **Expand test coverage** — Add tests for `HorizontalScrollContainer`, `RHUpload`, and `RHNumberInput`.