templates/editor: drawer mobile com form + variaveis (tabs)
Mobile (<1024px): so o editor (col 2) fica visivel. Form de
metadados (col 1) e variaveis (col 3) viram tabs dentro de um
drawer fixed que abre pela esquerda.
Padrao espelhado de MelissaBloqueios/MelissaDocumentosTemplates,
com adaptacoes pra ser autocontido (sem dependencia do componente
pai).
Script:
- drawerOpen + drawerTab ('form' | 'vars') + isMobile refs
- _mqMobile matchMedia listener (onMounted setup +
onBeforeUnmount cleanup)
- openDrawer(tab) / fecharDrawer helpers
- insertVariable agora fecha o drawer no mobile apos inserir
Template:
- Drawer wrap no inicio: tabs (Identificacao / Variaveis) +
botao close + 2 panes (#dte-mobile-drawer-form e
#dte-mobile-drawer-vars)
- Backdrop overlay com blur fecha o drawer
- Toolbar do editor ganha 2 botoes mobile-only (Identificacao /
Variaveis) com classe dte-toolbar__mobile-actions
- <Teleport to="#dte-mobile-drawer-form" :disabled="!isMobile">
envolvendo a <aside class="dte-side">
- <Teleport to="#dte-mobile-drawer-vars" :disabled="!isMobile">
envolvendo a <aside class="dte-vars">
CSS:
- .dte-mobile-drawer fixed left, transform translateX, 250ms
- 2 panes scroll interno separado
- @media (max-width:1023px): cols vira 1-col, side/vars inline
somem, botoes mobile aparecem, titulo canonico some
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { ref, watch, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useDocumentTemplates } from '../composables/useDocumentTemplates'
|
||||
import JoditEmailEditor from '@/components/ui/JoditEmailEditor.vue'
|
||||
|
||||
@@ -82,6 +82,9 @@ function insertVariable(varKey) {
|
||||
if (!form.value.variaveis.includes(varKey)) {
|
||||
form.value.variaveis = [...form.value.variaveis, varKey]
|
||||
}
|
||||
|
||||
// No mobile, fecha o drawer pra liberar a tela do editor
|
||||
if (isMobile.value) drawerOpen.value = false;
|
||||
}
|
||||
|
||||
// ── Save ────────────────────────────────────────────────────
|
||||
@@ -89,6 +92,40 @@ function insertVariable(varKey) {
|
||||
function onSave() {
|
||||
emit('save', { ...form.value })
|
||||
}
|
||||
|
||||
// ── Mobile drawer (espelha padrão MelissaBloqueios/Templates) ─
|
||||
// No mobile, form (col 1) + variáveis (col 3) viram tabs dentro
|
||||
// de um drawer único. Só o editor (col 2) fica visível na tela.
|
||||
const drawerOpen = ref(false);
|
||||
const drawerTab = ref('form'); // form | vars
|
||||
const isMobile = ref(false);
|
||||
let _mqMobile = null;
|
||||
function _onMqMobileChange(e) {
|
||||
isMobile.value = e.matches;
|
||||
if (!e.matches) drawerOpen.value = false;
|
||||
}
|
||||
function openDrawer(tab) {
|
||||
drawerTab.value = tab || 'form';
|
||||
drawerOpen.value = true;
|
||||
}
|
||||
function fecharDrawer() { drawerOpen.value = false; }
|
||||
|
||||
onMounted(() => {
|
||||
if (typeof window !== 'undefined' && window.matchMedia) {
|
||||
_mqMobile = window.matchMedia('(max-width: 1023px)');
|
||||
isMobile.value = _mqMobile.matches;
|
||||
try { _mqMobile.addEventListener('change', _onMqMobileChange); }
|
||||
catch { _mqMobile.addListener(_onMqMobileChange); }
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (_mqMobile) {
|
||||
try { _mqMobile.removeEventListener('change', _onMqMobileChange); }
|
||||
catch { _mqMobile.removeListener(_onMqMobileChange); }
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -417,21 +454,148 @@ function onSave() {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* ═══════ Mobile (<1024px): empilha 1 col ═══════ */
|
||||
/* ═══════ Toolbar mobile actions (botões "Identificação" / "Variáveis") ═══════ */
|
||||
.dte-toolbar__mobile-actions {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.dte-mobile-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--surface-border);
|
||||
background: var(--surface-ground);
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
font-family: inherit;
|
||||
transition: background-color 120ms ease;
|
||||
}
|
||||
.dte-mobile-btn:hover { background: color-mix(in srgb, var(--p-primary-color) 8%, transparent); }
|
||||
.dte-mobile-btn > i { color: var(--p-primary-color); font-size: 0.82rem; }
|
||||
|
||||
/* ═══════ Mobile drawer (form + variáveis em tabs) ═══════ */
|
||||
.dte-mobile-drawer {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
width: min(360px, 92vw);
|
||||
z-index: 80;
|
||||
background: var(--surface-card);
|
||||
border-right: 1px solid var(--surface-border);
|
||||
transform: translateX(-100%);
|
||||
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--text-color);
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
.dte-mobile-drawer.is-open { transform: translateX(0); }
|
||||
|
||||
.dte-mobile-drawer__tabs {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
background: var(--surface-ground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dte-drawer-tab {
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 12px 14px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease;
|
||||
}
|
||||
.dte-drawer-tab:hover {
|
||||
color: var(--text-color);
|
||||
background: color-mix(in srgb, var(--p-primary-color) 4%, transparent);
|
||||
}
|
||||
.dte-drawer-tab.is-active {
|
||||
color: var(--p-primary-color);
|
||||
border-bottom-color: var(--p-primary-color);
|
||||
}
|
||||
.dte-drawer-tab > i { font-size: 0.82rem; }
|
||||
|
||||
.dte-drawer-close {
|
||||
width: 44px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-color-secondary);
|
||||
cursor: pointer;
|
||||
border-left: 1px solid var(--surface-border);
|
||||
transition: background-color 120ms ease, color 120ms ease;
|
||||
}
|
||||
.dte-drawer-close:hover {
|
||||
background: var(--surface-ground);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.dte-mobile-drawer__pane {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
.dte-mobile-drawer__pane > .dte-side,
|
||||
.dte-mobile-drawer__pane > .dte-vars {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dte-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;
|
||||
}
|
||||
.dte-drawer-fade-enter-active,
|
||||
.dte-drawer-fade-leave-active { transition: opacity 200ms ease; }
|
||||
.dte-drawer-fade-enter-from,
|
||||
.dte-drawer-fade-leave-to { opacity: 0; }
|
||||
|
||||
/* ═══════ Mobile (<1024px): só o editor visível ═══════ */
|
||||
@media (max-width: 1023px) {
|
||||
/* Editor ocupa tela inteira — col 1 e col 3 viram drawer */
|
||||
.dte-cols {
|
||||
grid-template-columns: 1fr;
|
||||
overflow-y: auto;
|
||||
align-items: start;
|
||||
}
|
||||
.dte-side,
|
||||
.dte-main,
|
||||
.dte-vars {
|
||||
height: auto;
|
||||
}
|
||||
.dte-vars__list {
|
||||
max-height: 320px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dte-cols > .dte-side,
|
||||
.dte-cols > .dte-vars { display: none; }
|
||||
|
||||
/* Mostra os botões "Identificação" / "Variáveis" no header */
|
||||
.dte-toolbar__mobile-actions { display: inline-flex; }
|
||||
|
||||
/* Esconde o título canônico no mobile (espaço pros botões) */
|
||||
.dte-toolbar__title > span { display: none; }
|
||||
.dte-toolbar__title > i { display: none; }
|
||||
|
||||
.dte-preview {
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -442,9 +606,77 @@ function onSave() {
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<!-- ══ Mobile drawer (form + variáveis em tabs) ════════════ -->
|
||||
<Transition name="dte-drawer-fade">
|
||||
<div
|
||||
v-show="isMobile && drawerOpen"
|
||||
class="dte-mobile-drawer"
|
||||
:class="{ 'is-open': drawerOpen }"
|
||||
>
|
||||
<div class="dte-mobile-drawer__tabs">
|
||||
<button
|
||||
type="button"
|
||||
class="dte-drawer-tab"
|
||||
:class="{ 'is-active': drawerTab === 'form' }"
|
||||
@click="drawerTab = 'form'"
|
||||
>
|
||||
<i class="pi pi-tag" />
|
||||
<span>Identificação</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="dte-drawer-tab"
|
||||
:class="{ 'is-active': drawerTab === 'vars' }"
|
||||
@click="drawerTab = 'vars'"
|
||||
>
|
||||
<i class="pi pi-code" />
|
||||
<span>Variáveis</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="dte-drawer-close"
|
||||
v-tooltip.bottom="'Fechar'"
|
||||
@click="fecharDrawer"
|
||||
>
|
||||
<i class="pi pi-times" />
|
||||
</button>
|
||||
</div>
|
||||
<div id="dte-mobile-drawer-form" v-show="drawerTab === 'form'" class="dte-mobile-drawer__pane" />
|
||||
<div id="dte-mobile-drawer-vars" v-show="drawerTab === 'vars'" class="dte-mobile-drawer__pane" />
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition name="dte-drawer-fade">
|
||||
<div
|
||||
v-show="isMobile && drawerOpen"
|
||||
class="dte-mobile-drawer__backdrop"
|
||||
@click="fecharDrawer"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<div class="dte-page">
|
||||
<!-- ══ Toggle Editor / Preview no topo ══════════════════ -->
|
||||
<div class="dte-toolbar">
|
||||
<!-- Botões "Identificação" e "Variáveis" — mobile-only -->
|
||||
<div class="dte-toolbar__mobile-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="dte-mobile-btn"
|
||||
v-tooltip.bottom="'Identificação do template'"
|
||||
@click="openDrawer('form')"
|
||||
>
|
||||
<i class="pi pi-tag" />
|
||||
<span>Identificação</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="dte-mobile-btn"
|
||||
v-tooltip.bottom="'Inserir variáveis'"
|
||||
@click="openDrawer('vars')"
|
||||
>
|
||||
<i class="pi pi-code" />
|
||||
<span>Variáveis</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dte-toolbar__title">
|
||||
<i class="pi pi-file-edit" />
|
||||
<span>Conteúdo do documento</span>
|
||||
@@ -473,7 +705,8 @@ function onSave() {
|
||||
|
||||
<!-- ══ EDITOR — 3 colunas (form / editor / variáveis) ══ -->
|
||||
<div v-show="activeTab === 'editor'" class="dte-cols">
|
||||
<!-- ─── COL 1 (esquerda): Form de metadados ─── -->
|
||||
<!-- ─── COL 1 (esquerda): Form de metadados — teleporta pro drawer no mobile ─── -->
|
||||
<Teleport to="#dte-mobile-drawer-form" :disabled="!isMobile">
|
||||
<aside class="dte-side">
|
||||
<div class="dte-side__head">
|
||||
<i class="pi pi-tag" />
|
||||
@@ -498,6 +731,7 @@ function onSave() {
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</Teleport>
|
||||
|
||||
<!-- ─── COL 2 (centro): Tabs Cabeçalho/Corpo/Rodapé + editor ─── -->
|
||||
<main class="dte-main">
|
||||
@@ -544,7 +778,8 @@ function onSave() {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ─── COL 3 (direita): Variáveis disponíveis ─── -->
|
||||
<!-- ─── COL 3 (direita): Variáveis disponíveis — teleporta pro drawer no mobile ─── -->
|
||||
<Teleport to="#dte-mobile-drawer-vars" :disabled="!isMobile">
|
||||
<aside class="dte-vars">
|
||||
<div class="dte-vars__head">
|
||||
<i class="pi pi-code" />
|
||||
@@ -573,6 +808,7 @@ function onSave() {
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</Teleport>
|
||||
</div>
|
||||
|
||||
<!-- ══ PREVIEW — full width ════════════════════════════ -->
|
||||
|
||||
Reference in New Issue
Block a user