Melissa: hub Configuracoes + Embed + 9 Pages novas + dialog blueprint dark
Sprints 04-29 + 04-30 acumuladas. - MelissaConfiguracoes: hub 2-col com 6 grupos (Layout/Conta/Agenda/ Financeiro/WhatsApp/Sistema), tudo embedado via MelissaEmbed. - MelissaEmbed: wrapper generico que injeta layout-variant=melissa e remove cromos pra reaproveitar Pages tradicionais. - 9 Melissa Pages novas: CadastrosRecebidos, Compromissos, Configuracoes, Conversas, Embed, Grupos, Medicos, Recorrencias, Tags. - Dialog blueprint atualizado: bg-gray-100 (hardcoded light) -> bg-[var(--surface-ground)] (tema-aware). 22 dialogs migrados em 9 arquivos. Anti-pattern documentado. - PatientsCadastroPage: bug fix dropdown Grupo (optionLabel nome->name), toggle vertical/abas com persist localStorage, sticky margin-top. - Surface picker no popover do MelissaLayout (8 swatches). - useTopbarPlanMenu, useMelissaWhatsapp, useMelissaPacientesAside novos. - Migration: status agenda remarcado/confirmado. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Dialog — Padrão de Componente
|
||||
|
||||
> **Stack**: Vue 3 + PrimeVue 4 + Tailwind CSS
|
||||
> **Tema-aware**: header e footer respeitam dark/light automaticamente via CSS vars
|
||||
|
||||
---
|
||||
|
||||
@@ -19,6 +20,23 @@
|
||||
|
||||
---
|
||||
|
||||
## Sistema de cores (tema-aware)
|
||||
|
||||
O dialog **nunca** deve usar `bg-gray-100` ou cores hardcoded — isso quebra no dark mode.
|
||||
Usar sempre as CSS vars do projeto:
|
||||
|
||||
| Var | Light | Dark | Uso |
|
||||
|---|---|---|---|
|
||||
| `--surface-card` | `--p-surface-0` (branco) | `--p-surface-900` (quase preto) | Fundo do **corpo** do dialog (default) |
|
||||
| `--surface-ground` | `--p-surface-100` (cinza claro) | `--p-surface-950` (preto) | Fundo do **header** e **footer** — um shade mais escuro que o card |
|
||||
| `--surface-border` | `--p-content-border-color` | idem | Borda separadora entre header/content/footer |
|
||||
| `--text-color` | preto | branco | Título principal |
|
||||
| `--text-color-secondary` | cinza médio | cinza claro | Subtítulo, hints |
|
||||
|
||||
> Resumo: `bg-[var(--surface-ground)]` no header/footer fica **sempre um pouco mais escuro que o corpo**, em ambos os temas. Definido em `_light.scss:19` e `_dark.scss:19`.
|
||||
|
||||
---
|
||||
|
||||
## Estrutura obrigatória
|
||||
|
||||
```
|
||||
@@ -44,9 +62,9 @@
|
||||
class="dc-dialog w-[50rem]"
|
||||
:breakpoints="{ '1199px': '90vw', '768px': '94vw' }"
|
||||
:pt="{
|
||||
header: { class: '!p-3 !rounded-t-[12px] border-b border-[var(--surface-border)] shadow-[0_1px_0_0_rgba(255,255,255,0.06)] bg-gray-100' },
|
||||
header: { class: '!p-3 !rounded-t-[12px] border-b border-[var(--surface-border)] bg-[var(--surface-ground)]' },
|
||||
content: { class: '!p-3' },
|
||||
footer: { class: '!p-0 !rounded-b-[12px] border-t border-[var(--surface-border)] shadow-[0_1px_0_0_rgba(255,255,255,0.06)] bg-gray-100' },
|
||||
footer: { class: '!p-0 !rounded-b-[12px] border-t border-[var(--surface-border)] bg-[var(--surface-ground)]' },
|
||||
pcCloseButton: { root: { class: '!rounded-md hover:!text-red-500' } },
|
||||
pcMaximizeButton: { root: { class: '!rounded-md hover:!text-primary' } },
|
||||
}"
|
||||
@@ -58,14 +76,16 @@
|
||||
|
||||
| Chave | O que faz |
|
||||
|---|---|
|
||||
| `header` | `!p-3` padding uniforme; `!rounded-t-[12px]` borda top arredondada; `border-b` + `shadow` separador com profundidade; `bg-gray-100` fundo levemente cinza |
|
||||
| `content` | `!p-3` padding interno do corpo |
|
||||
| `footer` | `!p-0` remove padding nativo (controlado pelo wrapper interno); `!rounded-b-[12px]` borda bottom arredondada; `border-t` + `shadow` separador; `bg-gray-100` fundo levemente cinza |
|
||||
| `header` | `!p-3` padding uniforme; `!rounded-t-[12px]` borda top arredondada; `border-b` separador; `bg-[var(--surface-ground)]` fundo um shade mais escuro que o card (tema-aware) |
|
||||
| `content` | `!p-3` padding interno do corpo (herda `bg-[var(--surface-card)]` do PrimeVue) |
|
||||
| `footer` | `!p-0` remove padding nativo (controlado pelo wrapper interno); `!rounded-b-[12px]` borda bottom arredondada; `border-t` separador; `bg-[var(--surface-ground)]` mesmo fundo do header |
|
||||
| `pcCloseButton` | `!rounded-md` remove o círculo nativo; `hover:!text-red-500` feedback de danger no hover |
|
||||
| `pcMaximizeButton` | `!rounded-md` remove o círculo nativo; `hover:!text-primary` feedback de cor primária no hover |
|
||||
|
||||
> O `!` (important) é necessário porque o PrimeVue injeta estilos inline nos botões e no root do Dialog — sem ele o Tailwind perde a disputa de especificidade.
|
||||
|
||||
> **Migração de dialogs antigos**: trocar `bg-gray-100` por `bg-[var(--surface-ground)]`. O `shadow-[0_1px_0_0_rgba(255,255,255,0.06)]` antigo era um hack pro dark mode; pode ser removido (a borda já dá a separação).
|
||||
|
||||
---
|
||||
|
||||
## Header — slot `#header`
|
||||
@@ -89,10 +109,10 @@
|
||||
:style="{ backgroundColor: previewBgColor }"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<div class="text-base font-semibold truncate">
|
||||
<div class="text-base font-semibold truncate text-[var(--text-color)]">
|
||||
{{ form.name || (mode === 'create' ? 'Novo item' : 'Editar item') }}
|
||||
</div>
|
||||
<div class="text-xs opacity-50">
|
||||
<div class="text-xs text-[var(--text-color-secondary)]">
|
||||
{{ mode === 'create' ? 'Criando novo registro' : 'Editando registro' }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,6 +136,8 @@
|
||||
</template>
|
||||
```
|
||||
|
||||
> **Cores**: usar `text-[var(--text-color)]` no título e `text-[var(--text-color-secondary)]` no subtítulo. Não usar `opacity-50` — a cor secondary já tem contraste calibrado por tema.
|
||||
|
||||
---
|
||||
|
||||
## Footer — slot `#footer`
|
||||
@@ -157,6 +179,20 @@ Use a prop nativa `maximizable`. O PrimeVue injeta e gerencia o botão automatic
|
||||
<Dialog maximizable ...>
|
||||
```
|
||||
|
||||
Se você precisar customizar a largura/altura quando maximizado (ex: `100vw`), use `:style` reativo a um ref `maximized` E passe `:maximizable="false"` + um botão manual no `#header`. Padrão preferido: deixar o PrimeVue gerenciar.
|
||||
|
||||
---
|
||||
|
||||
## Dialogs aninhados (Dialog dentro de Dialog)
|
||||
|
||||
Quando um Dialog secundário (criar tag, criar grupo, criar convênio) é aberto a partir do form de um Dialog principal:
|
||||
|
||||
- Cada Dialog é independente — `v-model:visible` próprio
|
||||
- O Dialog secundário usa o **mesmo blueprint** (mesmo `pt`, mesmas cores)
|
||||
- Pode ser menor: `w-[36rem]` é o tamanho típico de "cadastro rápido"
|
||||
- Z-index: PrimeVue gerencia automaticamente (último aberto fica em cima)
|
||||
- Ao salvar no Dialog secundário, o item criado pode ser auto-selecionado no Dialog principal (UX comum em formulários grandes)
|
||||
|
||||
---
|
||||
|
||||
## Checklist antes de publicar um Dialog
|
||||
@@ -165,11 +201,13 @@ Use a prop nativa `maximizable`. O PrimeVue injeta e gerencia o botão automatic
|
||||
- [ ] `maximizable` na prop (botão nativo, sem estado manual)
|
||||
- [ ] `class="dc-dialog w-[50rem]"` + `:breakpoints="{ '1199px': '90vw', '768px': '94vw' }"`
|
||||
- [ ] `pt` completo: header, content, footer, pcCloseButton, pcMaximizeButton
|
||||
- [ ] Header com `bg-gray-100`, `border-b`, shadow e `!rounded-t-[12px]`
|
||||
- [ ] Footer com `bg-gray-100`, `border-t`, shadow e `!rounded-b-[12px]`
|
||||
- [ ] Header com `bg-[var(--surface-ground)]`, `border-b`, e `!rounded-t-[12px]`
|
||||
- [ ] Footer com `bg-[var(--surface-ground)]`, `border-t`, e `!rounded-b-[12px]`
|
||||
- [ ] **Nenhum `bg-gray-100` ou cor hardcoded** — só CSS vars tema-aware
|
||||
- [ ] Botão **Excluir** no header (nunca no footer), desabilitado se nativo
|
||||
- [ ] Cancelar = `text` + `hover:!text-red-500` | Salvar = primary
|
||||
- [ ] Padding do footer via `px-3 py-3` no `div` interno
|
||||
- [ ] Texto usa `text-[var(--text-color)]` e `text-[var(--text-color-secondary)]`
|
||||
|
||||
---
|
||||
|
||||
@@ -177,7 +215,33 @@ Use a prop nativa `maximizable`. O PrimeVue injeta e gerencia o botão automatic
|
||||
|
||||
| Uso | Classe |
|
||||
|---|---|
|
||||
| Formulário simples | `w-[36rem]` |
|
||||
| Cadastro rápido / formulário simples | `w-[36rem]` |
|
||||
| Formulário padrão | `w-[50rem]` ← **padrão** |
|
||||
| Formulário complexo | `w-[70rem]` |
|
||||
| Formulário complexo (multi-coluna) | `w-[70rem]` |
|
||||
| Cadastro completo (paciente, agenda) | `w-[1100px]` |
|
||||
| Tela cheia | `maximizable` — usuário controla |
|
||||
|
||||
---
|
||||
|
||||
## Anti-pattern
|
||||
|
||||
```vue
|
||||
<!-- ❌ NÃO fazer: -->
|
||||
<Dialog :pt="{
|
||||
header: { class: 'bg-gray-100' }, // quebra no dark
|
||||
footer: { class: 'bg-gray-100' }, // quebra no dark
|
||||
}" />
|
||||
|
||||
<!-- ❌ NÃO fazer: -->
|
||||
<div class="text-base opacity-50">subtítulo</div> <!-- usar text-color-secondary -->
|
||||
```
|
||||
|
||||
```vue
|
||||
<!-- ✅ Pattern correto: -->
|
||||
<Dialog :pt="{
|
||||
header: { class: 'bg-[var(--surface-ground)] border-b border-[var(--surface-border)]' },
|
||||
footer: { class: 'bg-[var(--surface-ground)] border-t border-[var(--surface-border)]' },
|
||||
}" />
|
||||
|
||||
<div class="text-xs text-[var(--text-color-secondary)]">subtítulo</div>
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user