npm package: @liberfi.io/ui-scaffold
Description: Page Layout Components for Liberfi React SDK
Author: liberfi.io
Latest version: 0.1.150

# @liberfi.io/ui-scaffold

Page layout and scaffold components for the Liberfi React SDK. Provides a responsive layout shell with configurable header/footer, auto-active navigation, and IoC-friendly design.

## Design Philosophy

- **IoC (Inversion of Control)**: The scaffold does not hardcode routing or side effects. Consumers inject `pathname` and `onNavigate` via props, keeping the library framework-agnostic.
- **Breakpoint-array visibility**: Header and footer visibility is described as an explicit list of breakpoints (e.g. `["desktop", "tablet"]`), making the API symmetric and self-documenting.
- **Slot-based composition**: Header and footer accept either pre-built components (`ScaffoldHeader`, `ScaffoldFooter`) or arbitrary `ReactNode` for full customisation.
- **CSS-variable height system**: Fixed header/footer heights are exposed as CSS custom properties (`--scaffold-header-height`, `--scaffold-footer-height`) so children can compute their own height via `calc()`.

## Installation

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

### Peer dependencies

The consumer must provide:

- `react` >= 18
- `react-dom` >= 18
- `jotai` >= 2.15.1 (used by the draggable sub-module)
- `@liberfi.io/ui` (workspace)
- `@liberfi.io/i18n` (workspace)

## API Reference

### Components

#### `Scaffold`

Main layout container. Manages header/footer areas, CSS variables, responsive visibility, and provides `ScaffoldContext`.

| Prop            | Type                                       | Default                         | Description                                     |
| --------------- | ------------------------------------------ | ------------------------------- | ----------------------------------------------- |
| `pathname`      | `string`                                   | `""`                            | Current route pathname for nav active detection |
| `onNavigate`    | `(href: string) => void`                   | no-op                           | Navigation callback (IoC)                       |
| `header`        | `ReactNode`                                | —                               | Header slot                                     |
| `footer`        | `ReactNode`                                | —                               | Footer slot                                     |
| `headerHeight`  | `number`                                   | `60`                            | Header height in px                             |
| `footerHeight`  | `number`                                   | `56`                            | Footer height in px                             |
| `headerVisible` | `LayoutBreakpoint[]`                       | `["desktop","tablet","mobile"]` | Breakpoints where header is visible             |
| `footerVisible` | `LayoutBreakpoint[]`                       | `[]`                            | Breakpoints where footer is visible             |
| `className`     | `string`                                   | —                               | Wrapper class                                   |
| `classNames`    | `{ wrapper?, header?, content?, footer? }` | —                               | Per-section classes                             |

#### `ScaffoldHeader`

Pre-built desktop header with left/nav/right slots.

| Prop       | Type        | Description                                        |
| ---------- | ----------- | -------------------------------------------------- |
| `left`     | `ReactNode` | Left slot (e.g. Logo)                              |
| `right`    | `ReactNode` | Right slot (e.g. account menu)                     |
| `navItems` | `NavItem[]` | Navigation items with auto-active detection        |
| `children` | `ReactNode` | Full custom override (ignores left/right/navItems) |

#### `ScaffoldFooter`

Pre-built mobile footer with tab-style navigation.

| Prop       | Type        | Description                                 |
| ---------- | ----------- | ------------------------------------------- |
| `navItems` | `NavItem[]` | Navigation items rendered as icon+text tabs |
| `children` | `ReactNode` | Full custom override (ignores navItems)     |

#### `NavLink`

Single navigation link with auto-active detection. Reads `pathname` and `onNavigate` from `ScaffoldContext`.

| Prop      | Type                   | Default    | Description               |
| --------- | ---------------------- | ---------- | ------------------------- |
| `item`    | `NavItem`              | —          | Navigation item to render |
| `variant` | `"header" \| "footer"` | `"header"` | Visual variant            |

#### `Logo`

Logo link component with desktop/mobile icon variants.

| Prop       | Type        | Default | Description                                 |
| ---------- | ----------- | ------- | ------------------------------------------- |
| `icon`     | `ReactNode` | —       | Icon for desktop                            |
| `miniIcon` | `ReactNode` | —       | Icon for mobile (and desktop when below xl) |
| `href`     | `string`    | `"/"`   | URL when clicking the logo                  |

### Hooks

#### `useScaffold()`

Returns the full `ScaffoldContextValue`. Must be used within a `<Scaffold>`.

```typescript
const {
  pathname,
  onNavigate,
  headerHeight,
  footerHeight,
  headerVisible,
  footerVisible,
  activeNavKey,
} = useScaffold();
```

#### `useScaffoldLayout(config)`

Declaratively set the layout for the current page. Overrides on mount, resets to Scaffold defaults on unmount.

```typescript
useScaffoldLayout({
  headerVisible?: LayoutBreakpoint[];
  footerVisible?: LayoutBreakpoint[];
  activeNavKey?: string;
});
```

### Types

#### `LayoutBreakpoint`

```typescript
type LayoutBreakpoint = "desktop" | "tablet" | "mobile";
```

Breakpoint mapping: desktop >= 1024px (lg), tablet 640–1023px (sm–lg), mobile < 640px.

#### `NavItem`

```typescript
type NavItem = {
  key: string;
  label: string;
  href: string;
  icon?: ReactNode;
  match?: string | RegExp | ((pathname: string) => boolean);
};
```

#### `ScaffoldContextValue`

```typescript
type ScaffoldContextValue = {
  pathname: string;
  onNavigate: (href: string) => void;
  headerHeight: number;
  footerHeight: number;
  headerVisible: LayoutBreakpoint[];
  footerVisible: LayoutBreakpoint[];
  activeNavKey: string | undefined;
  setHeaderVisible: (breakpoints: LayoutBreakpoint[]) => void;
  setFooterVisible: (breakpoints: LayoutBreakpoint[]) => void;
  setActiveNavKey: (key: string | undefined) => void;
  defaultHeaderVisible: LayoutBreakpoint[];
  defaultFooterVisible: LayoutBreakpoint[];
};
```

### Utilities

#### `isNavItemActive(item, pathname)`

Check whether a `NavItem` is active for the given pathname.

#### `getVisibilityClass(breakpoints)`

Map a `LayoutBreakpoint[]` to Tailwind visibility class names.

#### `ALL_BREAKPOINTS`

Constant: `["desktop", "tablet", "mobile"]`.

## Usage Examples

### Basic usage with custom header/footer

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

function App() {
  const pathname = usePathname(); // from your router
  const navigate = useNavigate();

  return (
    <Scaffold
      pathname={pathname}
      onNavigate={navigate}
      header={<MyCustomHeader />}
      footer={<MyCustomFooter />}
      headerVisible={["desktop", "tablet"]}
      footerVisible={["mobile"]}
    >
      <MyPageContent />
    </Scaffold>
  );
}
```

### Using built-in navigation

```tsx
import {
  Scaffold,
  ScaffoldHeader,
  ScaffoldFooter,
  Logo,
  type NavItem,
} from "@liberfi.io/ui-scaffold";

const navItems: NavItem[] = [
  { key: "home", label: "Home", href: "/", icon: <HomeIcon /> },
  { key: "trade", label: "Trade", href: "/trade", icon: <TradeIcon /> },
  { key: "account", label: "Account", href: "/account", icon: <AccountIcon /> },
];

function App() {
  return (
    <Scaffold
      pathname={pathname}
      onNavigate={navigate}
      headerVisible={["desktop", "tablet"]}
      footerVisible={["tablet", "mobile"]}
      header={
        <ScaffoldHeader
          left={<Logo icon={<LogoSvg />} />}
          navItems={navItems}
          right={<AccountMenu />}
        />
      }
      footer={<ScaffoldFooter navItems={navItems} />}
    >
      {children}
    </Scaffold>
  );
}
```

### Per-page layout overrides

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

function HomePage() {
  useScaffoldLayout({
    headerVisible: ["desktop", "tablet"],
    footerVisible: ["tablet", "mobile"],
    activeNavKey: "home",
  });
  return <HomeContent />;
}

function TradePage() {
  useScaffoldLayout({
    headerVisible: ["desktop", "tablet", "mobile"],
    footerVisible: [],
  });
  return <TradeContent />;
}
```

### Using CSS variables for height calculation

```tsx
function PageContent() {
  return (
    <div
      style={{
        height:
          "calc(100vh - var(--scaffold-header-height) - var(--scaffold-footer-height))",
      }}
    >
      {/* content with precise height */}
    </div>
  );
}
```

### Split View

#### `SplitView`

Resizable split-view container with a draggable handle between two panes. Supports horizontal (left | right) and vertical (top | bottom) splitting, and can be nested for complex layouts.

| Prop           | Type                                | Default        | Description                                              |
| -------------- | ----------------------------------- | -------------- | -------------------------------------------------------- |
| `direction`    | `"horizontal" \| "vertical"`        | `"horizontal"` | Split direction                                          |
| `primary`      | `ReactNode`                         | —              | Content for the primary (first) pane                     |
| `secondary`    | `ReactNode`                         | —              | Content for the secondary (second) pane                  |
| `defaultSize`  | `number`                            | `400`          | Initial size of the primary pane in px                   |
| `minSize`      | `number`                            | `100`          | Minimum size of the primary pane in px                   |
| `maxSize`      | `number`                            | `Infinity`     | Maximum size of the primary pane in px                   |
| `onSizeChange` | `(size: number) => void`            | —              | Callback when size changes                               |
| `persistId`    | `string`                            | —              | Unique id for localStorage persistence (omit to disable) |
| `className`    | `string`                            | —              | Wrapper class                                            |
| `classNames`   | `{ primary?, secondary?, handle? }` | —              | Per-section classes                                      |

#### `SplitHandle`

Resize handle rendered between panes. Used internally by `SplitView`, but exported for custom layouts.

| Prop            | Type                            | Description                          |
| --------------- | ------------------------------- | ------------------------------------ |
| `direction`     | `"horizontal" \| "vertical"`    | Direction of the split               |
| `isResizing`    | `boolean`                       | Whether a resize is in progress      |
| `onResizeStart` | `(e: React.MouseEvent) => void` | Mouse-down handler to begin resizing |

### Split View Usage

#### Horizontal split (left | right)

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

<SplitView
  direction="horizontal"
  defaultSize={500}
  minSize={200}
  maxSize={800}
  primary={<ChartPanel />}
  secondary={<OrderFormPanel />}
/>;
```

#### Nested trading dashboard layout

```tsx
<SplitView
  direction="horizontal"
  defaultSize={700}
  minSize={400}
  maxSize={1200}
  primary={
    <SplitView
      direction="vertical"
      defaultSize={450}
      minSize={200}
      primary={<ChartPanel />}
      secondary={<PositionsPanel />}
    />
  }
  secondary={<OrderFormPanel />}
/>
```

## Future Improvements

- Animation support for header/footer show/hide transitions
- `DraggablePanelProvider` integration as an optional Scaffold feature
- Keyboard navigation and focus management for nav items
- Touch/pointer event support for SplitView resize on mobile