import { Meta, Canvas, Source } from '@storybook/addon-docs/blocks'
import * as Stories from './TenantAdminShell.stories'
import demoSource from './TenantAdminShell.demo.tsx?raw'

<Meta of={Stories} />

# Recipe: Tenant Admin Shell

The skeleton every multi-tenant SaaS backend starts from: a localized app
frame with brand, permission-aware navigation, the signed-in user, and a
tenant switcher in the navbar. Everything the shell needs from your app —
route, permissions, tenant — comes in through props, so the same shell works
in any project.

**Composition** — `LocaleProvider` → `AdminLayout` (brand + navigation +
user) → `TenantSwitcher` in `navbarLeft` → your page content as children.

**Key decisions**

- `LocaleProvider` wraps the whole tree **once at the root** — every built-in
  string and aria-label (logout, tenant search placeholder, "All Tenants")
  resolves to `zhTW` from there. Never re-wrap per page or hardcode strings a
  component already localizes.
- Navigation is **data, not markup**: group-level `visibleWhen(ctx)` hides
  whole sections, item-level `permissions` hides single entries. The layout
  only calls the `hasPermission` function you pass — the permission model
  itself stays in your auth layer (here a role → permission map behind a demo
  role toggle).
- `TenantSwitcher` is **controlled** via `value`/`onChange` (the 0.12
  contract; `currentTenant`/`onSelect` are deprecated). `null` means the
  "All Tenants" platform view, so the page can distinguish tenant scope from
  platform scope.
- Routing: the demo simulates a router with `useState` + `onNavigate`. In a
  real SPA keep `onNavigate` and _also_ pass
  `renderLink={({ href, children, className }) => <Link to={href} …/>}` so
  nav items render as real anchors your router understands.

<Canvas of={Stories.Default} sourceState="none" />

## Full source

Copy-paste and replace the role map and tenant list with your auth store and
API:

<Source code={demoSource} language="tsx" dark />
