# @bureaulabs/web-ds — Figma Make Guidelines

A React component library and design-token system for Ebury products. This file is read by Figma Make to guide code generation. **Follow the rules in Section 1 before generating any code.**

---

## 1. HARD RULES — read these first

1. **Every page-level UI MUST use `<Page>` from this package as the root.** The user's content goes inside `<Page>`'s `children` prop. **Never** build a custom `<div className="page">` or `<main>` shell. The sidebar nav, action bar, page title, and content-area chrome are all provided by this package.

2. **Never re-implement a component that already exists in this package.**
   - Button → `<Button>` (don't write your own button)
   - Dropdown → `<Select>`
   - Icon → `<Icon>` (don't install `lucide-react`, `react-icons`, `@heroicons/react`, or any other icon library)
   - Sidebar → `<Nav>`
   - Page header → `<PageTitle>`
   - Header action row → `<PageActionBar>`
   - App shell → `<Page>`

3. **Never write raw colour values or pixel sizes.** Use the CSS custom properties listed in Section 5. `#f5f5f5` is wrong; `var(--control-primary-background-rest)` is right. `padding: 16px` is wrong; `padding: var(--sizing-16)` is right.

4. **Import the stylesheet exactly once at the app root:**
   ```tsx
   import '@bureaulabs/web-ds/styles'
   ```
   Without this line, no styles render. **Do not** add Tailwind, do not write custom CSS resets — the stylesheet handles all of it.

5. **If you see a wrapper package** (e.g. `@make-kits/web-ds-test`) that re-exports our components, the component names and prop shapes are unchanged. Components are still called `Button`, `Page`, `Nav`, etc. If the wrapper appears empty or missing exports, install `@bureaulabs/web-ds` directly and import from there.

---

## 2. Canonical page template — start here, then fill in the body

Every page you generate should follow this structure. **Copy this template verbatim, then customise only `children`** to put the page's actual content (cards, tables, charts, forms, etc.) into the body slot.

```tsx
import { useState } from 'react'
import '@bureaulabs/web-ds/styles'
import {
  Page,
  Nav,
  PageTitle,
  PageActionBar,
  Button,
  Select,
  Icon,
} from '@bureaulabs/web-ds'

export default function MyPage() {
  const [activePage, setActivePage] = useState('Accounts')

  return (
    <div style={{ minHeight: '100vh' }}>
      <Page
        sidebar={
          <Nav
            activePage={activePage}
            onPageChange={setActivePage}
          />
        }
        actionBar={
          <PageActionBar
            right={
              <>
                <Button variant="secondary" size="sm">Export</Button>
                <Button variant="brand" size="sm" leadingIcon="IconPlusMedium">
                  New
                </Button>
              </>
            }
          />
        }
        title={
          <PageTitle
            title="Accounts"
            subtitle="All currencies"
          />
        }
      >
        {/* ───── PAGE BODY GOES HERE ─────
            Put cards, tables, lists, charts, forms — whatever the page
            is about. Use semantic tokens for any custom styling:
              color: var(--text-primary)
              background: var(--page-bg-color-subtle)
              padding: var(--sizing-24)
              border-radius: var(--radius-medium)
            Compose <Button>, <Select>, <Icon> from this package wherever
            you need interactive controls or iconography.
        */}
      </Page>
    </div>
  )
}
```

### Choosing which slot of `<Page>` to use

| You want to add… | Pass it to `<Page>`'s prop… | Typical wrapper |
|---|---|---|
| A sidebar navigation | `sidebar` | `<Nav>` |
| A row of action buttons at the top of the content | `actionBar` | `<PageActionBar>` |
| The page heading + optional subtitle + heading-level actions | `title` | `<PageTitle>` |
| Cards, tables, charts, forms, the actual page content | `children` | (your own composition, using `<Button>` / `<Select>` / `<Icon>` from this package) |

Any of `sidebar`, `actionBar`, `title` is optional — omit the prop if the page doesn't need that section. `children` is where your real content lives.

---

## 3. Installation & setup

### Install
```bash
npm install @bureaulabs/web-ds
```
No runtime dependencies beyond `react` / `react-dom` (peer deps). Bundle includes all icons and fonts.

### Import the stylesheet **once** at the app root
```tsx
import '@bureaulabs/web-ds/styles'
```
Loads all tokens (`:root` CSS variables), `@font-face` declarations for KMR Apparat + Inter Variable, and global resets.

### Import components by name
```tsx
import { Button, Nav, Page, PageTitle, PageActionBar, Select, Icon } from '@bureaulabs/web-ds'
import type { ButtonProps, NavProps, PageProps /* … */ } from '@bureaulabs/web-ds'
```

---

## 4. Component reference

### `<Icon />`
Renders an icon inside a sized wrapper that inherits `currentColor` from its parent.

```tsx
<Icon name="IconHome" size={20} />
<Icon name="IconChevronRight" size={16} aria-label="Next" />
```

| Prop | Type | Default | Notes |
|---|---|---|---|
| `name` | `CentralIconName` | — | Required. See "Common icons" below. |
| `size` | `16 \| 20 \| 28` | `16` | Maps to `--icon-size-*` tokens |
| `aria-label` | `string` | — | Provide for meaningful icons; omit for decorative ones (`aria-hidden` set automatically) |

#### Common icons (use these names — all are confirmed to exist)

- **Navigation:** `IconHome`, `IconWallet1`, `IconBank`, `IconCreditCard1`, `IconPayment`, `IconCoins`, `IconUmbrellaSecurity`, `IconConnectors1`, `IconChecklist`, `IconSidebar`
- **UI affordances:** `IconChevronBottom`, `IconChevronTop`, `IconChevronLeft`, `IconChevronRight`, `IconPlusMedium`, `IconPlusSmall`, `IconCheckmark`, `IconArrowUpRight`
- **Currency / finance:** `IconCurrencyDollar`, `IconCurrencyEuro`, `IconCurrencyPounds`, `IconCurrencyYen`, `IconCurrencyLira`, `IconCurrencyPesos`, `IconCurrencyRupees`, `IconBank2`, `IconBanknote1`, `IconBanknote2`
- **Search / filter:** `IconSearchMenu`, `IconChartCompare`
- **Status / state:** `IconChecklist`, `IconBurger`
- **Other:** `IconSend`, `IconSendLater`, `IconCoinStack`, `IconCoinsAdd`, `IconAddToBasket`, `IconAddImage`

Any name from the `CentralIconName` type union is valid — TypeScript autocomplete will show you the full list (~2,000 icons). Stick to the names above unless you have a specific reason; they cover the vast majority of needs.

---

### `<Button />`
Primary interactive trigger. Extends all native `<button>` props.

```tsx
<Button variant="brand" size="md">Save changes</Button>
<Button variant="primary" size="sm" leadingIcon="IconHome">Home</Button>
<Button variant="ghost" size="sm" leadingIcon="IconHome" aria-label="Home" />  {/* icon-only */}
<Button variant="secondary" disabled>Disabled</Button>
```

| Prop | Type | Default |
|---|---|---|
| `variant` | `'action' \| 'brand' \| 'primary' \| 'secondary' \| 'ghost'` | `'action'` |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` |
| `leadingIcon` | `CentralIconName` | — |
| `trailingIcon` | `CentralIconName` | — |
| `disabled` | `boolean` | `false` |
| …plus all native `<button>` props (`onClick`, `type`, `aria-*`, etc.) |

**Variant guidance:**
- `brand` — the single most-important action on a page (e.g. "Save", "Create", "Submit")
- `action` — primary dark button (e.g. confirm dialogs)
- `primary` — neutral filled button (toolbar actions)
- `secondary` — outlined neutral (cancel, alternative actions)
- `ghost` — transparent (subtle / icon-only / nav-style buttons)

**Rules:**
- Icon-only buttons (`leadingIcon` without `children`) **must** have `aria-label`.
- Use `size="sm"` inside `<PageActionBar>` and `<PageTitle actions>` (matches 32 px row height).

---

### `<Select />`
Dropdown trigger button. Extends all native `<button>` props.

```tsx
<Select placeholder="Choose currency" />
<Select variant="secondary" size="md" leading={<Icon name="IconBank" size={20} />}>EUR — Euro</Select>
<Select error placeholder="Required" />
```

| Prop | Type | Default | Notes |
|---|---|---|---|
| `variant` | `'primary' \| 'secondary'` | `'primary'` | |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | |
| `error` | `boolean` | `false` | Red error border |
| `leading` | `ReactNode` | — | Typically an `<Icon>` |
| `placeholder` | `string` | — | |
| `children` | `ReactNode` | — | Displayed value; falls back to `placeholder` when empty |

---

### `<NavItem />`
Single navigation row. Used inside `<Nav>`; rarely standalone.

| Prop | Type | Default | Notes |
|---|---|---|---|
| `label` | `string` | — | Required |
| `icon` | `CentralIconName` | — | Only renders at `level="root"` |
| `level` | `'root' \| 'child'` | `'root'` | Child items are indented and don't show icons |
| `isSelected` | `boolean` | `false` | |
| `hasChevron` | `boolean` | `false` | Root only — indicates an expandable group |
| `isExpanded` | `boolean` | `false` | Controls chevron direction |
| `isCollapsed` | `boolean` | `false` | Root only — sidebar icon-only mode |
| `onClick` | `(e) => void` | — | |

---

### `<Nav />`
Application sidebar. Composes `Select` (account picker) + nav-toggle + `NavItem`s + ebury wordmark + 3-dot kebab. Default `items` mirror the full Figma nav catalog; pass `items` to override.

```tsx
<Nav activePage="Accounts" onPageChange={setPage} />
<Nav activePage="Hedge" isCollapsed onToggleCollapse={() => …} />
<Nav items={[...DEFAULT_NAV_ITEMS, { label: 'Reports', icon: 'IconChartBar' }]} />
```

| Prop | Type | Default |
|---|---|---|
| `items` | `NavItemConfig[]` | `DEFAULT_NAV_ITEMS` |
| `activePage` | `string` | — |
| `isCollapsed` | `boolean` | `false` |
| `onPageChange` | `(label: string) => void` | — |
| `onToggleCollapse` | `() => void` | — |
| `account` | `string` | `'CBC Canada'` |
| `onMenuClick` | `() => void` | — |

The group containing `activePage` auto-expands. **Width: 250 px expanded, 68 px collapsed.** Always use inside `<Page sidebar={…} />`, never standalone in a page-level UI.

---

### `<PageActionBar />`
Horizontal action-button row inside `<Page>`'s `actionBar` slot. Two slots (`left`, `right`) with `justify-content: space-between`.

```tsx
<PageActionBar
  left={<>
    <Button variant="primary" size="sm">Filter</Button>
    <Button variant="primary" size="sm">Sort</Button>
  </>}
  right={<>
    <Button variant="secondary" size="sm">Cancel</Button>
    <Button variant="brand" size="sm">Save</Button>
  </>}
/>
```

| Prop | Type |
|---|---|
| `left` | `ReactNode` |
| `right` | `ReactNode` |

Use `size="sm"` for buttons inside the action bar to match the 32 px row height.

---

### `<PageTitle />`
Page heading inside `<Page>`'s `title` slot. 32 px KMR Apparat title + optional subtitle (same size, `--text-tertiary`) + optional right-aligned actions.

```tsx
<PageTitle title="Accounts" />
<PageTitle title="All transactions" subtitle="March 2025" />
<PageTitle
  title="Hedge"
  actions={<>
    <Button variant="secondary" size="sm">Export</Button>
    <Button variant="brand" size="sm">New trade</Button>
  </>}
/>
```

| Prop | Type | Default |
|---|---|---|
| `title` | `ReactNode` | — |
| `subtitle` | `ReactNode` | — |
| `actions` | `ReactNode` | — |
| `as` | `'h1' \| 'h2' \| 'h3' \| 'h4'` | `'h1'` |

Use `as="h2"` on non-top-level pages — visual stays identical, semantic tag changes.

---

### `<Page />`
Application shell — **the root of every page**. Sidebar + content area with structured header (PageActionBar + PageTitle) and a body slot.

```tsx
<Page
  sidebar={<Nav activePage={page} onPageChange={setPage} />}
  actionBar={<PageActionBar left={…} right={…} />}
  title={<PageTitle title="…" subtitle="…" actions={…} />}
>
  {/* main page body content */}
</Page>
```

| Prop | Type | Notes |
|---|---|---|
| `sidebar` | `ReactNode` | Typically `<Nav>` |
| `actionBar` | `ReactNode` | Typically `<PageActionBar>` |
| `title` | `ReactNode` | Typically `<PageTitle>` |
| `children` | `ReactNode` | Page body content |

**Layout chrome (don't try to override):**
- Sidebar pinned left at intrinsic width
- Content area: `pl: 32, pr: 24, pt: 24, pb: 0`; flex column; 32 px gap between header and body
- Header section: PageActionBar on top, PageTitle below, **72 px** between them

`<Page>` fills its parent's height. For a full-viewport app shell wrap in `<div style={{ minHeight: '100vh' }}>`.

---

## 5. Design tokens

Tokens are CSS custom properties exposed on `:root` after importing `@bureaulabs/web-ds/styles`. **Always reference tokens, never raw colours or pixel values.**

### Use semantic tokens, never base palette tokens

| ✅ Do | ❌ Don't |
|---|---|
| `var(--control-brand-background-rest)` | `var(--eds-brand-1000)` |
| `var(--text-primary)` | `var(--eds-grey-1000)` |
| `var(--border-primary)` | `#e5e5e5` |
| `var(--sizing-16)` | `16px` |
| `var(--radius-medium)` | `8px` |

### Token categories

| Prefix | Purpose | Example |
|---|---|---|
| `--page-*` | Page backgrounds | `--page-bg-color-default`, `--page-bg-color-subtle` |
| `--text-*` | Text colours | `--text-primary`, `--text-secondary`, `--text-tertiary` |
| `--border-*` | Border colours | `--border-primary`, `--border-subtle` |
| `--control-*` | Interactive controls | `--control-brand-background-rest`, `--control-text-dark`, `--control-error-stroke` |
| `--switch-*` | Switches | `--switch-base-on`, `--switch-knob-off` |
| `--tag-*` | Tags / badges | `--tag-blue-text`, `--tag-red-stroke` |
| `--font-family-*` | Font families | `--font-family-ui` (Inter), `--font-family-heading` (KMR Apparat) |
| `--font-weight-*` | Font weights | `--font-weight-default` (450), `--font-weight-medium` (500), `--font-weight-semibold` (600) |
| `--font-size-control-*` | Control font sizes | `--font-size-control-sm` (13), `--font-size-control-md` (14), `--font-size-control-lg` (15) |
| `--font-size-heading-*` | Heading font sizes | `--font-size-heading-h1` (32) |
| `--sizing-*` | Spacing scale (px) | `--sizing-4`, `--sizing-8`, `--sizing-12`, … `--sizing-80` |
| `--radius-*` | Border radii | `--radius-small` (4), `--radius-medium` (8), `--radius-large` (12), `--radius-infinite` |
| `--icon-size-*` | Icon dimensions | `--icon-size-16`, `--icon-size-20`, `--icon-size-28` |
| `--stroke-*` | Border widths | `--stroke-default` (1px) |
| `--eds-*` | **Base palette — DO NOT USE in app code.** Reserved for the semantic token layer. | `--eds-grey-500` |

### Sizing scale

Available sizing steps: `0, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80`. Token name = the px value (`--sizing-16` = 16 px).

### Dark mode

Follows the OS preference by default. To override programmatically:
```ts
document.documentElement.setAttribute('data-theme', 'dark')   // force dark
document.documentElement.setAttribute('data-theme', 'light')  // force light
document.documentElement.removeAttribute('data-theme')        // back to OS preference
```
All semantic tokens flip automatically — components need no per-mode code.

---

## 6. CSS class naming (contributors only)

If you're authoring a new component for this package (not consuming it):

- Root class matches component name in kebab-case: `.button`, `.nav-item`, `.page-title`
- BEM modifiers with `--`: `.button--primary`, `.button--sm`, `.nav-item--selected`
- BEM elements with `__`: `.page-title__heading`, `.nav-item__label`
- No prefix (`eds-*` etc.) — keep selectors short
- One CSS file per component, colocated with the `.tsx`

---

## 7. Authoring conventions (contributors only)

### File layout
```
src/components/ComponentName/
├── ComponentName.tsx       ← Component + prop types (both exported)
├── ComponentName.css       ← Scoped BEM styles
├── ComponentName.figma.tsx ← Figma Code Connect mapping
└── index.ts                ← Re-export
```

### TypeScript
- Named exports only (no `default export`)
- Export the props type from the same file
- Extend native element props with `React.ComponentPropsWithoutRef<'button'>` (or `HTMLAttributes<…>`) when wrapping a DOM element

### Publishing
```bash
npm run build:check        # tsc --noEmit
npm run build              # tsup + bundle-styles → dist/
npm run publish:dry        # verify tarball contents
npm publish --access public --otp=<6-digit code>
npx @figma/code-connect publish   # update Figma Make mappings
```

---

*This file ships inside the npm tarball. Figma Make reads it to learn how to compose @bureaulabs/web-ds components. Keep it current as components are added or APIs change.*
