# App Layout (`app-layout`) - SignalOS widget

> Full page shell: header, collapsible desktop sidebar, and sheet-based mobile drawer composed around your page content.

- **Version:** 0.2.0
- **Kind:** widget · **Category:** layout
- **Install:** `npx shadcn@latest add @signalos/app-layout`
- **Registry dependencies (pulled automatically):** @signalos/tokens, @signalos/utils
- **npm dependencies:** lucide-react@^1.7.0
- **Files installed:** `src/components/widgets/app-layout/AppLayout.tsx`, `src/components/widgets/app-layout/AppLayout.types.ts`

## Access

This is a private registry: pulling source requires a `SIGNALOS_REGISTRY_TOKEN`
(GitHub fine-grained PAT with read access to the signal-widgets repo) and an
`@signalos` entry in components.json `"registries"`. Previews and this document are public.

## Usage

```tsx
import AppLayout from "@/components/widgets/app-layout/AppLayout"
```

## Example

```tsx
import { useState } from "react"
import { Inbox, LayoutDashboard, Settings } from "lucide-react"

import { AppLayout } from "@/components/widgets/app-layout/AppLayout"
import type { AppLayoutNavItem } from "@/components/widgets/app-layout/AppLayout.types"

const navigationItems: AppLayoutNavItem[] = [
  {
    id: "dashboard",
    label: "Dashboard",
    collapsedLabel: "Home",
    href: "#/dashboard",
    icon: LayoutDashboard,
    active: true,
  },
  {
    id: "feed",
    label: "Signal Feed",
    collapsedLabel: "Feed",
    href: "#/feed",
    icon: Inbox,
  },
  {
    id: "settings",
    label: "Settings",
    href: "#/settings",
    icon: Settings,
    section: "bottom",
  },
]

export default function Example() {
  const [collapsed, setCollapsed] = useState(false)

  return (
    <AppLayout
      navigationItems={navigationItems}
      logo={
        <span className="text-lg font-semibold text-sidebar-foreground">
          Acme Ops
        </span>
      }
      header={<span className="text-sm font-medium">Dashboard</span>}
      isSidebarCollapsed={collapsed}
      onSidebarCollapsedChange={setCollapsed}
    >
      <p className="text-sm text-muted-foreground">
        Route content renders here.
      </p>
    </AppLayout>
  )
}
```

## Props

### `AppLayoutNavItem`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes | - |  |
| `label` | `string` | yes | - |  |
| `href` | `string` | yes | - |  |
| `icon` | `ComponentType<{ className?: string \| undefined; }> \| undefined` | no | - |  |
| `section` | `"main" \| "bottom" \| undefined` | no | - |  |
| `active` | `boolean \| undefined` | no | - |  |
| `collapsedLabel` | `string \| undefined` | no | - |  |

### `AppLayoutLinkComponentProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `href` | `string` | yes | - |  |
| `className` | `string \| undefined` | no | - |  |
| `onClick` | `(() => void) \| undefined` | no | - |  |
| `aria-current` | `"page" \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |
| `children` | `ReactNode` | no | - |  |

### `AppLayoutProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `navigationItems` | `AppLayoutNavItem[]` | yes | - |  |
| `children` | `ReactNode` | yes | - |  |
| `logo` | `ReactNode` | no | - |  |
| `header` | `ReactNode` | no | - |  |
| `isSidebarCollapsed` | `boolean \| undefined` | no | - |  |
| `onSidebarCollapsedChange` | `((collapsed: boolean) => void) \| undefined` | no | - |  |
| `isMobileNavOpen` | `boolean \| undefined` | no | - |  |
| `onMobileNavOpenChange` | `((open: boolean) => void) \| undefined` | no | - |  |
| `onNavigate` | `((item: AppLayoutNavItem) => void) \| undefined` | no | - |  |
| `LinkComponent` | `ComponentType<AppLayoutLinkComponentProps> \| undefined` | no | - | Component used to render navigation links, e.g. Next.js `Link` or a router-aware anchor. Defaults to a plain `<a>` tag (full page reload). |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - | Test identifier rendered as `data-testid` on the root element. |

## Changelog

# app-layout

## 0.2.0

- **Breaking:** rename the `testId` prop to `data-testid` to match the rest
  of the registry and the DOM attribute name.

## 0.1.2

- Removed the `next/link` dependency from sidebar/mobile navigation. Added an
  optional `LinkComponent` prop so host apps can supply their own router-aware
  link (e.g. Next.js `Link`); defaults to a plain `<a>` tag when not provided.
- Content sync fix to bring source in line with published registry payload.

## 0.1.0

- Initial release: full shell composition (header + collapsible sidebar + sheet-style
  mobile drawer + main content area) around a single `<AppLayout>` component. Migrated
  and renamed from the `app-shell2` scaffold; internal `Header`/`Sidebar`/`MobileNav`/
  `NavList` pieces were inlined into `AppLayout.tsx` to match the registry's flat
  per-item file contract.
