# Blueprint — Melissa Page Padrão de página fullscreen dentro do MelissaLayout (Direção B do redesign). Use isto como molde pra cada nova página: Financeiro, WhatsApp, Prontuários etc. Validado em `MelissaAgenda.vue` (referência canônica) e `MelissaPacientes.vue`. --- ## 1. Princípio Cada Melissa Page é um componente fullscreen que ocupa o viewport inteiro (menos 6px de respiro + faixa do dock 76px no bottom), montado via `v-if="layoutReady && secaoAberta === ''"` no `MelissaLayout.vue`. A página tem **uma área central de conteúdo principal** (a coluna que importa) e **0–N colunas auxiliares** (asides). No desktop convivem lado a lado; no mobile (`. --- ## 2. Estrutura macro do template ``` ``` > Substitua `xx-` pelo prefixo da página (`ma-` agenda, `mp-` pacientes, > `mf-` financeiro, etc.). --- ## 3. Breakpoints ``` ≥1280px (xl) → todas as colunas + filtros inline na toolbar 1024–1279 (lg→xl) → todas as colunas + filtros migram pro botão "Ações" ≤1023px (` ```js import { ref, onMounted, onBeforeUnmount } from 'vue'; const drawerOpen = ref(false); const isMobile = ref(false); const isCompact = ref(false); let _mqMobile = null; let _mqCompact = null; function _onMqMobileChange(e) { isMobile.value = e.matches; if (!e.matches) drawerOpen.value = false; // saiu do mobile, fecha drawer } function _onMqCompactChange(e) { isCompact.value = e.matches; } onMounted(() => { if (typeof window !== 'undefined' && window.matchMedia) { _mqMobile = window.matchMedia('(max-width: 1023px)'); isMobile.value = _mqMobile.matches; _mqMobile.addEventListener('change', _onMqMobileChange); _mqCompact = window.matchMedia('(max-width: 1279px)'); isCompact.value = _mqCompact.matches; _mqCompact.addEventListener('change', _onMqCompactChange); } }); onBeforeUnmount(() => { if (_mqMobile) _mqMobile.removeEventListener('change', _onMqMobileChange); if (_mqCompact) _mqCompact.removeEventListener('change', _onMqCompactChange); }); function toggleDrawer() { drawerOpen.value = !drawerOpen.value; } function fecharDrawer() { drawerOpen.value = false; } ``` --- ## 6. CSS base (copy-paste, troque `xx-` pelo prefixo) ```css /* Container glass — convenção das Melissa Pages */ .xx-page { position: absolute; inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px; z-index: 40; display: flex; flex-direction: column; background: var(--m-bg-medium); backdrop-filter: blur(32px) saturate(160%); -webkit-backdrop-filter: blur(32px) saturate(160%); border: 1px solid var(--m-border); border-radius: 18px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4); overflow: hidden; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; color: var(--m-text); animation: xx-page-enter 240ms cubic-bezier(0.2, 0.7, 0.3, 1); } @keyframes xx-page-enter { from { opacity: 0; transform: scale(0.985); } to { opacity: 1; transform: scale(1); } } /* Header da página */ .xx-page__head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--m-border); flex-shrink: 0; gap: 10px; } .xx-page__title { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; font-size: 1rem; font-weight: 500; } .xx-page__title > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .xx-page__actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; } /* Body — flex row em desktop, column em mobile */ .xx-body { flex: 1; display: flex; min-height: 0; position: relative; } /* Botão "Menu" (mobile only) — primary filled, abre o drawer */ .xx-menu-btn { display: none; /* show via @media abaixo */ } .xx-menu-btn { height: 32px; align-items: center; gap: 6px; flex-shrink: 0; background: var(--m-accent); border: 1px solid var(--m-accent); color: white; padding: 0 11px; border-radius: 9px; cursor: pointer; font-family: inherit; font-size: 0.78rem; font-weight: 600; transition: background-color 140ms ease, transform 140ms ease; } .xx-menu-btn:hover { background: color-mix(in srgb, var(--m-accent) 88%, white); transform: translateY(-1px); } .xx-menu-btn:active { transform: translateY(0); } /* Drawer mobile — fora do .xx-page, fullheight */ .xx-mobile-drawer { position: fixed; top: 0; left: 0; height: 100vh; height: 100dvh; /* iOS toolbar dynamic */ width: min(360px, 88vw); z-index: 80; /* acima do ψ (70) */ background: var(--m-bg-medium); backdrop-filter: blur(28px) saturate(160%); -webkit-backdrop-filter: blur(28px) saturate(160%); border-right: 1px solid var(--m-border); transform: translateX(-100%); transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1); color: var(--m-text); } .xx-mobile-drawer.is-open { transform: translateX(0); } .xx-mobile-drawer__scroll { height: 100%; overflow-y: auto; overflow-x: hidden; padding: 12px 12px 24px; display: flex; flex-direction: column; gap: 12px; scrollbar-width: thin; scrollbar-color: var(--m-border-strong) transparent; } .xx-mobile-drawer__scroll::-webkit-scrollbar { width: 5px; } .xx-mobile-drawer__scroll::-webkit-scrollbar-thumb { background: var(--m-border-strong); border-radius: 3px; } /* Asides perdem padding/scroll/borda próprios quando teleportados pro drawer */ .xx-mobile-drawer__scroll .xx-side, .xx-mobile-drawer__scroll .xx-widgets { width: 100%; flex-shrink: 0; height: auto; overflow: visible; border-right: none; border-left: none; background: transparent; padding: 0; } /* Backdrop */ .xx-mobile-drawer__backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); z-index: 79; } .xx-drawer-fade-enter-active, .xx-drawer-fade-leave-active { transition: opacity 200ms ease; } .xx-drawer-fade-enter-from, .xx-drawer-fade-leave-to { opacity: 0; } /* Mobile (` no `MelissaLayout` ```vue ``` ### ❌ NÃO importar `Menu` do PrimeVue manualmente PrimeVueResolver auto-importa. Import duplo cria instâncias fantasmas e quebra o reconciler com `emitsOptions: null` em `shouldUpdateComponent`. ```js // ❌ NÃO faça import Menu from 'primevue/menu'; ``` ### ❌ NÃO usar `` Quando múltiplos Teleports compartilham target (ex: `.melissa-dock`): ```vue ``` ### ❌ NÃO escopar CSS de Teleport target Targets globais (`.melissa-dock`, `#xx-mobile-drawer-target`) precisam de CSS no `