npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liberfi.io/ui-scaffold

v0.1.91

Published

Page Layout Components for Liberfi React SDK

Readme

@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

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>.

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.

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

Types

LayoutBreakpoint

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

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

NavItem

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

ScaffoldContextValue

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

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

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

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

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)

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

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

Nested trading dashboard layout

<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