feat(sidebar): fleet sidebar standard (header row, user in sidebar, collapsible groups, mini mode, no-flash transitions)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b73c23bf55
commit
aa55d44406
8 files changed
+654
-225
No files matched your search
@@ -0,0 +1,351 @@
|
||||
/* Backoffice FitMe 3070: tokens da marca, casca (barra lateral, topbar, gaveta
|
||||
em telemóvel) e acertos dos componentes. Carregado por includes/header.php
|
||||
depois do Bootstrap. O Bootstrap nunca se edita: o que muda faz-se com
|
||||
variáveis e com classes próprias (prefixo bo-). */
|
||||
|
||||
:root {
|
||||
--bo-accent: #C2D500;
|
||||
--bo-accent-rgb: 194,213,0;
|
||||
--bo-dark: #0a0a0a;
|
||||
--bo-dark-2: #151515;
|
||||
--bo-surface: #eef0f4;
|
||||
--bo-sidebar-w: 264px;
|
||||
--bo-sidebar-w-mini: 80px;
|
||||
--bo-topbar-h: 56px;
|
||||
--bo-ease: .2s ease;
|
||||
/* Texto da barra lateral (escura nos dois temas) */
|
||||
--bo-nav-text: rgba(255,255,255,.62);
|
||||
--bo-nav-text-muted: rgba(255,255,255,.5);
|
||||
--bo-nav-line: rgba(255,255,255,.1);
|
||||
--bo-nav-strong: #fff;
|
||||
}
|
||||
[data-bs-theme="dark"] { --bo-surface: #121416; }
|
||||
|
||||
body { background: var(--bo-surface); font-size: .925rem; }
|
||||
a { color: var(--bs-emphasis-color); }
|
||||
main { margin-left: var(--bo-sidebar-w); padding-top: calc(var(--bo-topbar-h) + 12px); }
|
||||
|
||||
/* ── Barra lateral ───────────────────────────────────────────────────────── */
|
||||
/* Sem overflow aqui: o menu do utilizador sai da barra no modo mini.
|
||||
Quem rola é só .bo-sidebar__nav; cabeçalho e utilizador ficam fixos. */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
inset: 0 auto 0 0;
|
||||
z-index: 100;
|
||||
width: var(--bo-sidebar-w);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bo-dark);
|
||||
}
|
||||
|
||||
/* Cabeçalho numa linha: [☰] [marca] [↗ site público] */
|
||||
.bo-sidebar__head {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
height: var(--bo-topbar-h);
|
||||
padding: 0 .75rem;
|
||||
border-bottom: 1px solid var(--bo-nav-line);
|
||||
}
|
||||
.bo-sidebar__toggle,
|
||||
.bo-sidebar__site {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--bo-nav-text);
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
transition: background var(--bo-ease), color var(--bo-ease);
|
||||
}
|
||||
.bo-sidebar__site { font-size: 1rem; }
|
||||
.bo-sidebar__toggle:hover,
|
||||
.bo-sidebar__site:hover { background: rgba(255,255,255,.08); color: var(--bo-nav-strong); }
|
||||
.bo-sidebar__brand {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: var(--bo-nav-strong);
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
letter-spacing: .5px;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.bo-sidebar__brand i { color: var(--bo-accent); }
|
||||
|
||||
/* Utilizador logo abaixo do cabeçalho */
|
||||
.bo-sidebar__user {
|
||||
flex: none;
|
||||
padding: .6rem .75rem;
|
||||
border-bottom: 1px solid var(--bo-nav-line);
|
||||
}
|
||||
.bo-user-menu {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
padding: .35rem .45rem;
|
||||
border: 1px solid var(--bo-nav-line);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--bo-nav-strong);
|
||||
text-align: left;
|
||||
}
|
||||
.bo-user-menu:hover,
|
||||
.bo-user-menu.show { background: rgba(255,255,255,.08); color: var(--bo-nav-strong); }
|
||||
.bo-user-menu::after { margin-left: auto; flex: none; }
|
||||
.bo-user-menu__avatar {
|
||||
flex: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--bo-accent);
|
||||
color: var(--bo-dark);
|
||||
font-size: .8rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.bo-user-menu__text { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; line-height: 1.25; }
|
||||
.bo-user-menu__name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .85rem; font-weight: 600; }
|
||||
.bo-user-menu__text .badge { font-size: .62rem; font-weight: 600; }
|
||||
.bo-sidebar__user .dropdown-menu { min-width: 14rem; z-index: 1110; }
|
||||
|
||||
/* Lista de navegação: a única parte que rola */
|
||||
.bo-sidebar__nav {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: .5rem 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.sidebar .bo-nav-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
min-height: 40px;
|
||||
padding: .5rem 1.25rem;
|
||||
border-left: 3px solid transparent;
|
||||
color: var(--bo-nav-text);
|
||||
font-size: .875rem;
|
||||
transition: background var(--bo-ease), color var(--bo-ease), border-color var(--bo-ease);
|
||||
}
|
||||
.sidebar .bo-nav-item i { flex: none; width: 1.25rem; text-align: center; font-size: 1.05rem; }
|
||||
.sidebar .bo-nav-item:hover { color: var(--bo-nav-strong); background: rgba(255,255,255,.06); border-left-color: rgba(255,255,255,.2); }
|
||||
.sidebar .bo-nav-item.active { color: var(--bo-nav-strong); background: rgba(var(--bo-accent-rgb),.12); border-left-color: var(--bo-accent); }
|
||||
.sidebar .bo-nav-item.active i { color: var(--bo-accent); }
|
||||
.sidebar .bo-nav-item--primary i { font-size: 1.25rem; }
|
||||
/* Indentação = cadeia de dados: filhos de um grupo, e de um subgrupo. */
|
||||
.sidebar .bo-nav-item--sub { padding-left: 2.3rem; font-size: .84rem; }
|
||||
.sidebar .bo-nav-item--sub2 { padding-left: 3.2rem; font-size: .84rem; }
|
||||
.bo-nav-item__label { min-width: 0; }
|
||||
|
||||
.sidebar .bo-nav-item:focus-visible,
|
||||
.bo-nav-group__label:focus-visible,
|
||||
.bo-sidebar__toggle:focus-visible,
|
||||
.bo-sidebar__site:focus-visible,
|
||||
.bo-user-menu:focus-visible,
|
||||
.bo-sidebar__brand:focus-visible { outline: 2px solid var(--bo-accent); outline-offset: -2px; }
|
||||
|
||||
/* Grupos recolhíveis */
|
||||
.bo-nav-group { margin-top: .25rem; }
|
||||
.bo-nav-group__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
width: 100%;
|
||||
padding: .55rem 1.25rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--bo-nav-text-muted);
|
||||
font-family: inherit;
|
||||
font-size: .68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .14em;
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bo-nav-group__label:hover { color: var(--bo-nav-strong); background: rgba(255,255,255,.04); }
|
||||
.bo-nav-group__icon { flex: none; width: 1.25rem; text-align: center; font-size: 1.1rem; letter-spacing: 0; }
|
||||
.bo-nav-group__text { flex: 1; min-width: 0; }
|
||||
.bo-nav-group__chevron { font-size: .75rem; letter-spacing: 0; transition: rotate var(--bo-ease); }
|
||||
.bo-nav-group__label[aria-expanded="false"] .bo-nav-group__chevron { rotate: -90deg; }
|
||||
.bo-nav-group--sub > .bo-nav-group__label { padding-left: 1.9rem; }
|
||||
|
||||
/* Corpo recolhível: grelha 1fr -> 0fr anima a altura sem JavaScript a medir. */
|
||||
.bo-nav-group__body {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
transition: grid-template-rows var(--bo-ease), visibility var(--bo-ease);
|
||||
}
|
||||
.bo-nav-group__inner {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
/* Fechado = classe (clique no grupo da página ativa) ou chave no atributo do
|
||||
<html> posto pelo script do <head> antes da pintura. O grupo da página ativa
|
||||
só fecha por clique (.is-closed), nunca pelo estado guardado. visibility
|
||||
tira os links fechados da ordem de tabulação. Grupo novo = chave nova aqui. */
|
||||
:is(.bo-nav-group.is-closed,
|
||||
html[data-bo-groups~="gestao"] .bo-nav-group[data-group="gestao"]:not(.has-active),
|
||||
html[data-bo-groups~="site"] .bo-nav-group[data-group="site"]:not(.has-active),
|
||||
html[data-bo-groups~="horarios"] .bo-nav-group[data-group="horarios"]:not(.has-active),
|
||||
html[data-bo-groups~="comercial"] .bo-nav-group[data-group="comercial"]:not(.has-active),
|
||||
html[data-bo-groups~="legal"] .bo-nav-group[data-group="legal"]:not(.has-active),
|
||||
html[data-bo-groups~="utilizadores"] .bo-nav-group[data-group="utilizadores"]:not(.has-active),
|
||||
html[data-bo-groups~="sistema"] .bo-nav-group[data-group="sistema"]:not(.has-active)) > .bo-nav-group__body {
|
||||
grid-template-rows: 0fr;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Véu por trás da gaveta em telemóvel */
|
||||
.sidebar-backdrop { display: none; position: fixed; inset: 0; z-index: 1090; background: rgba(0,0,0,.45); }
|
||||
.sidebar-backdrop.show { display: block; }
|
||||
|
||||
/* ── Topbar ──────────────────────────────────────────────────────────────── */
|
||||
.topbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: var(--bo-sidebar-w);
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
height: var(--bo-topbar-h);
|
||||
padding: 0 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
background: var(--bs-body-bg);
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
}
|
||||
.bo-topbar__tools { display: flex; align-items: center; gap: .5rem; margin-left: auto; }
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bs-body-bg);
|
||||
color: var(--bs-body-color);
|
||||
font-size: 1.35rem;
|
||||
line-height: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.theme-toggle { min-width: 44px; min-height: 44px; }
|
||||
|
||||
/* ── Marca nos componentes ───────────────────────────────────────────────── */
|
||||
.badge-role-sysadmin { background: var(--bo-accent); color: var(--bo-dark); }
|
||||
.badge-role-admin { background: var(--bo-accent); color: var(--bo-dark); opacity: .85; }
|
||||
.badge-role-staff { background: #6f42c1; }
|
||||
.badge-role-client { background: #adb5bd; color: #212529; }
|
||||
.btn-outline-primary { color: var(--bo-accent); border-color: var(--bo-accent); }
|
||||
.btn-outline-primary:hover { background: var(--bo-accent); border-color: var(--bo-accent); color: var(--bo-dark); }
|
||||
.btn-primary { background: var(--bo-accent); border-color: var(--bo-accent); color: var(--bo-dark); }
|
||||
.btn-primary:hover { background: #a8ba00; border-color: #a8ba00; color: var(--bo-dark); }
|
||||
.card { border: none; box-shadow: 0 1px 3px rgba(0,0,0,.04); }
|
||||
[data-bs-theme="dark"] .table-light { --bs-table-bg: var(--bs-tertiary-bg); --bs-table-color: var(--bs-body-color); }
|
||||
|
||||
/* Summernote (WYSIWYG): alinhado com o aspeto do backoffice */
|
||||
.note-editor.note-frame { border-color: #ced4da; border-radius: .375rem; }
|
||||
.note-editor .note-editing-area .note-editable { font-size: .95rem; }
|
||||
.note-editor.note-frame.note-focused { border-color: var(--bo-accent); box-shadow: 0 0 0 .2rem rgba(var(--bo-accent-rgb), .25); }
|
||||
.note-toolbar { background: var(--bs-tertiary-bg); }
|
||||
.note-btn.btn-primary { background: var(--bo-accent); border-color: var(--bo-accent); color: var(--bo-dark); }
|
||||
|
||||
/* ── Telemóvel: barra lateral em gaveta ──────────────────────────────────── */
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform .25s ease;
|
||||
z-index: 1100;
|
||||
box-shadow: 2px 0 18px rgba(0,0,0,.3);
|
||||
}
|
||||
.sidebar.open { transform: translateX(0); }
|
||||
main { margin-left: 0; }
|
||||
.topbar { left: 0; padding: 0 .75rem; }
|
||||
.menu-toggle { display: inline-flex; }
|
||||
/* Tabelas largas rolam dentro do cartão em vez de alargar a página */
|
||||
main .card > .card-body { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
/* Cabeçalhos de página (título + botões) empilham em vez de transbordar */
|
||||
main .d-flex.justify-content-between.align-items-center { flex-wrap: wrap; gap: .5rem; }
|
||||
/* 16px nos campos: o iOS não faz zoom ao focar */
|
||||
.form-control, .form-select { font-size: 16px; }
|
||||
/* Margens confortáveis em telemóvel */
|
||||
/* !important: .p-3 é uma utilitária do Bootstrap e tem !important de origem */
|
||||
.p-3 { padding: .85rem !important; }
|
||||
}
|
||||
|
||||
/* ── Modo mini (só ecrã grande; classe posta no <html> pelo script do <head>) ── */
|
||||
@media (min-width: 992px) {
|
||||
html.bo-mini { --bo-sidebar-w: var(--bo-sidebar-w-mini); }
|
||||
html.bo-mini .bo-sidebar__head {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: auto;
|
||||
padding: .5rem 0;
|
||||
gap: .25rem;
|
||||
}
|
||||
html.bo-mini .bo-sidebar__brand { display: none; }
|
||||
html.bo-mini .bo-sidebar__user { padding: .5rem; }
|
||||
html.bo-mini .bo-user-menu { justify-content: center; padding: .25rem; }
|
||||
html.bo-mini .bo-user-menu__text,
|
||||
html.bo-mini .bo-user-menu::after { display: none; }
|
||||
html.bo-mini .bo-sidebar__nav { padding: .5rem 0; }
|
||||
html.bo-mini .sidebar .bo-nav-item,
|
||||
html.bo-mini .sidebar .bo-nav-item--sub,
|
||||
html.bo-mini .sidebar .bo-nav-item--sub2 { justify-content: center; padding: .5rem; }
|
||||
html.bo-mini .bo-nav-item__label { display: none; }
|
||||
html.bo-mini .bo-nav-item__badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 8px;
|
||||
margin: 0;
|
||||
padding: .15em .4em;
|
||||
font-size: .6rem;
|
||||
}
|
||||
/* Categoria no mini: sigla de texto (data-short), sem ícone nem chevron. */
|
||||
html.bo-mini .bo-nav-group__label,
|
||||
html.bo-mini .bo-nav-group--sub > .bo-nav-group__label { justify-content: center; padding: .55rem .25rem; font-size: 0; }
|
||||
html.bo-mini .bo-nav-group__label::before { content: attr(data-short); font-size: .66rem; font-weight: 700; letter-spacing: .08em; }
|
||||
html.bo-mini .bo-nav-group__text,
|
||||
html.bo-mini .bo-nav-group__icon,
|
||||
html.bo-mini .bo-nav-group__chevron { display: none; }
|
||||
}
|
||||
|
||||
/* ── Navegação sem piscar: só o conteúdo faz fade; barra e topbar ficam ── */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
@view-transition { navigation: auto; }
|
||||
.sidebar { view-transition-name: bo-sidebar; }
|
||||
.topbar { view-transition-name: bo-topbar; }
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) { animation-duration: .14s; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sidebar,
|
||||
.sidebar .bo-nav-item,
|
||||
.bo-nav-group__body,
|
||||
.bo-nav-group__chevron { transition: none; }
|
||||
}
|
||||
Reference in new issue
Block a user