Files
op-packages/luci-theme-footstrap/styles/theme/50-toplayout.css
T
github-actions[bot] 410d762592
Merge-upstream / merge (push) Canceled after 0s
🎄 Sync 2026-08-27 02:13:09
2026-08-27 02:13:09 +08:00

92 lines
5.6 KiB
CSS

@layer theme {
/* ---- the top-layout bar: :root[data-layout="top"], every width ----
*
* A DELTA, not a layout. The bar itself — sticky, blurred, row, popup submenus, square icon
* buttons, right cluster — is the unguarded base in 20-shell.css, the bar being what the chrome
* IS unless the vertical sidebar overrides it. This file adds what the top-layout bar wants on
* top of that:
* - a designed bar height, and padding aligned to the content column;
* - the menu on the brand's row instead of wrapped beneath it.
*
* No `@media` floor: "does the menu fit on the brand's row?" is a property of the CONTENT (5
* sections stock, eleven with a few luci-app-*), not of the viewport, so the whole bar is
* MEASURED — fs-chrome.js shrinks the pills, collapses the poll pill to an icon, then stacks the
* menu onto its own row, at any width. The under-item dropdown and its clamp therefore apply at
* every width too. */
:root[data-layout="top"] .fs-sidebar {
/* min-height, not height: the bar has a designed height but must be able to GROW —
* a long hostname wraps onto a second line inside the capped brand (20-shell.css),
* and a fixed height would clip it. */
min-height: var(--fs-bar-h); height: auto;
gap: var(--fs-space-3);
/* nowrap while the menu rides the brand's row. Without it flexbox wraps the BAR, carrying the
* menu onto a second line by itself while its own pills stay in one row — a stacked bar in
* everything but the styling. It also destroys the fits-on-one-row measurement, a menu handed
* a whole row of course fitting. Forbidding the wrap forces the menu to share the row and to
* wrap its OWN pills when it cannot, which is the signal fitChrome measures. */
flex-wrap: nowrap;
/* align the bar's contents to the .fs-content column, so the brand and the menu line
* up with the headings and the cards below */
padding: 0 max(20px, calc((100% - var(--fs-content-max)) / 2 + var(--fs-content-pad)));
box-shadow: var(--fs-shadow-bar);
}
/* the menu rides the brand's row; its flex:1 opens the gap that pushes the right-hand
* cluster to the content column's right edge */
:root[data-layout="top"] .fs-sidebar ul.nav {
order: 2; flex: 1 1 auto; margin-inline-start: var(--fs-space-2);
}
:root[data-layout="top"] .fs-sidebar #indicators,
:root[data-layout="top"] .fs-themerow,
:root[data-layout="top"] .fs-logout { order: 3; margin-inline-start: 0; }
/* The under-item dropdown anchor, its hover bridge and the clamp are NOT here: they belong to
* the BAR and live unguarded in 20-shell.css. They used to be this file's, and that WAS issue
* #19 — the sidebar layout collapsed onto a bar is the same chrome, and it inherited a panel
* pinned to the bar's left edge with no bridge, unreachable by hover. This file is the top
* layout's delta; a rule every bar state needs is not a delta. */
/* the mode strip (only rendered when a router has more than one top-level mode) takes
* its own full-width row under the bar */
/* :not(:empty):not(.single) instead of a hide pair — the bar's base rule (20-shell.css)
* already hides the strip; see the sidebar twin there for why the pair was folded. */
:root[data-layout="top"] .fs-modemenu:not(:empty):not(.single) {
display: flex; order: 4; flex: 1 1 100%; margin: 0; gap: var(--fs-space-1);
}
/* the bar replaces the sidebar's 22px top padding with the top-nav's 26px */
:root[data-layout="top"] .fs-content { padding-top: var(--fs-space-6); }
/* ---- the menu does not fit on the brand's row: .fs-bar-stack ----
*
* Set by fs-chrome.js (fitChrome), not by a media query: whether the menu fits beside the brand
* depends on how many sections the router HAS, so the width at which the bar must stack is a
* property of the content and a hard-coded breakpoint gets it wrong in both directions. The JS
* shrinks the pills first, then collapses the poll pill, and stacks only when even that still
* overflows. */
:root[data-layout="top"] .fs-sidebar.fs-bar-stack {
flex-wrap: wrap; height: auto; padding: var(--fs-space-2-5) var(--fs-space-4);
}
:root[data-layout="top"] .fs-sidebar.fs-bar-stack ul.nav {
order: 3; flex: 1 1 100%; margin-inline-start: 0;
}
:root[data-layout="top"] .fs-sidebar.fs-bar-stack #indicators,
:root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-themerow,
:root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-logout { order: 2; }
:root[data-layout="top"] .fs-sidebar.fs-bar-stack #indicators,
:root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-themerow { margin-inline-start: auto; }
:root[data-layout="top"] .fs-sidebar.fs-bar-stack #indicators:not(:empty) ~ .fs-themerow { margin-inline-start: 0; }
/* Two .fs-themerow buttons, Search then Appearance: only the FIRST rendered far-edge item may
* keep the auto inline-start margin, exactly as in the base bar, or flexbox splits the free
* space between the two autos and Search drifts to mid-bar. The base `.fs-themerow ~
* .fs-themerow` guard is out-specified by the auto rule above (0,4,0 against 0,2,0), so it must
* be restated here. Bites when #indicators is :empty — right after an SPA nav, before
* the poll pill returns. */
:root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-themerow ~ .fs-themerow { margin-inline-start: 0; }
/* The right cluster's own escalation — .fs-ind-compact and .fs-bar-actrow — is NOT here: it
* belongs to the BAR, and the sidebar layout is a bar too once fitShell has stamped
* data-narrow. Both live with the bar in 20-shell.css; this file is only the top layout's
* delta, and the menu's .fs-bar-stack above is the part that really is top-only. */
}