# Side Panel

Painel lateral para inspeção, edição contextual e navegação sem abandonar a página atual.

> Documentação do Sinapse DS. Snippets shadcn usam os componentes do seu projeto (`@/components/ui/*`); snippets HTML usam as classes `itps-*` do CSS Sinapse (`/css/<versão>/`, veja `/comecar.md`). Componentes do site de documentação não fazem parte do DS.

- Categoria: Overlays
- Status: stable
- Relacionados: /componentes/modal.md, /componentes/tabs.md

## Em cada stack

- HTML e Astro: Classes `itps-*` do CSS Sinapse, com todas as variantes desta página.
- React e Vue: Componentes padrão do shadcn (`sheet`, `tabs`) com o tema Sinapse: cores, fonte e raio vêm do tema, e as variantes `itps-*` desta página existem só no HTML. O Button é o Sinapse.

**React/Next (shadcn/ui)**

```tsx
import { Button } from "@/components/ui/button";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";

export function PatientSheet() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button>Ver paciente</Button>
      </SheetTrigger>
      <SheetContent side="right" className="sm:max-w-xl">
        <SheetHeader>
          <SheetTitle>Maria Silva</SheetTitle>
          <SheetDescription>Atendimento iniciado há 12 minutos</SheetDescription>
        </SheetHeader>
        <div className="py-4">
          <p>Conteúdo livre do painel.</p>
        </div>
      </SheetContent>
    </Sheet>
  );
}
```

**Vue 3/Nuxt 4 (shadcn-vue)**

```vue
<script setup lang="ts">
import { Button } from "@/components/ui/button";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
</script>

<template>
  <Sheet>
    <SheetTrigger as-child>
      <Button>Ver paciente</Button>
    </SheetTrigger>
    <SheetContent side="right" class="sm:max-w-xl">
      <SheetHeader>
        <SheetTitle>Maria Silva</SheetTitle>
        <SheetDescription>Atendimento iniciado há 12 minutos</SheetDescription>
      </SheetHeader>
      <div class="py-4">
        <p>Conteúdo livre do painel.</p>
      </div>
    </SheetContent>
  </Sheet>
</template>
```

**HTML (copy-first)**

```html
<button type="button" class="itps-btn itps-btn--primary" data-sidepanel-open="patient-panel">
  Ver paciente
</button>

<div id="patient-panel" class="itps-side-panel" data-sidepanel aria-hidden="true">
  <div class="itps-side-panel__backdrop" data-sidepanel-backdrop></div>
  <aside role="dialog" aria-modal="true" aria-labelledby="patient-panel-title" aria-describedby="patient-panel-description" tabindex="-1" class="itps-side-panel__surface" data-sidepanel-content>
    <header class="itps-side-panel__header">
      <div class="itps-side-panel__header-main">
        <div class="itps-side-panel__copy">
          <h2 id="patient-panel-title" class="itps-side-panel__title">Maria Silva</h2>
          <p id="patient-panel-description" class="itps-side-panel__description">Atendimento iniciado há 12 minutos</p>
        </div>
        <div class="itps-side-panel__actions">
          <button type="button" class="itps-side-panel__close" aria-label="Fechar painel" data-sidepanel-close>
            <i class="ti ti-x" aria-hidden="true"></i>
          </button>
        </div>
      </div>
    </header>
    <div class="itps-side-panel__body" data-sidepanel-body>
      <p>Conteúdo livre do painel.</p>
    </div>
  </aside>
</div>
```

## Instalação

O comando instala o Sheet e o Tabs da sua stack e o Button Sinapse com o tema Sinapse. Configure o registry uma vez por projeto em [Comece por aqui](/comecar).

**terminal · React/Next**

```sh
pnpm dlx shadcn@latest add @sinapse/side-panel
```

**terminal · Vue 3/Nuxt 4**

```sh
pnpm dlx shadcn-vue@latest add https://dsystem.sinapse.org.br/r/vue/side-panel.json
```

Sem shadcn, não há instalação por componente: use o CSS Sinapse e as classes da API copy-first desta página.

## Uso

Use Side Panel quando a pessoa precisa consultar ou editar um contexto sem sair da página atual. Para uma decisão bloqueante, use Modal.

Em React/Next com shadcn/ui e Vue 3/Nuxt 4 com shadcn-vue, use `Sheet` com `side="right"`. Para HTML ou wrappers finos, use `itps-side-panel` com os hooks `data-sidepanel*`.

## Exemplos

### Com metadados, ações e tabs

Use metadados no cabeçalho quando a informação ajuda a confirmar contexto. Em tabs, conecte cada `itps-side-panel__tab` a um `role="tabpanel"` real por `aria-controls`.

**React/Next (shadcn/ui)**

```tsx
import { IconEdit, IconShare } from "@tabler/icons-react";

import { Button } from "@/components/ui/button";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

export function RecordSheet() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button variant="secondary">Prontuário</Button>
      </SheetTrigger>
      <SheetContent side="right" className="sm:max-w-xl">
        <SheetHeader>
          <div className="flex items-start justify-between gap-3">
            <div>
              <SheetTitle>Ana Costa</SheetTitle>
              <SheetDescription>Dados do atendimento atual</SheetDescription>
              <p className="mt-2 text-sm text-muted-foreground">Próxima consulta · 12 mai, 14:30</p>
            </div>
            <div className="flex items-center gap-1">
              <Button type="button" variant="ghost" size="icon" aria-label="Editar paciente">
                <IconEdit className="size-4" aria-hidden="true" />
              </Button>
              <Button type="button" variant="ghost" size="icon" aria-label="Compartilhar prontuário">
                <IconShare className="size-4" aria-hidden="true" />
              </Button>
            </div>
          </div>
        </SheetHeader>

        <Tabs defaultValue="summary" className="mt-4">
          <TabsList>
            <TabsTrigger value="summary">Resumo</TabsTrigger>
            <TabsTrigger value="files">Arquivos</TabsTrigger>
          </TabsList>
          <TabsContent value="summary">Resumo executivo do atendimento.</TabsContent>
          <TabsContent value="files">Lista de documentos vinculados ao paciente.</TabsContent>
        </Tabs>
      </SheetContent>
    </Sheet>
  );
}
```

**Vue 3/Nuxt 4 (shadcn-vue)**

```vue
<script setup lang="ts">
import { IconEdit, IconShare } from "@tabler/icons-vue";

import { Button } from "@/components/ui/button";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
</script>

<template>
  <Sheet>
    <SheetTrigger as-child>
      <Button variant="secondary">Prontuário</Button>
    </SheetTrigger>
    <SheetContent side="right" class="sm:max-w-xl">
      <SheetHeader>
        <div class="flex items-start justify-between gap-3">
          <div>
            <SheetTitle>Ana Costa</SheetTitle>
            <SheetDescription>Dados do atendimento atual</SheetDescription>
            <p class="mt-2 text-sm text-muted-foreground">Próxima consulta · 12 mai, 14:30</p>
          </div>
          <div class="flex items-center gap-1">
            <Button type="button" variant="ghost" size="icon" aria-label="Editar paciente">
              <IconEdit class="size-4" aria-hidden="true" />
            </Button>
            <Button type="button" variant="ghost" size="icon" aria-label="Compartilhar prontuário">
              <IconShare class="size-4" aria-hidden="true" />
            </Button>
          </div>
        </div>
      </SheetHeader>

      <Tabs default-value="summary" class="mt-4">
        <TabsList>
          <TabsTrigger value="summary">Resumo</TabsTrigger>
          <TabsTrigger value="files">Arquivos</TabsTrigger>
        </TabsList>
        <TabsContent value="summary">Resumo executivo do atendimento.</TabsContent>
        <TabsContent value="files">Lista de documentos vinculados ao paciente.</TabsContent>
      </Tabs>
    </SheetContent>
  </Sheet>
</template>
```

**HTML (copy-first)**

```html
<button type="button" class="itps-btn itps-btn--secondary" data-sidepanel-open="record-panel">
  Prontuário
</button>

<div id="record-panel" class="itps-side-panel" data-sidepanel aria-hidden="true">
  <div class="itps-side-panel__backdrop" data-sidepanel-backdrop></div>
  <aside role="dialog" aria-modal="true" aria-labelledby="record-panel-title" aria-describedby="record-panel-description" tabindex="-1" class="itps-side-panel__surface" data-sidepanel-content>
    <header class="itps-side-panel__header">
      <div class="itps-side-panel__header-main">
        <div class="itps-side-panel__copy">
          <h2 id="record-panel-title" class="itps-side-panel__title">Ana Costa</h2>
          <p id="record-panel-description" class="itps-side-panel__description">Dados do atendimento atual</p>
          <div class="itps-side-panel__meta">
            <span class="itps-side-panel__meta-item">
              <i class="ti ti-calendar" aria-hidden="true"></i>
              <span>Próxima consulta</span>
            </span>
            <span class="itps-side-panel__meta-separator" aria-hidden="true"></span>
            <span class="itps-side-panel__meta-item">12 mai, 14:30</span>
          </div>
        </div>
        <div class="itps-side-panel__actions">
          <button type="button" class="itps-side-panel__action" aria-label="Editar paciente" title="Editar">
            <i class="ti ti-edit" aria-hidden="true"></i>
          </button>
          <button type="button" class="itps-side-panel__action" aria-label="Compartilhar prontuário" title="Compartilhar">
            <i class="ti ti-share" aria-hidden="true"></i>
          </button>
          <button type="button" class="itps-side-panel__close" aria-label="Fechar painel" data-sidepanel-close>
            <i class="ti ti-x" aria-hidden="true"></i>
          </button>
        </div>
      </div>
      <div class="itps-side-panel__tabs" role="tablist" aria-label="Seções do painel">
        <button type="button" id="record-panel-summary-tab" role="tab" aria-controls="record-panel-summary-panel" aria-selected="true" tabindex="0" class="itps-side-panel__tab" data-sidepanel-tab="summary">
          <i class="ti ti-layout-dashboard" aria-hidden="true"></i>
          <span>Resumo</span>
        </button>
        <button type="button" id="record-panel-files-tab" role="tab" aria-controls="record-panel-files-panel" aria-selected="false" tabindex="-1" class="itps-side-panel__tab" data-sidepanel-tab="files">
          <i class="ti ti-folder" aria-hidden="true"></i>
          <span>Arquivos</span>
        </button>
      </div>
    </header>
    <div class="itps-side-panel__body" data-sidepanel-body>
      <section id="record-panel-summary-panel" role="tabpanel" aria-labelledby="record-panel-summary-tab" tabindex="0">
        <p>Resumo executivo do atendimento.</p>
      </section>
      <section id="record-panel-files-panel" role="tabpanel" aria-labelledby="record-panel-files-tab" tabindex="0" hidden>
        <p>Lista de documentos vinculados ao paciente.</p>
      </section>
    </div>
  </aside>
</div>
```

### Estrutura

| Peça | React/Next e Vue 3/Nuxt 4 | HTML |
|------|--------------------------|------|
| Raiz | `Sheet` | `itps-side-panel` com `data-sidepanel` |
| Gatilho | `SheetTrigger` | botão com `data-sidepanel-open` |
| Superfície | `SheetContent side="right"` | `itps-side-panel__surface` |
| Cabeçalho | `SheetHeader`, `SheetTitle`, `SheetDescription` | `itps-side-panel__header`, `__title`, `__description` |
| Tabs | `Tabs` dentro do conteúdo | `itps-side-panel__tabs` e `itps-side-panel__tab` |

### Comportamento

A aplicação controla `data-state="open"`, alterna `aria-hidden`, trava o scroll da página enquanto aberto e devolve foco ao trigger ao fechar.

## API copy-first

| Classe/atributo | Uso |
|-----------------|-----|
| `itps-side-panel` | Contêiner raiz controlado por `aria-hidden` e `data-state`. |
| `itps-side-panel__backdrop` | Camada que fecha o painel ao clicar. |
| `itps-side-panel__surface` | Superfície lateral com `role="dialog"`. |
| `itps-side-panel__header` | Cabeçalho fixo com título, descrição, metadados e ações. |
| `itps-side-panel__title`, `itps-side-panel__description` | Nome e contexto do painel, referenciados por `aria-labelledby` e `aria-describedby`. |
| `itps-side-panel__meta` | Metadados curtos no cabeçalho. |
| `itps-side-panel__action` | Ação curta de cabeçalho com `aria-label`. |
| `itps-side-panel__close` | Botão de fechar com `aria-label="Fechar painel"`. |
| `itps-side-panel__tabs`, `itps-side-panel__tab` | Lista de tabs do painel. |
| `itps-side-panel__body` | Área rolável do conteúdo. |
| `data-sidepanel*` | Hooks usados pela aplicação para abrir, fechar, alternar tabs e devolver foco. |

## Acessibilidade

- Use `role="dialog"` e `aria-modal="true"` na superfície.
- Use `aria-labelledby` apontando para `itps-side-panel__title`.
- Use `aria-describedby` quando houver descrição.
- O botão de fechar precisa de `aria-label="Fechar painel"`.
- Em tabs, conecte cada tab a um `role="tabpanel"` real com `aria-controls`.
- Feche com Esc, backdrop e botão explícito; devolva foco ao trigger.
