MelissaPerfil mobile: menu de configs inline no topo (sem drawer)
Refator do menu mobile so no Perfil (validacao do padrao): - Remove o drawer slide-in da esquerda + backdrop + botao "Menu"/ "Configuracoes" no header - Renderiza <MelissaConfigSidebar> INLINE no topo do .mpr-body em mobile (.mpr-mobile-config-menu) - A sidebar contextual (Sua evolucao + Avatar) tambem renderiza inline em sequencia, abaixo do menu global - O main com o form fica abaixo de tudo - Body em mobile vira flex column + overflow-y: auto (scroll externo unico pra toda a pagina) Drawer state (drawerOpen/toggle/fechar) e Teleport removidos do JS+template. Em desktop nada muda — MelissaLayout segue renderizando a sidebar global fixa na esquerda. Pendente: aplicar o mesmo pattern nas outras 8 paginas de config se o usuario validar este formato. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import { useConfirm } from 'primevue/useconfirm';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { supabase } from '@/lib/supabase/client';
|
import { supabase } from '@/lib/supabase/client';
|
||||||
import { useTenantStore } from '@/stores/tenantStore';
|
import { useTenantStore } from '@/stores/tenantStore';
|
||||||
|
import MelissaConfigSidebar from './MelissaConfigSidebar.vue';
|
||||||
// InputText/Select/Textarea/InputMask/Skeleton/Tag/Button: auto via PrimeVueResolver
|
// InputText/Select/Textarea/InputMask/Skeleton/Tag/Button: auto via PrimeVueResolver
|
||||||
|
|
||||||
const emit = defineEmits(['close']);
|
const emit = defineEmits(['close']);
|
||||||
@@ -32,16 +33,11 @@ const tenantStore = useTenantStore();
|
|||||||
|
|
||||||
const AVATAR_BUCKET = 'avatars';
|
const AVATAR_BUCKET = 'avatars';
|
||||||
|
|
||||||
// ── Breakpoints + drawer ───────────────────────────────────
|
// ── Breakpoints (sem drawer — em mobile o menu de configs renderiza
|
||||||
const drawerOpen = ref(false);
|
// inline no topo via MelissaConfigSidebar). ─────────────────
|
||||||
const isMobile = ref(false);
|
const isMobile = ref(false);
|
||||||
let _mqMobile = null;
|
let _mqMobile = null;
|
||||||
function _onMqMobileChange(e) {
|
function _onMqMobileChange(e) { isMobile.value = e.matches; }
|
||||||
isMobile.value = e.matches;
|
|
||||||
if (!e.matches) drawerOpen.value = false;
|
|
||||||
}
|
|
||||||
function toggleDrawer() { drawerOpen.value = !drawerOpen.value; }
|
|
||||||
function fecharDrawer() { drawerOpen.value = false; }
|
|
||||||
|
|
||||||
// ── Estado ─────────────────────────────────────────────────
|
// ── Estado ─────────────────────────────────────────────────
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
@@ -420,10 +416,6 @@ const SECTION_BY_FIELD = {
|
|||||||
function scrollToSection(field) {
|
function scrollToSection(field) {
|
||||||
const sec = SECTION_BY_FIELD[field];
|
const sec = SECTION_BY_FIELD[field];
|
||||||
if (!sec) return;
|
if (!sec) return;
|
||||||
// Avatar fica na sidebar; em mobile mantem o drawer aberto (e nao scrolla
|
|
||||||
// o main, pq a sessao nao existe no main).
|
|
||||||
const isAvatar = sec === 'avatar';
|
|
||||||
if (isMobile.value && !isAvatar) drawerOpen.value = false;
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const target = document.getElementById('mpr-sec-' + sec);
|
const target = document.getElementById('mpr-sec-' + sec);
|
||||||
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
@@ -468,34 +460,8 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- Backdrop + drawer mobile pra sidebar (Teleport target) -->
|
|
||||||
<Transition name="mpr-drawer-fade">
|
|
||||||
<div
|
|
||||||
v-show="isMobile && drawerOpen"
|
|
||||||
class="mpr-mobile-drawer"
|
|
||||||
:class="{ 'is-open': drawerOpen }"
|
|
||||||
>
|
|
||||||
<div id="mpr-mobile-drawer-target" class="mpr-mobile-drawer__scroll" />
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
<Transition name="mpr-drawer-fade">
|
|
||||||
<div
|
|
||||||
v-show="isMobile && drawerOpen"
|
|
||||||
class="mpr-mobile-drawer__backdrop"
|
|
||||||
@click="fecharDrawer"
|
|
||||||
/>
|
|
||||||
</Transition>
|
|
||||||
|
|
||||||
<section class="mpr-page">
|
<section class="mpr-page">
|
||||||
<header class="mpr-page__head">
|
<header class="mpr-page__head">
|
||||||
<button
|
|
||||||
class="mpr-menu-btn mpr-menu-btn--mobile-only"
|
|
||||||
v-tooltip.bottom="'Evolução & Avatar'"
|
|
||||||
@click="toggleDrawer"
|
|
||||||
>
|
|
||||||
<i class="pi pi-bars" />
|
|
||||||
<span>Menu Perfil</span>
|
|
||||||
</button>
|
|
||||||
<div class="mpr-page__title">
|
<div class="mpr-page__title">
|
||||||
<i class="pi pi-user mpr-page__title-icon" />
|
<i class="pi pi-user mpr-page__title-icon" />
|
||||||
<span>Meu Perfil</span>
|
<span>Meu Perfil</span>
|
||||||
@@ -526,8 +492,16 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mpr-body">
|
<div class="mpr-body">
|
||||||
<!-- ═══ COL 1: Sidebar (gamificação + avatar) ═══ -->
|
<!-- Menu global de configs SO em mobile, no topo do body
|
||||||
<Teleport to="#mpr-mobile-drawer-target" :disabled="!isMobile">
|
(em desktop o MelissaLayout ja renderiza a sidebar
|
||||||
|
fixada na esquerda fora do .mpr-page). -->
|
||||||
|
<div v-if="isMobile" class="mpr-mobile-config-menu">
|
||||||
|
<MelissaConfigSidebar />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ═══ COL 1: Sidebar (gamificação + avatar) — inline em
|
||||||
|
desktop e mobile. Em mobile o .mpr-body e flex column,
|
||||||
|
entao essa aside aparece acima do main. ═══ -->
|
||||||
<aside class="mpr-side">
|
<aside class="mpr-side">
|
||||||
<div class="mpr-side__scroll">
|
<div class="mpr-side__scroll">
|
||||||
<!-- Card: Sua evolução -->
|
<!-- Card: Sua evolução -->
|
||||||
@@ -668,7 +642,6 @@ onBeforeUnmount(() => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</Teleport>
|
|
||||||
|
|
||||||
<!-- ═══ COL 2: Main (form) ═══ -->
|
<!-- ═══ COL 2: Main (form) ═══ -->
|
||||||
<div class="mpr-main">
|
<div class="mpr-main">
|
||||||
@@ -1498,97 +1471,66 @@ onBeforeUnmount(() => {
|
|||||||
.mpr-custom > .p-floatlabel,
|
.mpr-custom > .p-floatlabel,
|
||||||
.mpr-custom > div { min-width: 0; }
|
.mpr-custom > div { min-width: 0; }
|
||||||
|
|
||||||
/* ═══════ Mobile drawer (sidebar teleportada) ═══════ */
|
/* ═══════ Menu global de configs inline em mobile (topo do body)
|
||||||
.mpr-mobile-drawer {
|
— substitui o drawer antigo. Em desktop fica oculto pq o
|
||||||
position: fixed;
|
MelissaLayout ja renderiza a sidebar global fixada na esquerda. */
|
||||||
top: 0; left: 0;
|
.mpr-mobile-config-menu {
|
||||||
height: 100vh;
|
display: none;
|
||||||
height: 100dvh;
|
|
||||||
width: min(360px, 88vw);
|
|
||||||
z-index: 80;
|
|
||||||
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);
|
|
||||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.mpr-mobile-drawer.is-open { transform: translateX(0); }
|
|
||||||
.mpr-mobile-drawer__scroll {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.mpr-mobile-drawer__scroll .mpr-side {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
|
||||||
background: transparent;
|
|
||||||
border-right: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.mpr-mobile-drawer__scroll .mpr-side__scroll {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: var(--m-border-strong) transparent;
|
|
||||||
}
|
|
||||||
.mpr-mobile-drawer__scroll .mpr-side__scroll::-webkit-scrollbar { width: 5px; }
|
|
||||||
.mpr-mobile-drawer__scroll .mpr-side__scroll::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--m-border-strong);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.mpr-mobile-drawer__scroll .mpr-w--side {
|
|
||||||
margin: 0;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.mpr-mobile-drawer__scroll .mpr-side__footer {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 12px;
|
|
||||||
background: var(--m-bg-medium);
|
|
||||||
border-top: 1px solid var(--m-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-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;
|
|
||||||
}
|
|
||||||
.mpr-drawer-fade-enter-active,
|
|
||||||
.mpr-drawer-fade-leave-active { transition: opacity 200ms ease; }
|
|
||||||
.mpr-drawer-fade-enter-from,
|
|
||||||
.mpr-drawer-fade-leave-to { opacity: 0; }
|
|
||||||
|
|
||||||
/* ═══════ Mobile (<1024px) ═══════ */
|
/* ═══════ Mobile (<1024px) ═══════ */
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
|
/* Body vira flex column + scroll vertical externo. Tudo (menu
|
||||||
|
global, sidebar contextual, main) stack verticalmente. */
|
||||||
.mpr-body {
|
.mpr-body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
/* Menu global de configs no topo */
|
||||||
|
.mpr-mobile-config-menu {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 8px 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.mpr-mobile-config-menu :deep(.mcs-aside) {
|
||||||
|
height: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
.mpr-mobile-config-menu :deep(.mcs-aside__body) {
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
/* Sidebar contextual (Sua evolucao + Avatar) inline */
|
||||||
.mpr-side {
|
.mpr-side {
|
||||||
display: none;
|
width: 100%;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background: transparent;
|
||||||
|
border-right: none;
|
||||||
|
padding: 8px 8px 0;
|
||||||
|
}
|
||||||
|
.mpr-side__scroll {
|
||||||
|
padding: 0;
|
||||||
|
gap: 8px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.mpr-side__footer {
|
||||||
|
padding: 8px 0 0;
|
||||||
|
background: transparent;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
.mpr-w--side {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.mpr-main {
|
.mpr-main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
overflow: visible;
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
/* Cards em mobile: altura natural por conteudo (sem stretch
|
/* Cards em mobile: altura natural por conteudo (sem stretch
|
||||||
vertical do flex column do .mpr-main, sem clip por overflow). */
|
vertical do flex column do .mpr-main, sem clip por overflow). */
|
||||||
|
|||||||
Reference in New Issue
Block a user