mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-11 19:05:11 +08:00
508 lines
27 KiB
CSS
508 lines
27 KiB
CSS
@layer theme {
|
|
/* Generic .dropdown-menu popup. Nothing in luci-base emits it — the split "Save & Apply" button is
|
|
* a `.cbi-dropdown`, a different widget — so this is coverage vocabulary: stock
|
|
* luci-theme-bootstrap styles it, so a third-party luci-app-* may emit it and expect a themed
|
|
* result — theme/10-chrome.css claims the same class for the same reason. Not the theme's nav
|
|
* menu, which is a plain nested <ul>.
|
|
* Hover text is accent to match the open cbi-dropdown list: this is a form control's menu. */
|
|
.dropdown-menu { background-color: var(--fs-panel); border-color: var(--fs-border); }
|
|
.dropdown-menu a { color: var(--fs-text); text-shadow: none; }
|
|
.dropdown-menu a:hover,
|
|
.dropdown-menu a:focus-visible { background: var(--fs-panel2); background-image: none; color: var(--fs-accent); }
|
|
|
|
/* Split "Save & Apply": a plain dropdown in the action bar carries no frame of its own.
|
|
*
|
|
* The guard is the same `:not(.btn):not(.cbi-button)` the widget rules below use, and it is
|
|
* load-bearing: the split control's outer div IS the button, and the class on it is the one
|
|
* ui.js picked for the SELECTED mode — `cbi-button-apply`, or `cbi-button-negative` the moment
|
|
* the user picks "Apply unchecked" (luci.js's ComboButton `classes` map). Unguarded, this rule
|
|
* (0,2,0) erases that variant's fill while its ink survives from the variant rule that sets no
|
|
* background: a button painted page-on-page, at 1.06-1.13:1. */
|
|
.cbi-page-actions .cbi-dropdown:not(.btn):not(.cbi-button) {
|
|
border: 0; border-radius: var(--fs-radius); background: transparent;
|
|
}
|
|
/* Apply is the one variant with no fill of its own — `.cbi-button-action` and the rest carry
|
|
* theirs from theme/55-buttons.css, and `.cbi-button-apply` is filled only where it is THE
|
|
* primary action, which is this bar. Painted by SPECIFICITY — it used to be a rule written
|
|
* afterwards, i.e. a fix that depended on source order. */
|
|
.cbi-page-actions .cbi-dropdown.cbi-button-apply,
|
|
.cbi-page-actions .cbi-dropdown > .cbi-button {
|
|
background: var(--fs-accent);
|
|
background-image: none;
|
|
border: 0;
|
|
color: var(--fs-on-accent);
|
|
}
|
|
.cbi-page-actions .cbi-dropdown.cbi-button-apply:hover,
|
|
.cbi-page-actions .cbi-dropdown > .cbi-button:hover { filter: var(--fs-hover-lift); }
|
|
|
|
/* ---- cbi-dropdown widget: the JS select used across all forms ----
|
|
* Restyled to the design dropdown (panel2, rounded, chevron, focus ring) with a themed open
|
|
* list. A native <select> is styled separately.
|
|
*
|
|
* The widget's MACHINERY is here too, absorbed from base whole. ui.js drives it purely through
|
|
* attributes ([open], [multiple], [empty], [more], [display], [selected]) and the rules below
|
|
* turn those into a working combobox. The display state machine is UNFLAGGED: upstream resolved
|
|
* the states with !important, but flag-vs-flag falls back to specificity-then-order anyway, so
|
|
* the machine below states the same resolution openly. The flags came in with the absorbed
|
|
* widget from a theme with no @layer, which needed them to beat its own bare `ul` rule. */
|
|
.cbi-dropdown {
|
|
display: inline-flex;
|
|
cursor: pointer;
|
|
height: auto;
|
|
position: relative;
|
|
padding: 0;
|
|
}
|
|
.cbi-dropdown img {
|
|
/* constrain a vector icon of any intrinsic size to the text line */
|
|
max-height: 1.6em;
|
|
width: auto;
|
|
}
|
|
.cbi-dropdown > ul {
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
overflow-x: hidden;
|
|
overflow-y: hidden;
|
|
display: flex;
|
|
width: 100%;
|
|
}
|
|
/* clears the button's own side inset so the label starts on its text, not on its border.
|
|
* Unflagged: (0,3,1) over the rule above's (0,1,1) — it only ever needed a flag to beat that
|
|
* rule's flag. */
|
|
.cbi-dropdown.btn > ul:not(.dropdown),
|
|
.cbi-dropdown.cbi-button > ul:not(.dropdown) {
|
|
margin: 0 0 0 var(--fs-space-3);
|
|
}
|
|
.cbi-dropdown.btn.spinning > ul:not(.dropdown),
|
|
.cbi-dropdown.cbi-button.spinning > ul:not(.dropdown) {
|
|
margin: 0;
|
|
}
|
|
.cbi-dropdown > ul.preview {
|
|
display: none;
|
|
}
|
|
/* no `display` here: `.open` states its own below, and `.more` is display: none until the
|
|
* [multiple][more]/[multiple][empty] showers flip it — one declaration per state, or the
|
|
* audit's same-selector shadow check fires on this very pair */
|
|
.cbi-dropdown > .open,
|
|
.cbi-dropdown > .more {
|
|
flex-grow: 0;
|
|
flex-shrink: 0;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
text-align: center;
|
|
}
|
|
/* margin only: base also gave the button's chevron a .5em padding and a border-left divider,
|
|
* but both were DEAD on arrival — the theme's `.open` rule below zeroes padding and border in
|
|
* a later layer. A dead declaration is deleted, not relocated. */
|
|
.cbi-dropdown.btn > .open,
|
|
.cbi-dropdown.cbi-button > .open {
|
|
margin-inline-start: .5em;
|
|
}
|
|
/* display only: the placeholder's ink is the theme's own `li[placeholder]` --fs-faint below
|
|
* (base's --fs-dim had already lost to it by layer — moving it would recolour the row) */
|
|
.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[placeholder] {
|
|
display: none;
|
|
}
|
|
.cbi-dropdown > ul > li {
|
|
display: none;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
flex-shrink: 1;
|
|
flex-grow: 1;
|
|
align-items: center;
|
|
align-self: center;
|
|
color: inherit;
|
|
}
|
|
.cbi-dropdown > ul > li .hide-open { display: initial; }
|
|
.cbi-dropdown > ul > li .hide-close { display: none; }
|
|
/* the chip divider on a BUTTON dropdown's preview; the form dropdown zeroes it below */
|
|
.cbi-dropdown > ul > li[display]:not([display="0"]) {
|
|
border-inline-start: var(--fs-hairline);
|
|
}
|
|
.cbi-dropdown[empty] > ul {
|
|
max-width: 1px;
|
|
}
|
|
.cbi-dropdown > ul > li > form {
|
|
display: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
pointer-events: none;
|
|
}
|
|
.cbi-dropdown > ul > li img {
|
|
vertical-align: middle;
|
|
margin-inline-end: .25em;
|
|
}
|
|
.cbi-dropdown > ul > li > form > input[type="checkbox"] {
|
|
margin: 0 var(--fs-space-1) 0 0;
|
|
}
|
|
|
|
/* ---- the display state machine, on specificity ----
|
|
* Every shower out-specifies the hider it overrides: li[display] (0,2,2) over the li hider
|
|
* (0,1,2); [open] > ul.dropdown > li (0,3,2) likewise; the placeholder rules (0,5,2 and 0,6,2)
|
|
* over the placeholder hider (0,4,2); the form rule (0,4,3) over its hider (0,1,3). [empty]'s
|
|
* block (0,2,2) loses to [open]'s flex (0,3,2) deliberately — an open empty list still lays out
|
|
* as a popup — which is the resolution the old flags produced among themselves. */
|
|
.cbi-dropdown > ul > li[display],
|
|
.cbi-dropdown[open] > ul.preview,
|
|
.cbi-dropdown[open] > ul.dropdown > li,
|
|
.cbi-dropdown[multiple] > ul > li > label,
|
|
.cbi-dropdown[multiple][open] > ul.dropdown > li,
|
|
.cbi-dropdown[multiple][more] > .more,
|
|
.cbi-dropdown[multiple][empty] > .more {
|
|
flex-grow: 1;
|
|
display: flex;
|
|
}
|
|
/* The one state the old flags actually carried: a closed optional single dropdown with an empty
|
|
* value. ui.js hands the [display] attribute to the placeholder row itself, and the placeholder
|
|
* hider (0,4,2) out-specifies the generic li[display] shower (0,2,2), so unflagged the closed
|
|
* control renders EMPTY. `:not([open])` keeps this out of the open list, where the
|
|
* [optional][open] rule below owns the row. */
|
|
.cbi-dropdown:not([open]):not(.btn):not(.cbi-button) > ul > li[placeholder][display] {
|
|
flex-grow: 1;
|
|
display: flex;
|
|
}
|
|
.cbi-dropdown[empty] > ul > li,
|
|
.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder] {
|
|
display: block;
|
|
}
|
|
.cbi-dropdown[multiple][open] > ul.dropdown > li > form {
|
|
display: flex;
|
|
}
|
|
.cbi-dropdown[open] > ul.dropdown > li .hide-open { display: none; }
|
|
/* `min-width` because the thing that overflows an open row is a CHILD with an inline width, which
|
|
* wrapping the row cannot reach: form.js's RichListValue writes the description block with an
|
|
* inline `min-width:25vw`, assuming the list grows to fit — true on a theme that leaves
|
|
* ul.dropdown unbounded, false here (capped at min(92vw, 420px), 90-responsive.css). The inline
|
|
* floor is a VIEWPORT fraction, so the wider the screen the worse it gets: at 2559px, 25vw is
|
|
* 640px inside a 406px row and the row's own `overflow: hidden` cut the sentence mid-word
|
|
* (issue #15). An author !important is the only thing that outranks an inline declaration. */
|
|
.cbi-dropdown[open] > ul.dropdown > li .hide-close { display: initial; min-width: 0 !important; }
|
|
.cbi-dropdown[open] > ul.dropdown > li:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
.cbi-dropdown[open] > ul.dropdown > li[unselectable] {
|
|
opacity: .7;
|
|
}
|
|
|
|
.cbi-dropdown:not(.btn):not(.cbi-button) {
|
|
background: var(--fs-panel2);
|
|
border: var(--fs-hairline);
|
|
border-radius: var(--fs-radius);
|
|
color: var(--fs-text);
|
|
min-height: var(--fs-ctl-h);
|
|
/* absorbed from base's generic field box with the width below. The fixed height is
|
|
* load-bearing next to the min-height: without it a closed dropdown grows to its
|
|
* tallest child (the Save & Apply split control carries a 40px button — measured,
|
|
* 38 -> 42px on the gallery). */
|
|
height: var(--fs-control-h);
|
|
font-size: var(--fs-type);
|
|
transition: var(--fs-field-transition);
|
|
/* Width absorbed from base/30-forms.css, where it was grouped with .cbi-dynlist's — so the
|
|
* theme sized the dynlist and base still sized the dropdown, two halves of the same
|
|
* decision in two layers. Same numbers, and they are NOT the dynlist's (240/440): a
|
|
* dropdown holds one value, a dynlist a column of them. */
|
|
min-width: 210px;
|
|
max-width: 400px;
|
|
width: auto;
|
|
/* The widget's own box. In base these two carried `!important` — not to beat an inline
|
|
* style, but to beat base's OWN generic form-field rule, which sets `display: inline-block`
|
|
* and `padding: 4px` on this very selector at a higher specificity (0,3,0 vs 0,1,0). A later
|
|
* LAYER beats it for free, which is exactly what the layer split is for. */
|
|
display: inline-flex;
|
|
padding: 0;
|
|
}
|
|
/* …and the button dropdown (the Save & Apply split control), where the flag was beating the
|
|
* theme's own `.cbi-button` padding. Specificity does it: 0,2,0 over 0,1,0. */
|
|
.cbi-dropdown.btn, .cbi-dropdown.cbi-button { display: inline-flex; padding: 0; }
|
|
.cbi-dropdown:not(.btn):not(.cbi-button):hover { border-color: var(--fs-dim); }
|
|
.cbi-dropdown:focus, .cbi-dropdown[open] {
|
|
border-color: var(--fs-accent);
|
|
box-shadow: var(--fs-focus-ring);
|
|
outline: none;
|
|
}
|
|
/* `.cbi-dropdown > ul.dropdown > li` is the same menu row on a BUTTON dropdown (the Save & Apply
|
|
* split control), which the `:not(.btn):not(.cbi-button)` guard excludes — so that menu kept
|
|
* base's .25em rows while every other dropdown got these. One menu, one row. */
|
|
.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li,
|
|
.cbi-dropdown > ul.dropdown > li {
|
|
padding: var(--fs-space-2) var(--fs-space-3); color: var(--fs-text);
|
|
font-family: var(--fs-font-sans);
|
|
/* one menu row: a grid step, which is what a line of text at this scale rounds to. NOT
|
|
* `calc(--fs-type * --fs-leading)` — that is 19.5px, and a half-pixel on a BOX edge is
|
|
* the thing the scale exists to stop. Absorbed with the machinery above. */
|
|
min-height: var(--fs-space-5);
|
|
}
|
|
/* …and the row's CONTENTS, which is where that font is otherwise lost. A MULTI-select wraps every
|
|
* item in `<form><label><input type=checkbox> text</label></form>`, and `.cbi-value-field *` in
|
|
* theme/60-inputs.css hands mono to every descendant of a field — matching that form and label
|
|
* directly, while the rule above only reaches the `<li>`. So the same widget draws two fonts
|
|
* depending on one option: a single-select list sans, every multi-select list mono, in the open
|
|
* menu and in the closed control alike.
|
|
*
|
|
* It is a LABEL, not a value — the distinction `<strong>` gets in theme/45-misc.css — and mono
|
|
* spacing is what makes a wordy option list look stretched. `inherit`, not the token again: the
|
|
* row above is the one place the menu's font is chosen. */
|
|
.cbi-dropdown > ul > li > form,
|
|
.cbi-dropdown > ul > li label { font-family: inherit; }
|
|
.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[display]:not([display="0"]) { border-inline-start: 0; }
|
|
.cbi-dropdown > ul > li[placeholder] { color: var(--fs-faint); }
|
|
/* the multiselect's inline filter/create inputs — moved from base with the generic `input`
|
|
* box in theme/60-inputs.css: in base these beat it in-layer, left behind they would lose
|
|
* to it by layer and render as free-standing 210px fields inside the open list */
|
|
.cbi-dropdown > ul > li input[type="text"] { height: var(--fs-space-5); }
|
|
.cbi-dropdown[open] > ul.dropdown > li > input.create-item-input:first-child:last-child { width: 100%; }
|
|
|
|
/* Custom chevron in the toggle (stock text/svg arrow hidden). `.open` is the only hit target that
|
|
* opens the list — on a button dropdown (.cbi-dropdown.btn, e.g. Diagnostics) a click anywhere
|
|
* else RUNS the button. `font-size: 0` collapsed the stock `line-height: 2em`, so the span
|
|
* measured 28x0 and the chevron (an absolute ::after) sat outside its own box: aiming at it hit
|
|
* the button. Hence a real box, chevron centred in it. */
|
|
.cbi-dropdown > .open {
|
|
display: flex;
|
|
font-size: 0; line-height: 0; border: 0;
|
|
position: relative; padding: 0;
|
|
align-self: stretch;
|
|
min-width: var(--fs-btn-size); min-height: var(--fs-ctl-h-sm);
|
|
align-items: center;
|
|
cursor: pointer;
|
|
}
|
|
/* `.more` is the "…" a multi-value dropdown shows when its chips overflow. base wrote it in ONE
|
|
* rule with `.open`; the theme took `.open` for the chevron above and left its twin behind, so
|
|
* the two halves of the same toggle strip sat in different layers. Same geometry, absorbed —
|
|
* and hidden by default here (the [multiple][more]/[multiple][empty] showers reveal it),
|
|
* with base's muted ink. */
|
|
.cbi-dropdown > .more { line-height: 2em; padding: 0 .3em; color: var(--fs-dim); display: none; }
|
|
.cbi-dropdown > .open > * { display: none; }
|
|
.cbi-dropdown > .open::after {
|
|
content: ""; position: absolute; inset: 0; margin: auto;
|
|
width: 7px; height: 7px;
|
|
border-right: 2px solid var(--fs-dim); border-bottom: 2px solid var(--fs-dim);
|
|
transform: translateY(-2px) rotate(45deg); /* optical centre of a "v" */
|
|
}
|
|
/* A BUTTON dropdown draws that chevron on the button's own FILL, where --fs-dim is a grey nobody
|
|
* measured against it — on the accent-filled Save & Apply it reads as a smudge rather than an
|
|
* arrow. currentColor takes whatever ink the variant already set, so there is no second list of
|
|
* variants to keep in sync. The form-control dropdown keeps the muted chevron on purpose: there
|
|
* the arrow is an affordance beside the value, not the content. */
|
|
.cbi-dropdown.btn > .open::after,
|
|
.cbi-dropdown.cbi-button > .open::after { border-color: currentColor; }
|
|
|
|
/* open list -> design popover (this was once stated four times over; these are the values
|
|
* that were winning). */
|
|
.cbi-dropdown[open] { z-index: var(--fs-z-dropdown); }
|
|
.cbi-dropdown[open] > ul.dropdown {
|
|
/* what makes it a popup at all — out of flow, stacked from the --fs-z-* scale;
|
|
* absorbed with the machinery */
|
|
display: block;
|
|
position: absolute;
|
|
z-index: var(--fs-z-dropdown);
|
|
transition: max-height var(--fs-dur-move);
|
|
overflow-y: auto;
|
|
background: var(--fs-panel);
|
|
border: var(--fs-hairline);
|
|
border-radius: var(--fs-radius-lg);
|
|
box-shadow: var(--fs-shadow-pop);
|
|
padding: var(--fs-space-1-5);
|
|
/* the gap that detaches the popover from its control. It was DEAD until the `ul` margin
|
|
* flags above came out (they out-ranked it from a lower specificity), so the list sat 1px
|
|
* off the control — the one value that moved when they went. */
|
|
margin-top: var(--fs-space-1-5);
|
|
}
|
|
.cbi-dropdown[open] > ul.dropdown > li {
|
|
border-radius: var(--fs-radius-sm); padding: var(--fs-space-2) var(--fs-space-3);
|
|
border-inline-start: 0; color: var(--fs-text);
|
|
position: relative;
|
|
/* An OPEN row wraps; only the closed preview ellipsizes. `.cbi-dropdown > ul > li` sets nowrap
|
|
* plus ellipsis, which is right for the collapsed control — one line showing the current
|
|
* value — and wrong for the menu: the list is capped at min(92vw, 420px), so an option
|
|
* carrying a description runs out of box and is cut mid-word, with no ellipsis because the
|
|
* cut falls inside a nested element (issue #15). Raising the cap instead would let one
|
|
* verbose option decide the width of every menu on the page. */
|
|
white-space: normal; overflow-wrap: anywhere;
|
|
/* no border-bottom divider: on a rounded box it curls up at the corners. The straight
|
|
* inset strip below (::after) is the divider, so the hover pill stays rounded. */
|
|
border-bottom: 0;
|
|
}
|
|
.cbi-dropdown[open] > ul.dropdown > li:not(:last-child)::after {
|
|
content: ""; position: absolute; inset-inline: 11px; bottom: 0;
|
|
height: 1px; background: var(--fs-border);
|
|
}
|
|
.cbi-dropdown[open] > ul.dropdown > li:hover,
|
|
.cbi-dropdown[open] > ul.dropdown > li.focus { background: var(--fs-panel2); color: var(--fs-accent); }
|
|
/* After hover on purpose: hovering the selected row must still read as selected. The surface is
|
|
* OPAQUE (--fs-panel2) and the accent is carried by the inset rail, not by a tint of itself:
|
|
* accent text on --fs-accent-soft measures 4.21:1 in dark, under AA — the tint drags the
|
|
* background toward the text and eats its own contrast. axe never saw it, no gallery case
|
|
* rendering an OPEN dropdown with a chosen value. */
|
|
.cbi-dropdown[open] > ul.dropdown > li[selected] {
|
|
/* @mirror selected-row/paint */
|
|
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: var(--fs-row-rail);
|
|
/* @endmirror */
|
|
}
|
|
/* The focused row wears the theme's ring, not the browser's: ui.js focuses a row as it opens the
|
|
* menu, and the UA answers a programmatic focus with `outline: auto` — a system-blue capsule
|
|
* around the top row of every dropdown that already has a value.
|
|
*
|
|
* Inset rather than the token's outer 3px, because the menu clips its own overflow and an outer
|
|
* ring is cut on the first and last rows. The rail rides INSIDE this ring so a focused row that
|
|
* is also selected keeps it — a shorthand cannot merge with the one below, so the two are listed
|
|
* together and `--fs-row-rail` keeps that a reference rather than a third copy.
|
|
*
|
|
* Both selectors carry `[selected]`-level weight on purpose: the selected-row paint above is
|
|
* (0,4,2) and a bare `li:focus-visible` would tie with it, leaving the focus ring to source
|
|
* order. */
|
|
.cbi-dropdown[open] > ul.dropdown > li[selected]:focus-visible,
|
|
.cbi-dropdown[open] > ul.dropdown > li:focus-visible {
|
|
outline: none;
|
|
box-shadow: var(--fs-row-rail), inset 0 0 0 2px var(--fs-accent-soft);
|
|
}
|
|
|
|
/* A MULTI-select row says "chosen" with its CHECKBOX, so it does not also need the rail. The rail
|
|
* is `inset 2px 0 0` on a row with an 8px radius, so it draws as an ARC rather than a bar —
|
|
* fine as the single-select cue it was designed for, one row at a time, and wrong down a column
|
|
* of them, where three chosen rows read as a broken frame. The checkbox is a non-colour cue in
|
|
* its own right, which is what WCAG 2.2 SC 1.4.1 asks of "selected"; a single-select row has
|
|
* none and keeps the rail.
|
|
*
|
|
* Written as an override rather than by narrowing the paint below, because those three
|
|
* declarations are pinned byte-identical to `select option:checked` in theme/60-inputs.css
|
|
* (tools/mirror.mjs) — a native `<select multiple>` option has no checkbox, so the rail is still
|
|
* the only cue there. `:not(:focus-visible)` keeps this off the focused row. */
|
|
.cbi-dropdown[multiple][open] > ul.dropdown > li[selected]:not(:focus-visible) { box-shadow: none; }
|
|
.cbi-dropdown[multiple][open] > ul.dropdown > li[selected]:focus-visible {
|
|
box-shadow: inset 0 0 0 2px var(--fs-accent-soft);
|
|
}
|
|
|
|
.cbi-dropdown[open] > ul.dropdown > li.create-item { border-top: var(--fs-hairline); margin-top: var(--fs-space-1); }
|
|
/* The dropdown's "add item" field is a text input and takes the same radius as every other
|
|
* one (theme/60-inputs.css). It said 8px here, and won or lost on source order. */
|
|
.cbi-dropdown .create-item-input { background: var(--fs-panel2); border: var(--fs-hairline); border-radius: var(--fs-radius); color: var(--fs-text); }
|
|
/* dropdown add-item (e.g. Community Lists) — the .cbi-dropdown styles itself, so the
|
|
* wrapper stays transparent: no double frame. */
|
|
.cbi-dynlist > .add-item:has(> .cbi-dropdown) {
|
|
background: none; border: 0; overflow: visible; box-shadow: none;
|
|
}
|
|
|
|
.cbi-section-table .td .cbi-dropdown,
|
|
.cbi-section-table .td .cbi-input-select,
|
|
.cbi-section-table .td select,
|
|
.cbi-section-table .td input[type="text"] {
|
|
min-width: 0;
|
|
max-width: none;
|
|
width: 100%;
|
|
}
|
|
|
|
/* Below ~960px of available width a multi-dropdown row no longer fits, so each config row cards.
|
|
* Same shape as the fs-dt stack (theme/30-tables.css): label above its cell, a read-only cell
|
|
* half the row, a widget cell the whole width.
|
|
*
|
|
* A @container, not the measurement the data tables use, and it must stay one: these rows are
|
|
* full of widgets that bake in a width from the layout they were rendered in, so un-collapsing
|
|
* one to take a reading CHANGES what is read — the firewall zone table then claimed 1747px
|
|
* where it needs 1190px. Do not "finish the job" by measuring it. */
|
|
@container fs-content (max-width: 960px) {
|
|
.table.cbi-section-table,
|
|
.table.cbi-section-table .tbody,
|
|
.table.cbi-section-table .td { display: block; width: auto; }
|
|
/* Hide the header in BOTH markups — a card repeats every label, so the header is a duplicate that
|
|
* only runs off the right edge. form.js wraps titles and descriptions in a `.thead`, while the
|
|
* Lua CBI that many third-party apps still render through emits a bare
|
|
* `tr.cbi-section-table-titles` with no .thead, which is why hiding only `.thead` left
|
|
* luci-app-openvpn's header on screen. `.tr.table-titles` is deliberately not listed:
|
|
* it comes from L.ui.Table, which never renders a .cbi-section-table, and is hidden by the
|
|
* fs-dt stack in theme/30-tables.css. */
|
|
.table.cbi-section-table .thead,
|
|
.table.cbi-section-table .tr.cbi-section-table-titles,
|
|
.table.cbi-section-table .tr.cbi-section-table-descr { display: none; }
|
|
/* no align-items: flex-start — stretch keeps a pair's cells (and their separators) the
|
|
* same height when one wraps; see the data card's .tr in theme/30-tables.css. */
|
|
.table.cbi-section-table .tr {
|
|
display: flex; flex-wrap: wrap; width: auto;
|
|
border: var(--fs-hairline);
|
|
border-radius: var(--fs-radius-lg);
|
|
background: var(--fs-panel2);
|
|
padding: var(--fs-space-2) var(--fs-space-3-5) var(--fs-space-3);
|
|
margin-bottom: var(--fs-space-3);
|
|
}
|
|
.table.cbi-section-table .tr:hover { background: var(--fs-panel2); }
|
|
/* `.table.cbi-section-table`, not `.cbi-section-table`: the desktop cell padding is written as
|
|
* `.table.cbi-section-table .td` and outranks a class-lighter selector, so a plainer one here
|
|
* silently keeps the desktop padding.
|
|
*
|
|
* margin: 0 is load-bearing. The Lua CBI gives a table CELL the same `.cbi-value-field` class
|
|
* a FORM field carries, and base indents that class by 20px, so a stacked cell measures
|
|
* 50% + 20px, two no longer fit on a line, and each pair-cell wraps to its own. */
|
|
.table.cbi-section-table .td {
|
|
flex: 1 1 100%; min-width: 0; margin: 0;
|
|
padding-block: var(--fs-space-1-5); padding-inline: 0 var(--fs-space-2-5); border: 0; text-align: start;
|
|
/* same as the data card's cells (theme/30-tables.css): a carded cell holds opaque
|
|
* tokens (a real-length IPv6 GUA is 39 chars with no break point — colons are not
|
|
* break opportunities), and without `anywhere` one ran 156px past a 360px viewport
|
|
* on the Interfaces page. overflow-wrap inherits, so the inner spans get it too;
|
|
* white-space beats the `.nowrap` class LuCI puts on some cells. */
|
|
overflow-wrap: anywhere; white-space: normal;
|
|
}
|
|
/* A cell that only READS a value (dummy text, a flag, one button) is short, so two fit on a
|
|
* line; anything with a real widget keeps the full width (the 100% basis above) — a dropdown
|
|
* at half a phone's width is unusable. Both spellings of each widget: `CBI.DummyValue` from
|
|
* form.js, `dvalue` from the Lua CBI's typename. */
|
|
.table.cbi-section-table .td[data-widget="CBI.DummyValue"],
|
|
.table.cbi-section-table .td[data-widget="CBI.FlagValue"],
|
|
.table.cbi-section-table .td[data-widget="CBI.Button"],
|
|
.table.cbi-section-table .td[data-widget="dvalue"],
|
|
.table.cbi-section-table .td[data-widget="fvalue"],
|
|
.table.cbi-section-table .td[data-widget="button"] { flex-basis: 50%; }
|
|
/* PINNED to the data table's copy in theme/30-tables.css: the same card, needed under a CLASS
|
|
* there (.fs-stacked, measured) and under this @container here, and CSS cannot OR the two
|
|
* guards. tools/mirror.mjs holds the copies byte-identical — a duplicate detector goes QUIET
|
|
* once they differ, exactly when it should shout. */
|
|
.table.cbi-section-table .td[data-title]::before {
|
|
/* @mirror table-card/label */
|
|
content: attr(data-title); display: block; margin-bottom: var(--fs-space-1);
|
|
font-size: var(--fs-type-xs); font-weight: var(--fs-eyebrow-weight); letter-spacing: var(--fs-eyebrow-tracking);
|
|
color: var(--fs-eyebrow-color);
|
|
/* @endmirror */
|
|
}
|
|
/* The row's buttons are not a label/value pair: full width, each button stretching to
|
|
* share the line (Edit | Delete). PINNED — see above. (These six declarations were the
|
|
* data table's six in a DIFFERENT ORDER, so a grep could not tell they were one rule.) */
|
|
.table.cbi-section-table .td.cbi-section-actions {
|
|
/* @mirror table-card/actions */
|
|
display: flex; flex: 1 1 100%; justify-content: flex-start;
|
|
flex-wrap: wrap; gap: var(--fs-space-2); padding-top: var(--fs-space-2);
|
|
/* @endmirror */
|
|
}
|
|
.table.cbi-section-table .td.cbi-section-actions::before { content: none; }
|
|
/* flex-wrap: LuCI groups the row's buttons in ONE inner <div>, so the td's own wrap
|
|
* (above) never fires — the div is a single flex item. Four buttons are ~330px; on a
|
|
* 320px phone the carded interfaces row overflowed its section by 17px. PINNED to the
|
|
* data table's copy (theme/30-tables.css), which hit the same trap on Startup. */
|
|
.table.cbi-section-table .td.cbi-section-actions > * {
|
|
/* @mirror table-card/actions-inner */
|
|
flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: var(--fs-space-2);
|
|
/* @endmirror */
|
|
}
|
|
.table.cbi-section-table .td .cbi-dropdown,
|
|
.table.cbi-section-table .td select,
|
|
.table.cbi-section-table .td input[type="text"] { width: 100%; min-width: 0; }
|
|
.table.cbi-section-table .td .zone-forwards { width: 100%; }
|
|
}
|
|
|
|
/* ---- widget popups must escape the table card ----
|
|
* The standalone-table card (theme/30-tables.css) clips row backgrounds to its rounded corners
|
|
* with `overflow: hidden`, but a form table also holds .cbi-dropdown widgets whose open list is
|
|
* absolutely positioned — the clip crops it at the table's edge. So drop the clip where a widget
|
|
* lives; the `.cbi-dropdown[open]` z-index above lifts the list over the following cards.
|
|
*
|
|
* Dropping it squares the frame off, though: a real <table> inherits `border-collapse: collapse`,
|
|
* which ignores border-radius — the card's corners came from the clip, not the border. The
|
|
* separated model restores them, and at zero spacing it lays out identically. */
|
|
.table:has(.cbi-dropdown),
|
|
.table:has(.cbi-dynlist) {
|
|
overflow: visible;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
}
|
|
}
|