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

2024-01-012024-01-31
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...

Card 1
Card 2
Card 3
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.

Active context id:sponsored-ads

Click 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} />