@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
pathnameandonNavigatevia 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 arbitraryReactNodefor 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 viacalc().
Installation
pnpm add @liberfi.io/ui-scaffoldPeer dependencies
The consumer must provide:
react>= 18react-dom>= 18jotai>= 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
DraggablePanelProviderintegration as an optional Scaffold feature- Keyboard navigation and focus management for nav items
- Touch/pointer event support for SplitView resize on mobile
