Intentwise Primitives
Layout
Structural components for building dashboard pages — shells, headers, and section dividers.
DashboardHeader
Full Header
Page-level header with title, subtitle, date range badge, and action slot.
Sponsored Ads
Campaign Performance Dashboard
Show code
<DashboardHeader
title="Sponsored Ads"
subtitle="Campaign Performance Dashboard"
dateRange={{ start: '2024-01-01', end: '2024-01-31' }}
actions={<button>Export</button>}
/>Minimal Header
Title-only header for simpler pages.
Product Catalog
Show code
<DashboardHeader title="Product Catalog" />With Description
Header with a longer description paragraph.
Analytics
Performance Overview
Track key metrics across all campaigns, ad groups, and keywords. Data refreshes every 4 hours.
Show code
<DashboardHeader
title="Analytics"
subtitle="Performance Overview"
description="Track key metrics across all campaigns..."
/>SectionHeader
Basic Section Header
Section divider with title and optional description. Used to separate content blocks within a page.
Campaign Performance
Key metrics for your active Sponsored Products campaigns over the selected date range.
Show code
<SectionHeader title="Campaign Performance" description="..." />With Action Controls
Action slot supports buttons, toggles, or any React node aligned to the right.
Keyword Analysis
Top performing keywords by click-through rate
Show code
<SectionHeader title="Keywords" actions={<SegmentedControl ... />} />DashboardShell
Full-Page Shell
The outermost layout wrapper. Provides a max-width container with consistent padding. Supports an optional sidebar.
Mini Dashboard Preview
Showing DashboardShell layout
Content Section
Content goes here...
Show code
<DashboardShell>
<DashboardHeader title="..." />
<SectionHeader title="..." />
{/* page content */}
</DashboardShell>DashboardSidebar
variant="flat" — vertical icon + label
Flat variant: icon + label per row, controlled active item via activeId / onActiveChange. Optional width sets CSS width on the sidebar root (all variants). Use the PanelLeftClose toggle above the content to collapse the sidebar to an icon-only rail. Click an item to see the item + parent payload on the right-side content pane.
Flat sidebar
Icon + label per row. Click an item to see the payload on the right.
Select an item — the item + parent payload appears here.
Show code
const SIDEBAR_FLAT_ITEMS: DashboardSidebarFlatItem[] = [
{ id: 'overview', label: 'Overview', icon: <LayoutDashboard className="h-5 w-5" /> },
{ id: 'campaigns', label: 'Campaigns', icon: <Megaphone className="h-5 w-5" /> },
{ id: 'keywords', label: 'Keywords', icon: <Search className="h-5 w-5" /> },
{ id: 'products', label: 'Products', icon: <Package className="h-5 w-5" /> },
];
// Flat has no onLeafPress — the "click" signal is onActiveChange(id).
// Look up the item and render { item, parent } in the main content pane on
// the right, same layout shape every other variant uses.
function FlatSidebarShellDemo() {
const [activeId, setActiveId] = useState('overview');
const [click, setClick] = useState<LeafClick | null>(null);
const handleActiveChange = (id: string) => {
setActiveId(id);
const item = SIDEBAR_FLAT_ITEMS.find((i) => i.id === id);
if (!item) return;
setClick({
item: { id: item.id, label: item.label },
parent: { id: 'flat-navigation', label: 'Navigation' }, // flat has no real group
});
};
return (
<div className="flex h-full min-h-0 overflow-hidden">
{/* Left: sidebar */}
<div className="w-56 shrink-0 border-r bg-iw-surface">
<DashboardSidebar
variant="flat"
width="100%"
items={SIDEBAR_FLAT_ITEMS}
activeId={activeId}
onActiveChange={handleActiveChange}
/>
</div>
{/* Right: main content + click details */}
<div className="flex min-w-0 flex-1 flex-col overflow-hidden bg-iw-background">
<DashboardHeader title="Flat sidebar" />
<div className="p-4 text-sm">
<ClickDetails click={click} />
</div>
</div>
</div>
);
}variant="flat" — horizontal icon-only rail (left sidebar)
Set showLabels={false} and flatLayout="horizontal" for a compact icon-only rail. Optional width sets CSS width on the sidebar root (all variants). Rendered inside a narrow left sidebar column; the horizontal rail's flex-row flex-wrap makes icons wrap vertically to fill the column. The DashboardShell toggle collapses the rail further to the icon-only column. Click details render in the content pane on the right.
Icon rail
Horizontal icon-only rail inside the left sidebar; icons wrap in the narrow column.
Tap an icon — the item + parent payload appears here.
Show code
const SIDEBAR_CONTEXT_RAIL: DashboardSidebarFlatItem[] = [
{ id: 'optimize', label: 'Sponsored Ads', icon: <Megaphone className="h-5 w-5" /> },
{ id: 'intelligence', label: 'Intelligence', icon: <LineChart className="h-5 w-5" /> },
{ id: 'foundation', label: 'Foundation', icon: <Layers className="h-5 w-5" /> },
{ id: 'community', label: 'Joint community',icon: <UsersRound className="h-5 w-5" /> },
{ id: 'global-settings', label: 'Global settings',icon: <Settings className="h-5 w-5" /> },
];
// Horizontal icon rail inside the LEFT sidebar column (same layout shape
// as the other variants). The rail's flex-row flex-wrap makes icons wrap
// vertically to fill the narrow column while staying icon-only.
function FlatIconRailShellDemo() {
const [activeId, setActiveId] = useState('optimize');
const [click, setClick] = useState<LeafClick | null>(null);
const handleActiveChange = (id: string) => {
setActiveId(id);
const item = SIDEBAR_CONTEXT_RAIL.find((i) => i.id === id);
if (!item) return;
setClick({
item: { id: item.id, label: item.label },
parent: { id: 'flat-icon-rail', label: 'Product rail' },
});
};
return (
<div className="flex h-full min-h-0 overflow-hidden">
{/* Left: narrow sidebar column holding the horizontal rail */}
<div className="w-20 shrink-0 border-r bg-iw-surface">
<DashboardSidebar
variant="flat"
width="100%"
flatLayout="horizontal"
showLabels={false}
items={SIDEBAR_CONTEXT_RAIL}
activeId={activeId}
onActiveChange={handleActiveChange}
/>
</div>
{/* Right: content pane + click details */}
<div className="flex min-w-0 flex-1 flex-col bg-iw-background">
<DashboardHeader title="Icon rail" />
<div className="p-4 text-sm">
<ClickDetails click={click} />
</div>
</div>
</div>
);
}variant="rail-stack" — icon rail + contextual menu
Pass variant="rail-stack" with a typed groups tree (string icon keys, url on item leaves, optional externallink). Item rows may nest **`children`** for contextual submenus (chevron folders under a product tile). **Intelligence**, **Foundation**, and pinned **Global Settings** use item-only accordion rows; **Optimize** exposes a product icon rail then the contextual menu. defaultId seeds the initial product tile; **onMenuChange** fires with **`DashboardSidebarMenuChangeDetail`** (id, label, source, section, optional nestedRail, **`contextualMenu`**, **`pinnedMenu`**) whenever the selected menu context changes. Optional width sets CSS width on the sidebar root (all variants); pair DashboardShell sidebarWidth when both apply. Route-based selection follows the browser URL by default — set activePathname when your app pathname differs. Use the DashboardShell toggle to collapse the full rail-stack to an icon-only rail. Leaf rows never self-navigate: implement routing in onLeafPress.
Rail-stack sidebar
Icon rail → product tile → contextual menu.
sponsored-adsClick a leaf — the sidebar does not navigate by itself. Only onLeafPress runs; the item + parent payload appears here.
Show code
// Host-app JSON shape: string Lucide icon keys, `url` on leaves,
// optional `children` on type: "item" for nested contextual submenus,
// top-level `externallink`. Passed as `groups` to variant="rail-stack".
// Optional `width` — CSS width on the sidebar root (flat / groups / rail-stack).
const RAIL_NAVIGATION_TREE: DashboardSidebarRailNavTreeNode[] = [
{
id: 'optimize',
label: 'Optimize',
type: 'group',
icon: 'SlidersHorizontal',
items: [
{
id: 'sponsored-ads',
label: 'Sponsored Ads',
type: 'rail',
icon: 'Megaphone',
items: [
{ id: 'sponsored-ads-overview', label: 'Overview', type: 'item', url: '/sponsored-ads/overview' },
{
id: 'sponsored-ads-reporting',
label: 'Reporting & exports',
type: 'item',
children: [
{ id: 'sponsored-ads-weekly', label: 'Weekly performance', type: 'item', url: '/sponsored-ads/reports/weekly' },
],
},
],
},
{
id: 'dsp',
label: 'DSP',
type: 'rail',
icon: 'Megaphone',
items: [{ id: 'dsp-overview', label: 'Overview', type: 'item', url: '/dsp/overview' }],
},
],
},
{
id: 'intelligence',
label: 'Intelligence',
type: 'group',
icon: 'LineChart',
items: [
{
id: 'intelligence-performance-hub',
label: 'Performance insights',
type: 'item',
children: [
{ id: 'intelligence-overview-dash', label: 'Overview dashboard', type: 'item', url: '/intelligence/overview' },
],
},
{ id: 'intelligence-alerts', label: 'Alerts', type: 'item', url: '/intelligence/alerts' },
],
},
{
id: 'foundation',
label: 'Foundation',
type: 'group',
icon: 'LayoutGrid',
items: [
{
id: 'foundation-org-access',
label: 'Organization & access',
type: 'item',
children: [
{ id: 'foundation-users', label: 'Users', type: 'item', url: '/foundation/users' },
],
},
],
},
{ id: 'join-intentwise-community', label: 'Join community', type: 'externallink', icon: 'LayoutGrid', pin: 'bottom', url: 'https://www.intentwise.com' },
{
id: 'global-settings',
label: 'Global Settings',
type: 'group',
pin: 'bottom',
icon: 'LayoutGrid',
items: [
{
id: 'settings-security',
label: 'Security & compliance',
type: 'item',
children: [
{ id: 'settings-audit', label: 'Audit log', type: 'item', url: '/settings/audit' },
],
},
],
},
];
function RailStackShellDemo() {
const [menuContextId, setMenuContextId] = useState('sponsored-ads');
const [click, setClick] = useState<LeafClick | null>(null);
// onLeafPress payload for rail-stack:
// - detail.groupId = contextual menu id (e.g. 'sponsored-ads-menu')
// - detail.itemId = leaf id (e.g. 'sponsored-ads-overview')
// - detail.label = leaf label
// - detail.url / detail.href = leaf link when defined
const handleLeafPress = (detail: DashboardSidebarLeafPressDetail) => {
const parent = resolveRailParent(RAIL_NAVIGATION_TREE, detail.groupId);
setClick({
item: { id: detail.itemId, label: detail.label, href: detail.href, url: detail.url },
parent,
});
if (detail.href) router.push(detail.href); // host-app routing, not the sidebar
};
return (
<DashboardShell
sidebar={
<DashboardSidebar
variant="rail-stack"
width="100%"
// groups config (typed tree — preferred over legacy railNavigationTree)
groups={RAIL_NAVIGATION_TREE}
// defaultId value — initial selected product tile / menu context
defaultId="sponsored-ads"
// callback for menuchange — fires whenever the active rail / tile changes
onMenuChange={(detail) => {
console.log('menu context changed:', detail);
setMenuContextId(detail.id);
}}
// Leaf click — do routing + log item/parent here. Sidebar never navigates.
onLeafPress={handleLeafPress}
/>
}
>
<DashboardHeader title="Rail-stack sidebar" subtitle={`Active: ${menuContextId}`} />
<ClickDetails click={click} />
</DashboardShell>
);
}variant="groups" — accordion navigation
Groups variant: accordion rows with optional pin. Items can be plain strings (leaf links, no chevron) or objects with children (nested group with chevron). Optional width sets CSS width on the sidebar root (all variants). onLeafPress emits { groupId, itemId, label, href?, url? } — the demo resolves groupId → parent label so both the clicked item and its parent group show on the right.
Grouped sidebar
Expand a group and click a leaf — payload appears on the right.
Click a leaf — the item + parent (group) payload appears here.
Show code
const SIDEBAR_NAV_GROUPS: DashboardSidebarGroup[] = [
{
id: 'optimize',
label: 'Optimize',
icon: <SlidersHorizontal className="h-5 w-5" />,
items: [
'Bid rules',
{
id: 'budget-spend',
label: 'Budget & spend',
children: [
'Budget caps',
{
id: 'placement-nested',
label: 'Placement',
children: ['Top of search', 'Product pages', 'Rest of search'],
},
],
},
'Placement controls',
'Dayparting',
],
},
{
id: 'intelligence',
label: 'Intelligence',
icon: <LineChart className="h-5 w-5" />,
items: ['Performance summary', 'Attribution', 'Forecasts', 'Alerts'],
},
{
id: 'foundation',
label: 'Foundation',
icon: <LayoutGrid className="h-5 w-5" />,
items: ['Accounts', 'Catalog sync', 'Integrations', 'Users & roles'],
},
];
function GroupsSidebarDemo({ groups }: { groups: DashboardSidebarGroup[] }) {
const [click, setClick] = useState<LeafClick | null>(null);
// onLeafPress fires for every leaf click. The payload carries both:
// - item: { id, label, href?, url? } ← the leaf that was clicked
// - parent: { id, label } ← the accordion group it lives in
const handleLeafPress = (detail: DashboardSidebarLeafPressDetail) => {
const parent = groups.find((g) => g.id === detail.groupId);
setClick({
item: { id: detail.itemId, label: detail.label, href: detail.href, url: detail.url },
parent: parent ? { id: parent.id, label: parent.label } : null,
});
};
// Sidebar on the left (DashboardShell sidebar slot), ClickDetails rendered
// in the main content area on the right — same right-side payload pattern
// used by rail-stack.
return (
<DashboardShell
sidebar={
<DashboardSidebar variant="groups" width="100%" groups={groups} onLeafPress={handleLeafPress} />
}
>
<DashboardHeader title="Grouped sidebar" />
<div className="p-4 text-sm">
<ClickDetails click={click} />
</div>
</DashboardShell>
);
}variant="groups" — pinned sections (top / bottom)
Pinned groups stay at top and bottom; unpinned groups scroll in the middle. Set pin: 'top' | 'bottom' on any group. Optional width sets CSS width on the sidebar root (all variants). onLeafPress still reports the item id/label plus the parent group id for every click.
Pinned grouped sidebar
Shortcuts stay at top; Foundation stays at bottom. Click a leaf for the payload.
Click a leaf — the item + parent (group) payload appears here.
Show code
const SIDEBAR_NAV_GROUPS_PINNED: DashboardSidebarGroup[] = [
{
id: 'shortcuts',
label: 'Shortcuts',
icon: <Bookmark className="h-5 w-5" />,
items: ['Saved views', 'Recent reports', 'Starred campaigns'],
},
{
id: 'optimize',
label: 'Optimize',
icon: <SlidersHorizontal className="h-5 w-5" />,
items: ['Bid rules', 'Budget caps', 'Placement controls', 'Dayparting'],
},
{
id: 'intelligence',
label: 'Intelligence',
icon: <LineChart className="h-5 w-5" />,
items: ['Performance summary', 'Attribution', 'Forecasts', 'Alerts'],
},
{
id: 'operations',
label: 'Operations',
icon: <Layers className="h-5 w-5" />,
items: [
'Campaigns', 'Portfolios', 'Rules', 'Labels', 'Comments',
'Change log', 'Scheduled actions', 'Bulk edits', 'Audiences', 'Creative assets',
],
},
{
id: 'foundation',
label: 'Foundation',
icon: <LayoutGrid className="h-5 w-5" />,
pin: 'bottom', // `pin: 'top' | 'bottom'` partitions the rail
items: ['Accounts', 'Catalog sync', 'Integrations', 'Users & roles'],
},
];
// Same GroupsSidebarDemo as above — onLeafPress receives groupId (parent)
// and itemId/label (item) for every click, regardless of pin placement.
<DashboardShell
sidebar={
<DashboardSidebar
variant="groups"
width="100%"
groups={SIDEBAR_NAV_GROUPS_PINNED}
onLeafPress={(detail) => {
const parent = SIDEBAR_NAV_GROUPS_PINNED.find((g) => g.id === detail.groupId);
console.log('item:', { id: detail.itemId, label: detail.label });
console.log('parent:', parent && { id: parent.id, label: parent.label });
}}
/>
}
/>PageTabs
Page Tabs
Horizontal tab bar with underline indicator for page-level navigation.
Active tab: overview
Show code
<PageTabs items={items} activeId={activeId} onChange={setActiveId} />