Componentes
Side Panel
Drawer 628px slide-in da direita. Header tinted (title + extraInfo + actions + close), tabs row opcional, body scrollável.
Default
Trigger via data-sidepanel-open="<id>". Backdrop semi-transparente + blur.
<Button data-sidepanel-open="patient-1">Ver paciente</Button>
<SidePanel id="patient-1" title="Maria Silva" description="Atendimento iniciado há 12 minutos">
<p>Conteúdo livre do painel.</p>
</SidePanel>
<Button data-sidepanel-open="patient-1">Ver paciente</Button>
<SidePanel id="patient-1" title="Maria Silva" description="Atendimento iniciado há 12 minutos">
<p>Conteúdo livre do painel.</p>
</SidePanel> <template>
<button @click="open = true" class="itps-btn itps-btn--primary">Ver paciente</button>
<Transition>
<div v-if="open" class="fixed inset-0 z-50">
<div @click="open = false" class="absolute inset-0 bg-black/40 backdrop-blur-sm"></div>
<aside class="absolute right-0 top-0 h-full w-[628px] max-w-full bg-card border-l border-border shadow-extra-large flex flex-col">
<header class="flex items-start gap-3 p-6 border-b border-border bg-secondary-50">
<div class="flex-1">
<h2 class="text-lg font-semibold">Maria Silva</h2>
<p class="text-sm text-muted-foreground">Atendimento iniciado há 12 minutos</p>
</div>
<button @click="open = false" class="itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<div class="flex-1 overflow-y-auto p-6">
<p>Conteúdo livre do painel.</p>
</div>
</aside>
</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<button @click="open = true" class="itps-btn itps-btn--primary">Ver paciente</button>
<Transition>
<div v-if="open" class="fixed inset-0 z-50">
<div @click="open = false" class="absolute inset-0 bg-black/40 backdrop-blur-sm"></div>
<aside class="absolute right-0 top-0 h-full w-[628px] max-w-full bg-card border-l border-border shadow-extra-large flex flex-col">
<header class="flex items-start gap-3 p-6 border-b border-border bg-secondary-50">
<div class="flex-1">
<h2 class="text-lg font-semibold">Maria Silva</h2>
<p class="text-sm text-muted-foreground">Atendimento iniciado há 12 minutos</p>
</div>
<button @click="open = false" class="itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<div class="flex-1 overflow-y-auto p-6">
<p>Conteúdo livre do painel.</p>
</div>
</aside>
</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const open = ref(false)
</script> import { useState } from 'react';
export function PatientPanel() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)} className="itps-btn itps-btn--primary">Ver paciente</button>
{open && (
<div className="fixed inset-0 z-50">
<div onClick={() => setOpen(false)} className="absolute inset-0 bg-black/40 backdrop-blur-sm" />
<aside className="absolute right-0 top-0 h-full w-[628px] max-w-full bg-card border-l border-border shadow-extra-large flex flex-col">
<header className="flex items-start gap-3 p-6 border-b border-border bg-secondary-50">
<div className="flex-1">
<h2 className="text-lg font-semibold">Maria Silva</h2>
<p className="text-sm text-muted-foreground">Atendimento iniciado há 12 minutos</p>
</div>
<button onClick={() => setOpen(false)} className="itps-btn itps-btn--ghost itps-btn--icon">
<span className="material-symbols-outlined">close</span>
</button>
</header>
<div className="flex-1 overflow-y-auto p-6">
<p>Conteúdo livre do painel.</p>
</div>
</aside>
</div>
)}
</>
);
}
import { useState } from 'react';
export function PatientPanel() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)} className="itps-btn itps-btn--primary">Ver paciente</button>
{open && (
<div className="fixed inset-0 z-50">
<div onClick={() => setOpen(false)} className="absolute inset-0 bg-black/40 backdrop-blur-sm" />
<aside className="absolute right-0 top-0 h-full w-[628px] max-w-full bg-card border-l border-border shadow-extra-large flex flex-col">
<header className="flex items-start gap-3 p-6 border-b border-border bg-secondary-50">
<div className="flex-1">
<h2 className="text-lg font-semibold">Maria Silva</h2>
<p className="text-sm text-muted-foreground">Atendimento iniciado há 12 minutos</p>
</div>
<button onClick={() => setOpen(false)} className="itps-btn itps-btn--ghost itps-btn--icon">
<span className="material-symbols-outlined">close</span>
</button>
</header>
<div className="flex-1 overflow-y-auto p-6">
<p>Conteúdo livre do painel.</p>
</div>
</aside>
</div>
)}
</>
);
} <!-- Preline offcanvas -->
<button type="button" data-hs-overlay="#hs-panel" class="itps-btn itps-btn--primary">Ver paciente</button>
<div id="hs-panel" class="hs-overlay hidden fixed inset-0 z-50">
<aside class="absolute right-0 top-0 h-full w-[628px] max-w-full bg-card border-l border-border shadow-extra-large flex flex-col">
<header class="flex items-start gap-3 p-6 border-b border-border bg-secondary-50">
<div class="flex-1">
<h2 class="text-lg font-semibold">Maria Silva</h2>
<p class="text-sm text-muted-foreground">Atendimento iniciado há 12 minutos</p>
</div>
<button type="button" data-hs-overlay="#hs-panel" class="itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<div class="flex-1 overflow-y-auto p-6">
<p>Conteúdo livre do painel.</p>
</div>
</aside>
</div>
<!-- Preline offcanvas -->
<button type="button" data-hs-overlay="#hs-panel" class="itps-btn itps-btn--primary">Ver paciente</button>
<div id="hs-panel" class="hs-overlay hidden fixed inset-0 z-50">
<aside class="absolute right-0 top-0 h-full w-[628px] max-w-full bg-card border-l border-border shadow-extra-large flex flex-col">
<header class="flex items-start gap-3 p-6 border-b border-border bg-secondary-50">
<div class="flex-1">
<h2 class="text-lg font-semibold">Maria Silva</h2>
<p class="text-sm text-muted-foreground">Atendimento iniciado há 12 minutos</p>
</div>
<button type="button" data-hs-overlay="#hs-panel" class="itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<div class="flex-1 overflow-y-auto p-6">
<p>Conteúdo livre do painel.</p>
</div>
</aside>
</div> Com extraInfo + actions
extraInfo mostra metadado contextual no header. actions adiciona botões de ícone na barra superior.
<Button data-sidepanel-open="patient-2">Detalhes completos</Button>
<SidePanel
id="patient-2"
title="João Santos"
description="CPF 123.456.789-00"
extraInfo={{ icon: "calendar_today", first: "Próxima consulta", second: "12 mai · 14:30" }}
actions={[
{ icon: "edit", label: "Editar", ariaLabel: "Editar paciente" },
{ icon: "share", label: "Compartilhar", ariaLabel: "Compartilhar prontuário" },
]}
>
<p>Histórico clínico completo.</p>
</SidePanel>
<Button data-sidepanel-open="patient-2">Detalhes completos</Button>
<SidePanel
id="patient-2"
title="João Santos"
description="CPF 123.456.789-00"
extraInfo={{ icon: "calendar_today", first: "Próxima consulta", second: "12 mai · 14:30" }}
actions={[
{ icon: "edit", label: "Editar", ariaLabel: "Editar paciente" },
{ icon: "share", label: "Compartilhar", ariaLabel: "Compartilhar prontuário" },
]}
>
<p>Histórico clínico completo.</p>
</SidePanel> Com tabs
Array tabs adiciona row navegável abaixo do header. Componente dispara sidepanel:tab-change; consumer faz swap do conteúdo.
<Button data-sidepanel-open="record-1">Prontuário</Button>
<SidePanel
id="record-1"
title="Ana Costa"
tabs={[
{ id: "summary", label: "Resumo", icon: "description" },
{ id: "history", label: "Histórico", icon: "history" },
{ id: "files", label: "Arquivos", icon: "folder" },
]}
>
<p>Conteúdo da tab ativa.</p>
</SidePanel>
<Button data-sidepanel-open="record-1">Prontuário</Button>
<SidePanel
id="record-1"
title="Ana Costa"
tabs={[
{ id: "summary", label: "Resumo", icon: "description" },
{ id: "history", label: "Histórico", icon: "history" },
{ id: "files", label: "Arquivos", icon: "folder" },
]}
>
<p>Conteúdo da tab ativa.</p>
</SidePanel> Como fechar
- Botão X no header
- Tecla ESC
- Click no backdrop
Modal vs Side Panel
| Modal | Side Panel |
|---|---|
| Centralizado, focused, bloqueia fluxo | Lateral, inspeciona sem perder contexto |
| Decisão / confirmação | Detalhes / edição contextual |
| 800px width, max altura limitada | 628px width, full height |
Props
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
id | string | obrigatório | ID único usado pelo trigger. |
title | string | obrigatório | Título do header. |
description | string | — | Subtítulo. |
extraInfo | ExtraInfo | — | Metadado contextual (data, status, etc). |
actions | ActionButton[] | [] | Botões de ícone na barra. |
tabs | Tab[] | [] | Tabs de navegação interna. |
activeTab | string | primeira | Tab inicial. |
width | number | 628 | Largura em px. |
class | string | — | Classes adicionais. |