Ajusta layout
This commit is contained in:
881
src/App.vue
881
src/App.vue
@@ -1,89 +1,898 @@
|
||||
<template>
|
||||
<q-layout view="lHh lpr lff">
|
||||
<q-header v-if="!isLoginPage" reveal class="bg-primary text-white">
|
||||
<q-toolbar>
|
||||
<Button dense flat round icon="mdi-menu" @click="toggleLeftDrawer" />
|
||||
<q-toolbar-title> {{ currentRouteTitle }} </q-toolbar-title>
|
||||
<Toggle v-model="darkMode" @toggle="toggleDarkMode" />
|
||||
<q-layout view="lHh Lpr lFf" class="app-shell">
|
||||
<q-header v-if="!isLoginPage" class="app-header">
|
||||
<q-toolbar class="app-toolbar">
|
||||
<div class="app-toolbar__page">
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
round
|
||||
icon="sym_o_menu"
|
||||
class="app-toolbar__menu"
|
||||
aria-label="Abrir menu"
|
||||
@click="toggleLeftDrawer"
|
||||
/>
|
||||
|
||||
<div class="app-toolbar__breadcrumbs">
|
||||
<span class="app-toolbar__eyebrow">Clipper · Painel</span>
|
||||
<h1 class="app-toolbar__title">{{ currentRouteTitle }}</h1>
|
||||
<p v-if="currentRouteDescription" class="app-toolbar__subtitle">
|
||||
{{ currentRouteDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-toolbar__actions">
|
||||
<Button
|
||||
v-if="quickAction"
|
||||
:icon="quickAction.icon || 'sym_o_add'"
|
||||
:label="quickAction.label"
|
||||
@click="handleQuickAction"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
dense
|
||||
color="primary"
|
||||
icon="sym_o_notifications"
|
||||
class="app-toolbar__icon"
|
||||
aria-label="Notificações"
|
||||
/>
|
||||
|
||||
<Toggle v-model="darkMode" color="primary" @toggle="toggleDarkMode" />
|
||||
|
||||
<q-btn
|
||||
v-if="!isLoginPage"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
class="app-toolbar__avatar-btn"
|
||||
>
|
||||
<q-avatar size="36px" class="app-toolbar__avatar">
|
||||
<span>{{ userInitials }}</span>
|
||||
</q-avatar>
|
||||
|
||||
<q-menu
|
||||
class="app-user-menu"
|
||||
anchor="bottom right"
|
||||
self="top right"
|
||||
>
|
||||
<div class="app-user-menu__header">
|
||||
<q-avatar size="40px" color="primary" text-color="white">
|
||||
{{ userInitials }}
|
||||
</q-avatar>
|
||||
|
||||
<div class="app-user-menu__info">
|
||||
<div class="app-user-menu__name">
|
||||
{{ currentUserName || "Usuário" }}
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
currentUserEmail && currentUserEmail !== currentUserName
|
||||
"
|
||||
class="app-user-menu__email"
|
||||
>
|
||||
{{ currentUserEmail }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-separator spaced />
|
||||
|
||||
<q-list padding class="app-user-menu__list">
|
||||
<q-item clickable disable>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="sym_o_settings" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>Configurações</q-item-label>
|
||||
<q-item-label caption>Em breve</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item
|
||||
clickable
|
||||
:disable="logoutLoading"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="sym_o_logout" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>Logout</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</div>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
<q-drawer
|
||||
v-if="!isLoginPage"
|
||||
show-if-above
|
||||
v-model="leftDrawerOpen"
|
||||
side="left"
|
||||
show-if-above
|
||||
:width="260"
|
||||
bordered
|
||||
class="app-drawer"
|
||||
>
|
||||
<div class="row q-pa-md items-center">
|
||||
<div class="col q-ml-xs title">
|
||||
<span>Clipper AI</span>
|
||||
<div class="app-drawer__header">
|
||||
<q-avatar size="44px" color="primary" text-color="white">CI</q-avatar>
|
||||
<div>
|
||||
<div class="app-drawer__brand">Clipper IA</div>
|
||||
<div class="app-drawer__tag">Assistente de cortes</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-separator />
|
||||
<q-scroll-area class="fit">
|
||||
<template v-if="menuSections.length">
|
||||
<q-list padding class="app-nav">
|
||||
<template
|
||||
v-for="(section, sectionIndex) in menuSections"
|
||||
:key="section.key"
|
||||
>
|
||||
<q-item-label header class="app-nav__header">
|
||||
<q-icon
|
||||
v-if="section.icon"
|
||||
:name="section.icon"
|
||||
size="18px"
|
||||
class="app-nav__header-icon"
|
||||
/>
|
||||
<span>{{ section.label }}</span>
|
||||
</q-item-label>
|
||||
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="route in routes.filter((route) => route.meta.showinModal)"
|
||||
clickable
|
||||
v-ripple
|
||||
:to="route.path"
|
||||
>
|
||||
<q-item-section>{{ route.meta.title }}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-item
|
||||
v-for="route in section.items"
|
||||
:key="route.path"
|
||||
clickable
|
||||
v-ripple
|
||||
:active="$route.path.startsWith(route.path)"
|
||||
:to="route.path"
|
||||
class="app-nav__item"
|
||||
active-class="app-nav__item--active"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon
|
||||
:name="
|
||||
route.meta?.icon || section.icon || 'sym_o_dashboard'
|
||||
"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<div class="app-nav__label">{{ route.meta?.title }}</div>
|
||||
<div v-if="route.meta?.description" class="app-nav__caption">
|
||||
{{ route.meta.description }}
|
||||
</div>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-icon
|
||||
name="sym_o_chevron_right"
|
||||
size="18px"
|
||||
class="app-nav__chevron"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator
|
||||
v-if="sectionIndex !== menuSections.length - 1"
|
||||
spaced
|
||||
inset
|
||||
/>
|
||||
</template>
|
||||
</q-list>
|
||||
</template>
|
||||
|
||||
<div v-else class="app-nav__empty">
|
||||
<q-icon name="sym_o_lock_person" size="36px" color="primary" />
|
||||
<div class="app-nav__empty-title">Nenhum menu disponível</div>
|
||||
<p class="app-nav__empty-text">
|
||||
Entre em contato com o administrador para habilitar as permissões
|
||||
necessárias.
|
||||
</p>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
|
||||
<div class="app-drawer__footer">
|
||||
<div class="app-drawer__cta">
|
||||
<div class="app-drawer__cta-title">Precisa de ajuda?</div>
|
||||
<div class="app-drawer__cta-text">
|
||||
Nossa equipe está pronta para acelerar seus cortes.
|
||||
</div>
|
||||
<Button
|
||||
color="secondary"
|
||||
text-color="primary"
|
||||
variant="ghost"
|
||||
icon="sym_o_help"
|
||||
label="Abrir suporte"
|
||||
full-width
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-drawer>
|
||||
|
||||
<q-page-container>
|
||||
<router-view />
|
||||
<div v-if="isLoginPage">
|
||||
<router-view />
|
||||
</div>
|
||||
<div v-else class="app-page">
|
||||
<router-view />
|
||||
</div>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Dark } from "quasar";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
import Button from "@components/Button";
|
||||
import Toggle from "@components/Toggle";
|
||||
|
||||
import { routes } from "./auth/router";
|
||||
import { API } from "@config/axios";
|
||||
import { routes, MENUS } from "./auth/router";
|
||||
import {
|
||||
buildUserProfileFromToken,
|
||||
extractUserInitials,
|
||||
mapRolesToAppRoles,
|
||||
} from "@/utils/keycloak";
|
||||
|
||||
const MENU_CONFIG = {
|
||||
default: {
|
||||
label: "Navegação",
|
||||
icon: "sym_o_dashboard",
|
||||
},
|
||||
[MENUS.VIDEOS]: {
|
||||
label: "Vídeos",
|
||||
icon: "sym_o_video_library",
|
||||
},
|
||||
[MENUS.USUARIOS]: {
|
||||
label: "Usuários",
|
||||
icon: "sym_o_groups",
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Button,
|
||||
Toggle,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
leftDrawerOpen: false,
|
||||
leftDrawerOpen: true,
|
||||
darkMode: Dark.isActive,
|
||||
logoutLoading: false,
|
||||
routes,
|
||||
userProfile: this.getInitialUserProfile(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
navigationRoutes() {
|
||||
return this.routes.filter(
|
||||
(route) => route.meta?.showinModal && this.hasPermission(route)
|
||||
);
|
||||
},
|
||||
menuSections() {
|
||||
const groups = this.navigationRoutes.reduce((acc, route) => {
|
||||
const key = route.meta?.menu || "default";
|
||||
|
||||
if (!acc[key]) {
|
||||
const config = MENU_CONFIG[key] || MENU_CONFIG.default || {};
|
||||
|
||||
acc[key] = {
|
||||
key,
|
||||
label:
|
||||
config.label ||
|
||||
route.meta?.menuLabel ||
|
||||
route.meta?.title ||
|
||||
"Navegação",
|
||||
icon: config.icon,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
acc[key].items.push(route);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return Object.values(groups).map((section) => ({
|
||||
...section,
|
||||
items: section.items.sort((a, b) => {
|
||||
const orderA = a.meta?.order ?? 0;
|
||||
const orderB = b.meta?.order ?? 0;
|
||||
return orderA - orderB;
|
||||
}),
|
||||
}));
|
||||
},
|
||||
currentRouteTitle() {
|
||||
const route = this.$route;
|
||||
return route.meta?.title || route.name || "Clipper IA";
|
||||
},
|
||||
currentRouteDescription() {
|
||||
const current = this.$route.meta?.description;
|
||||
if (current) return current;
|
||||
|
||||
const fallback = this.findParentRoute();
|
||||
|
||||
return (
|
||||
fallback?.meta?.description || "Operacionalize cortes com inteligência."
|
||||
);
|
||||
},
|
||||
quickAction() {
|
||||
if (this.$route.meta?.quickAction === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.$route.meta?.quickAction) {
|
||||
return this.$route.meta.quickAction;
|
||||
}
|
||||
|
||||
const fallback = this.findParentRoute();
|
||||
|
||||
return fallback?.meta?.quickAction || null;
|
||||
},
|
||||
isLoginPage() {
|
||||
return this.$route.path === "/login";
|
||||
},
|
||||
userRoles() {
|
||||
const profileRoles = Array.isArray(this.userProfile?.roles)
|
||||
? this.userProfile.roles
|
||||
: [];
|
||||
const rawRoles = Array.isArray(this.userProfile?.rawRoles)
|
||||
? this.userProfile.rawRoles
|
||||
: [];
|
||||
|
||||
const canonical = mapRolesToAppRoles([...profileRoles, ...rawRoles]);
|
||||
|
||||
return Array.from(new Set(canonical));
|
||||
},
|
||||
currentUserName() {
|
||||
return this.userProfile?.name || this.userProfile?.username || "";
|
||||
},
|
||||
currentUserEmail() {
|
||||
return this.userProfile?.email || this.userProfile?.username || "";
|
||||
},
|
||||
userInitials() {
|
||||
return extractUserInitials(this.currentUserName) || "CI";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"$route.path"() {
|
||||
this.syncDrawerWithViewport();
|
||||
this.refreshUserProfile();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.syncDrawerWithViewport();
|
||||
this.refreshUserProfile();
|
||||
window.addEventListener("resize", this.syncDrawerWithViewport);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("resize", this.syncDrawerWithViewport);
|
||||
},
|
||||
methods: {
|
||||
toggleLeftDrawer() {
|
||||
this.leftDrawerOpen = !this.leftDrawerOpen;
|
||||
},
|
||||
toggleDarkMode() {
|
||||
Dark.toggle();
|
||||
this.darkMode = Dark.isActive;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentRouteTitle() {
|
||||
const route = this.$route;
|
||||
return route.meta?.title || route.name || "Clipper AI";
|
||||
handleQuickAction() {
|
||||
if (this.quickAction?.handler) {
|
||||
this.quickAction.handler();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.quickAction?.to) {
|
||||
this.$router.push(this.quickAction.to);
|
||||
}
|
||||
},
|
||||
isLoginPage() {
|
||||
return this.$route.path === "/login";
|
||||
async handleLogout() {
|
||||
try {
|
||||
this.logoutLoading = true;
|
||||
|
||||
await API.post("/auth/logout");
|
||||
|
||||
const cookieRemovalOptions = { path: "/" };
|
||||
|
||||
Cookies.remove("token", cookieRemovalOptions);
|
||||
Cookies.remove("refresh_token", cookieRemovalOptions);
|
||||
Cookies.remove("user_roles", cookieRemovalOptions);
|
||||
Cookies.remove("user_profile", cookieRemovalOptions);
|
||||
|
||||
this.userProfile = { roles: [] };
|
||||
this.leftDrawerOpen = false;
|
||||
|
||||
if (this.$route.path !== "/login") {
|
||||
this.$router.push("/login");
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
this.logoutLoading = false;
|
||||
}
|
||||
},
|
||||
syncDrawerWithViewport() {
|
||||
if (window.innerWidth < 1024) {
|
||||
this.leftDrawerOpen = false;
|
||||
} else {
|
||||
this.leftDrawerOpen = true;
|
||||
}
|
||||
},
|
||||
findParentRoute() {
|
||||
const currentMenu = this.$route.meta?.menu;
|
||||
|
||||
if (!currentMenu) {
|
||||
return this.navigationRoutes.find(
|
||||
(route) => route.path === this.$route.path
|
||||
);
|
||||
}
|
||||
|
||||
return this.navigationRoutes.find(
|
||||
(route) => route.meta?.menu && route.meta.menu === currentMenu
|
||||
);
|
||||
},
|
||||
hasPermission(route) {
|
||||
const required = route.meta?.permissions;
|
||||
|
||||
if (!Array.isArray(required) || required.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return required.some((role) => this.userRoles.includes(role));
|
||||
},
|
||||
getInitialUserProfile() {
|
||||
const profileCookie = Cookies.get("user_profile");
|
||||
const rolesCookie = Cookies.get("user_roles");
|
||||
|
||||
let profileFromCookie = null;
|
||||
|
||||
if (profileCookie) {
|
||||
try {
|
||||
profileFromCookie = JSON.parse(profileCookie);
|
||||
} catch (error) {
|
||||
console.warn("Invalid user profile cookie", error);
|
||||
}
|
||||
}
|
||||
|
||||
const collectedRawRoles = new Set();
|
||||
|
||||
if (Array.isArray(profileFromCookie?.roles)) {
|
||||
profileFromCookie.roles.forEach((role) => collectedRawRoles.add(role));
|
||||
}
|
||||
|
||||
if (Array.isArray(profileFromCookie?.rawRoles)) {
|
||||
profileFromCookie.rawRoles.forEach((role) =>
|
||||
collectedRawRoles.add(role)
|
||||
);
|
||||
}
|
||||
|
||||
if (rolesCookie) {
|
||||
try {
|
||||
const parsedRoles = JSON.parse(rolesCookie);
|
||||
|
||||
if (Array.isArray(parsedRoles)) {
|
||||
parsedRoles.forEach((role) => collectedRawRoles.add(role));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Invalid roles cookie", error);
|
||||
}
|
||||
}
|
||||
|
||||
const combinedRawRoles = Array.from(collectedRawRoles);
|
||||
const mappedRoles = mapRolesToAppRoles(combinedRawRoles);
|
||||
|
||||
if (profileFromCookie) {
|
||||
return {
|
||||
...profileFromCookie,
|
||||
rawRoles: combinedRawRoles,
|
||||
roles: mappedRoles,
|
||||
};
|
||||
}
|
||||
|
||||
const token = Cookies.get("token");
|
||||
|
||||
if (!token) {
|
||||
if (mappedRoles.length) {
|
||||
return {
|
||||
roles: mappedRoles,
|
||||
rawRoles: combinedRawRoles,
|
||||
};
|
||||
}
|
||||
|
||||
return { roles: [] };
|
||||
}
|
||||
|
||||
const profile = buildUserProfileFromToken(token);
|
||||
|
||||
if (!profile) {
|
||||
return { roles: [] };
|
||||
}
|
||||
|
||||
const combinedFromToken = new Set([
|
||||
...(profile.rawRoles || []),
|
||||
...combinedRawRoles,
|
||||
...(profile.roles || []),
|
||||
]);
|
||||
|
||||
const finalRawRoles = Array.from(combinedFromToken);
|
||||
const finalRoles = mapRolesToAppRoles(finalRawRoles);
|
||||
|
||||
return {
|
||||
...profile,
|
||||
rawRoles: finalRawRoles,
|
||||
roles: finalRoles,
|
||||
};
|
||||
},
|
||||
refreshUserProfile() {
|
||||
const profile = this.getInitialUserProfile();
|
||||
this.userProfile = profile || { roles: [] };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
font-size: 24px;
|
||||
color: $primary;
|
||||
<style scoped lang="scss">
|
||||
.app-shell {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
backdrop-filter: blur(18px);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border-bottom: 1px solid rgba(127, 86, 217, 0.12);
|
||||
padding: 10px clamp(12px, 2vw, 24px);
|
||||
}
|
||||
|
||||
body.body--dark .app-header {
|
||||
background: rgba(16, 24, 40, 0.9);
|
||||
border-bottom-color: rgba(244, 235, 255, 0.08);
|
||||
}
|
||||
|
||||
.app-toolbar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: clamp(16px, 3vw, 40px);
|
||||
}
|
||||
|
||||
.app-toolbar__page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.app-toolbar__menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-toolbar__breadcrumbs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.app-toolbar__eyebrow {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: #7f56d9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app-toolbar__title {
|
||||
font-size: clamp(24px, 3vw, 32px);
|
||||
font-weight: 700;
|
||||
color: #101828;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app-toolbar__subtitle {
|
||||
font-size: 14px;
|
||||
color: #475467;
|
||||
margin: 0;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
body.body--dark .app-toolbar__eyebrow {
|
||||
color: #f4ebff;
|
||||
}
|
||||
|
||||
body.body--dark .app-toolbar__title {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
body.body--dark .app-toolbar__subtitle {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.app-toolbar__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.app-toolbar__avatar-btn {
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-toolbar__avatar-btn .q-btn__content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.app-toolbar__icon {
|
||||
background: rgba(127, 86, 217, 0.08);
|
||||
color: #53389e;
|
||||
}
|
||||
|
||||
body.body--dark .app-toolbar__icon {
|
||||
background: rgba(244, 235, 255, 0.12);
|
||||
color: #f4ebff;
|
||||
}
|
||||
|
||||
.app-toolbar__avatar {
|
||||
background: rgba(127, 86, 217, 0.16);
|
||||
color: #53389e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body.body--dark .app-toolbar__avatar {
|
||||
background: rgba(244, 235, 255, 0.2);
|
||||
color: #0b1120;
|
||||
}
|
||||
|
||||
.app-user-menu {
|
||||
min-width: 240px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.app-user-menu__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 16px 8px;
|
||||
}
|
||||
|
||||
.app-user-menu__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.app-user-menu__name {
|
||||
font-weight: 600;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.app-user-menu__email {
|
||||
font-size: 13px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.app-user-menu__list .q-item {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.app-user-menu__list .q-item:not(.q-item--disabled):hover {
|
||||
background: rgba(127, 86, 217, 0.08);
|
||||
}
|
||||
|
||||
body.body--dark .app-user-menu__name {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
body.body--dark .app-user-menu__email {
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
body.body--dark .app-user-menu__list .q-item:not(.q-item--disabled):hover {
|
||||
background: rgba(244, 235, 255, 0.12);
|
||||
}
|
||||
|
||||
.app-drawer {
|
||||
padding: 24px 0 16px;
|
||||
border-right: none;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
body.body--dark .app-drawer {
|
||||
background: rgba(16, 24, 40, 0.96);
|
||||
}
|
||||
|
||||
.app-drawer__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.app-drawer__brand {
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.app-drawer__tag {
|
||||
font-size: 13px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
body.body--dark .app-drawer__brand {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
body.body--dark .app-drawer__tag {
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
.app-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.app-nav__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: #98a2b3;
|
||||
}
|
||||
|
||||
.app-nav__header-icon {
|
||||
color: #7f56d9;
|
||||
}
|
||||
|
||||
.app-nav__item {
|
||||
border-radius: 14px;
|
||||
margin: 0 12px;
|
||||
padding: 12px;
|
||||
transition: background 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.app-nav__item:hover {
|
||||
background: rgba(127, 86, 217, 0.08);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.app-nav__item--active {
|
||||
background: rgba(127, 86, 217, 0.16);
|
||||
color: #53389e;
|
||||
}
|
||||
|
||||
.app-nav__label {
|
||||
font-weight: 600;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.app-nav__caption {
|
||||
font-size: 12px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.app-nav__chevron {
|
||||
color: #c7c9d9;
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__item:hover {
|
||||
background: rgba(244, 235, 255, 0.1);
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__item--active {
|
||||
background: rgba(244, 235, 255, 0.18);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__label {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__caption {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__chevron {
|
||||
color: rgba(244, 235, 255, 0.4);
|
||||
}
|
||||
|
||||
.app-nav__empty {
|
||||
padding: 48px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 12px;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
.app-nav__empty-title {
|
||||
font-weight: 600;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.app-nav__empty-text {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__header-icon {
|
||||
color: #f4ebff;
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__empty-title {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
body.body--dark .app-nav__empty-text {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.app-drawer__footer {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.app-drawer__cta {
|
||||
background: rgba(127, 86, 217, 0.12);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.app-drawer__cta-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #53389e;
|
||||
}
|
||||
|
||||
.app-drawer__cta-text {
|
||||
font-size: 13px;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
body.body--dark .app-drawer__cta {
|
||||
background: rgba(244, 235, 255, 0.12);
|
||||
}
|
||||
|
||||
body.body--dark .app-drawer__cta-title {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
body.body--dark .app-drawer__cta-text {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.app-toolbar__menu {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.app-drawer {
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-toolbar__actions {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.app-toolbar__subtitle {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.app-toolbar__icon,
|
||||
.app-toolbar__avatar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user