4b826567b8
The rail was on the right. It now leads the row instead, so the section menu reads as a conventional left-hand navigation column. Which side it lands on was never a stylesheet decision: `.app-body` is a flex row and the aside is simply its first child now, with `.app-main` after it. The only style change that had to follow is the rail's separator, which moves from its left edge to its right, plus the collapse tooltip's placement. The global shell contract in base.css now says this explicitly, so the next person to move the rail knows the side comes from DOM order rather than from a rule in that file.
205 lines
6.0 KiB
CSS
205 lines
6.0 KiB
CSS
/* ==========================================================================
|
|
* Global base styles — imported once from src/main.ts, after Element Plus.
|
|
*
|
|
* This file owns exactly two things: a minimal reset, and the application
|
|
* shell's viewport contract. Every view inherits both, so no page has to
|
|
* re-declare margins, heights or scroll behaviour.
|
|
*
|
|
* Why it exists
|
|
* -------------
|
|
* Element Plus ships component styles only: dist/index.css contains no
|
|
* `html` / `body` reset. The browser default `body { margin: 8px }` therefore
|
|
* applied on top of a shell declared as `min-height: 100vh`. The document
|
|
* measured one viewport PLUS 8px, which produced:
|
|
*
|
|
* - a permanently visible vertical scrollbar on the right edge, even on a
|
|
* page whose content is far shorter than the viewport;
|
|
* - every page shifted 8px away from the intended 20px gutter;
|
|
* - two competing scroll containers (the window and .el-main).
|
|
*
|
|
* The contract now
|
|
* ----------------
|
|
* html / body / #app / .app-shell exactly one viewport tall, zero margin
|
|
* body never scrolls -> the window has no bar
|
|
* .app-main the single scroll container, showing a
|
|
* slim bar only when content really does
|
|
* exceed the viewport
|
|
* page gutter el-header / el-main padding (20px),
|
|
* which all content stays inside
|
|
* ========================================================================== */
|
|
|
|
/* --- reset ----------------------------------------------------------------- */
|
|
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html,
|
|
body {
|
|
height: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
/* Element Plus declares --el-font-family but never applies it to the
|
|
document, so page-level text would otherwise fall back to the browser
|
|
default. */
|
|
font-family: var(--el-font-family);
|
|
background-color: var(--el-bg-color-page);
|
|
color: var(--el-text-color-primary);
|
|
|
|
/* The shell below is exactly one viewport tall and owns all scrolling.
|
|
The window itself must never scroll, so no bar appears on the right. */
|
|
overflow: hidden;
|
|
}
|
|
|
|
#app {
|
|
height: 100%;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Media can never be the reason a page overflows its padding. */
|
|
img,
|
|
svg,
|
|
video,
|
|
canvas {
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Long unbroken strings — DSNs, URLs, hashes — wrap instead of pushing the
|
|
content box wider than the page gutter. */
|
|
code,
|
|
kbd,
|
|
samp {
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
/* --- application shell ------------------------------------------------------
|
|
.app-shell, .app-body, .app-main and .app-aside are applied by src/App.vue
|
|
and src/components/SectionAside.vue. They live here because the viewport
|
|
contract spans html -> body -> #app -> shell -> body -> main and only holds
|
|
together when it is declared in one place.
|
|
|
|
The shape is:
|
|
|
|
.app-shell column: header over .app-body
|
|
.app-body row: .app-aside (if present) then .app-main
|
|
.app-main the single scroll container
|
|
.app-aside fixed-width rail, scrolls independently
|
|
|
|
Which side the rail lands on is decided by its DOM order inside the row,
|
|
not by a rule here: first child means left.
|
|
--------------------------------------------------------------------------- */
|
|
|
|
.app-shell {
|
|
/* Fill the viewport exactly. Not `min-height: 100vh`: that grows with
|
|
content and re-introduces the window scrollbar. */
|
|
height: 100%;
|
|
min-height: 0;
|
|
|
|
/* el-container defaults to a row; the shell stacks header over content. */
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* The header keeps its height; only the content area scrolls. */
|
|
.app-shell > .app-header {
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
/* The row holding the content and the right-hand menu. */
|
|
.app-body {
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
|
|
/* el-container is a row by default and stays one because it holds an
|
|
el-aside; stating it keeps the layout readable. */
|
|
flex-direction: row;
|
|
}
|
|
|
|
.app-main {
|
|
/* min-height: 0 lets this flex child actually shrink, so overflow is
|
|
contained here instead of escaping to the window. */
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
|
|
/* Slim, low-contrast scrollbar: invisible while content fits, unobtrusive
|
|
when it does not. Firefox. */
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--el-border-color-darker) transparent;
|
|
}
|
|
|
|
/* WebKit / Blink. */
|
|
.app-main::-webkit-scrollbar,
|
|
.app-aside::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
.app-main::-webkit-scrollbar-track,
|
|
.app-aside::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.app-main::-webkit-scrollbar-thumb,
|
|
.app-aside::-webkit-scrollbar-thumb {
|
|
background-color: var(--el-border-color-darker);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.app-main::-webkit-scrollbar-thumb:hover,
|
|
.app-aside::-webkit-scrollbar-thumb:hover {
|
|
background-color: var(--el-text-color-placeholder);
|
|
}
|
|
|
|
/* The second-level menu, to the left of the content. Its own width is set by
|
|
the component, so only the shrink behaviour belongs here. */
|
|
.app-aside {
|
|
flex: 0 0 auto;
|
|
min-height: 0;
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--el-border-color-darker) transparent;
|
|
transition: width 0.2s ease;
|
|
}
|
|
|
|
/* --- page-level helpers -----------------------------------------------------
|
|
A view's outermost element uses .page to get a consistent vertical rhythm
|
|
inside the main gutter. */
|
|
|
|
.page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Toolbar above a table: filters on the left, actions on the right, wrapping
|
|
on narrow screens instead of overflowing the gutter. */
|
|
.toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.toolbar-spacer {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Right-aligned pagination under a table. */
|
|
.table-pagination {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
margin-top: 16px;
|
|
}
|