Side Panel
Painel lateral para inspeção, edição contextual e navegação sem abandonar a página atual.
Como usar
Use itps-side-panel quando a pessoa precisa consultar ou editar um contexto sem sair da página atual. Para uma decisão bloqueante, use Modal.
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>
);
} <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> <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> CLI
pnpm dlx github:ITPS/sinapse-ui-cli add side-panel
Este comando individual já é reconhecido pela CLI. Para componentes sem adapter de patch de fonte, o comando confirma o contrato theme-only: use o tema ITpS aplicado pelo init e as abas React/Next, Vue3/Nuxt4 ou HTML desta página.
Com metadados, ações e tabs
Use metadados no header quando a informação ajuda a confirmar contexto. Em tabs, conecte cada itps-side-panel__tab a um role="tabpanel" real por aria-controls.
import { Edit, Share2 } from "lucide-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">
<Edit className="size-4" aria-hidden="true" />
</Button>
<Button type="button" variant="ghost" size="icon" aria-label="Compartilhar prontuário">
<Share2 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>
);
} <script setup lang="ts">
import { Edit, Share2 } from "lucide-vue-next";
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">
<Edit class="size-4" aria-hidden="true" />
</Button>
<Button type="button" variant="ghost" size="icon" aria-label="Compartilhar prontuário">
<Share2 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> <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> Contrato público
| Classe ou atributo | Uso |
|---|---|
.itps-side-panel | Container 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 | Header fixo com título, descrição, metadados e ações. |
.itps-side-panel__body | Área rolável do conteúdo. |
.itps-side-panel__action | Ação curta de header com aria-label. |
.itps-side-panel__tabs | Lista de tabs do painel. |
data-sidepanel* | Hooks usados pela aplicação para abrir, fechar, alternar tabs e devolver foco. |
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.
Acessibilidade
- Use
role="dialog"earia-modal="true"na superfície. - Use
aria-labelledbyapontando paraitps-side-panel__title. - Use
aria-describedbyquando houver descrição. - O botão de fechar precisa de
aria-label="Fechar painel". - Em tabs, conecte cada tab a um
role="tabpanel"real comaria-controls. - Feche por ESC, backdrop e botão explícito; devolva foco ao trigger.