mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-07 04:34:14 +08:00
382 lines
18 KiB
CSS
382 lines
18 KiB
CSS
@layer theme {
|
|
/* generic .dropdown-menu popup: nothing in luci-base emits it — coverage vocabulary, since
|
|
* stock luci-theme-bootstrap styles it and a third-party app may emit it */
|
|
.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); color: var(--fs-accent); }
|
|
|
|
/* the outer div IS the button, class = ui.js's SELECTED mode; unguarded this erased that
|
|
* variant's fill while its ink survived — measured 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 (theme/55-buttons.css gives the rest theirs); painted by specificity, not source order */
|
|
.cbi-page-actions .cbi-dropdown.cbi-button-apply,
|
|
.cbi-page-actions .cbi-dropdown > .cbi-button {
|
|
background: var(--fs-accent);
|
|
border: 0;
|
|
color: var(--fs-on-accent);
|
|
}
|
|
.cbi-page-actions .cbi-dropdown > .cbi-button:hover { filter: var(--fs-hover-lift); }
|
|
|
|
/* ---- cbi-dropdown widget: the JS select used across all forms ---- */
|
|
/* machinery absorbed from base, driven by ui.js attributes; the state machine below is
|
|
* UNFLAGGED, stating openly what upstream's !important flags resolved — docs/css.md
|
|
* "Dropdown: the display state machine, unflagged" has the specificity table. */
|
|
.cbi-dropdown {
|
|
display: inline-flex;
|
|
cursor: pointer;
|
|
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: hidden;
|
|
display: flex;
|
|
width: 100%;
|
|
}
|
|
/* clears the button's own side inset so the label starts on its text, unflagged (0,3,1) over the rule above's (0,1,1) */
|
|
.cbi-dropdown.btn > ul:not(.dropdown),
|
|
.cbi-dropdown.cbi-button > ul:not(.dropdown) {
|
|
margin-block: 0;
|
|
margin-inline: 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, `.more` is none until the showers flip it — one declaration per state or the audit's same-selector shadow check fires */
|
|
.cbi-dropdown > .open,
|
|
.cbi-dropdown > .more {
|
|
flex-grow: 0;
|
|
flex-shrink: 0;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
text-align: center;
|
|
}
|
|
/* currentColor: the variant's own ink, 4.6-5.4:1 on every button fill measured; --fs-border is 1.45:1 */
|
|
.cbi-dropdown.btn > .open,
|
|
.cbi-dropdown.cbi-button > .open {
|
|
border-inline-start: 1px solid currentColor;
|
|
}
|
|
.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[placeholder] {
|
|
display: none;
|
|
}
|
|
.cbi-dropdown > ul > li {
|
|
display: none;
|
|
font-size: var(--fs-type-field);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
/* ellipsis did nothing until min-width: 0 — a flex item's automatic minimum floors it at its
|
|
* own content width — docs/css.md "Dropdown: ellipsis needs `min-width: 0` first". */
|
|
min-width: 0;
|
|
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; }
|
|
/* a button dropdown's label is a button label, not a form field's 16px value; block, not the
|
|
* state machine's flex, since text-overflow only draws its "…" on a block container */
|
|
.cbi-dropdown:is(.btn, .cbi-button) > ul:not(.dropdown) > li[display] { display: block; font-size: inherit; }
|
|
/* 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-block: 0;
|
|
margin-inline: 0 var(--fs-space-1);
|
|
}
|
|
|
|
/* ---- the display state machine, on specificity (docs/css.md has the per-selector table) ---- */
|
|
/* a closed optional dropdown with an empty value renders EMPTY unflagged too: the placeholder
|
|
* hider out-specifies the generic shower. :not([open]) keeps this out of the open list. */
|
|
.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][more] > .more,
|
|
.cbi-dropdown[multiple][empty] > .more,
|
|
.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: a CHILD with an inline width overflows, wrapping the row cannot reach it —
|
|
* form.js's RichListValue assumes an unbounded list (issue #15). */
|
|
.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);
|
|
/* fixed height: without it a closed dropdown grows to its tallest child (Save & Apply's
|
|
* 40px button, measured 38 -> 42px on the gallery); --fs-ctl-h, since the min-height floor always won */
|
|
height: var(--fs-ctl-h);
|
|
font-size: var(--fs-type);
|
|
transition: var(--fs-field-transition);
|
|
/* absorbed from base/30-forms.css; NOT the dynlist's numbers (240/440) — one value vs a column of them */
|
|
min-width: 13.125rem;
|
|
max-width: 25rem;
|
|
width: auto;
|
|
}
|
|
.cbi-dropdown.btn, .cbi-dropdown.cbi-button { 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 row on a BUTTON dropdown, excluded by
|
|
* :not(.btn):not(.cbi-button) below — one menu, one row, but two selectors. */
|
|
.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);
|
|
/* a menu row is not a button label: the open list sits inside the button and would inherit its 600 and 1.1 line (a plain dropdown's rows: 400, 1.5) */
|
|
font-weight: var(--fs-weight-normal); line-height: var(--fs-leading);
|
|
/* a grid step, not calc(--fs-type * --fs-leading) (19.5px) — a half-pixel box edge is what the scale exists to stop */
|
|
min-height: var(--fs-space-5);
|
|
}
|
|
/* a MULTI-select wraps items in <form><label>…</label>, which theme/60-inputs.css's `.cbi-value-field *` would hand mono — inherit keeps it the row's sans, a LABEL not a value */
|
|
.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; }
|
|
/* same ink base/30-forms.css gives `placeholder`; repeats the row rule's two selectors rather
|
|
* than a shorter form, which LOSES to the row's own color and rendered in the value's ink. */
|
|
.cbi-dropdown:not(.btn):not(.cbi-button) > ul > li[placeholder],
|
|
.cbi-dropdown > ul.dropdown > li[placeholder] { color: var(--fs-placeholder); }
|
|
/* the multiselect's inline filter/create inputs, moved from base with the generic input box (theme/60-inputs.css) */
|
|
.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%; }
|
|
|
|
/* `.open` is the only hit target that opens the list (a click elsewhere on a button dropdown
|
|
* RUNS the button). `font-size: 0` collapsed the stock line-height to 28x0, putting the
|
|
* chevron outside its own box — a real box fixes that. */
|
|
.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` was one base rule with `.open`, split when the theme took `.open` for the chevron */
|
|
.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" */
|
|
}
|
|
/* --fs-dim reads as a smudge on the accent-filled Save & Apply; currentColor uses the variant's own ink */
|
|
.cbi-dropdown.btn > .open::after,
|
|
.cbi-dropdown.cbi-button > .open::after { border-color: currentColor; }
|
|
|
|
/* open list -> design popover */
|
|
.cbi-dropdown[open] { z-index: var(--fs-z-dropdown); }
|
|
.cbi-dropdown[open] > ul.dropdown {
|
|
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);
|
|
margin-top: var(--fs-space-1-5);
|
|
/* anchored to its widget: ui.js's touch branch assumes the viewport as containing block; !important beats the inline style */
|
|
inset-inline: 0 auto !important;
|
|
min-width: 100%;
|
|
/* desktop only: min(92vw,420px) ignores trigger's left offset, grew past viewport under 767px (113 findings) */
|
|
width: max-content;
|
|
max-width: min(92vw, 26.25rem);
|
|
}
|
|
.cbi-dropdown[open] > ul.dropdown > li {
|
|
border-radius: var(--fs-radius-sm);
|
|
border-inline-start: 0;
|
|
position: relative;
|
|
/* an OPEN row wraps; only the closed preview ellipsizes — the menu is capped at
|
|
* min(92vw, 420px), so a verbose option would otherwise decide the width of every menu */
|
|
white-space: normal; overflow-wrap: anywhere;
|
|
/* no border-bottom: it curls at the corners of a rounded box; the ::after inset strip below is the divider instead */
|
|
border-bottom: 0;
|
|
}
|
|
/* The row's own wrap above never reaches a `.zonebadge` (base/95-luci.css): `white-space: nowrap`
|
|
* there, right for a one-line pill in a table cell, wrong here where a multi-homed interface
|
|
* (RichListValue) nests two `.ifacebadge` chips in one badge with no space between them — an
|
|
* unbreakable 254-258px at 320px (`R` past the 320px viewport), overflowing
|
|
* regardless of the popup's own width. Scoped to the open list only: every other `.zonebadge`
|
|
* keeps `nowrap`. */
|
|
.cbi-dropdown[open] > ul.dropdown > li .zonebadge {
|
|
white-space: normal; overflow-wrap: anywhere;
|
|
}
|
|
.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 — docs/css.md
|
|
* "Dropdown: the open list's rail and the selected-row's contrast". */
|
|
.cbi-dropdown[open] > ul.dropdown > li[selected] {
|
|
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: var(--fs-row-rail);
|
|
}
|
|
/* theme's ring, not the UA's outline:auto; inset, since the menu clips overflow, rail rides
|
|
* INSIDE it so a focused+selected row keeps both */
|
|
.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);
|
|
}
|
|
|
|
/* MULTI-select shows "chosen" via its CHECKBOX, drops the rail; the same declarations as
|
|
* `select option:checked` in theme/60-inputs.css, which has no checkbox (kept identical by hand) */
|
|
.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); }
|
|
/* dropdown add-item (e.g. Community Lists) — the .cbi-dropdown styles itself, 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:not(.btn):not(.cbi-button),
|
|
.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 a multi-dropdown row no longer fits, so each row cards, same shape as the
|
|
* data-table stack (theme/30-tables.css); a @container, not the tables' own measurement, since
|
|
* un-collapsing a row to measure it CHANGES what its widgets read. 992, not 960 — docs/css.md
|
|
* "Dropdown: 992px, not 960" has the padding subtraction and the regression it fixed. */
|
|
@container fs-content (max-width: 62rem) {
|
|
.table.cbi-section-table,
|
|
.table.cbi-section-table .thead,
|
|
.table.cbi-section-table .tbody,
|
|
.table.cbi-section-table .td { display: block; width: auto; }
|
|
/* a card repeats every label, so the header goes in BOTH markups (form.js's .thead children,
|
|
* the Lua CBI's bare titles/descr rows) — except the opt-in Table Filters row (25.12 tags it
|
|
* -titles too): no card repeats it, and hidden its 4 gallery controls rendered 0x0 */
|
|
.table.cbi-section-table .thead > :not(.cbi-section-table-filter),
|
|
.table.cbi-section-table .tr.cbi-section-table-titles:not(.cbi-section-table-filter),
|
|
.table.cbi-section-table .tr.cbi-section-table-descr { display: none; }
|
|
/* stretch (not flex-start) keeps a pair's cells the same height when one wraps */
|
|
.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);
|
|
}
|
|
/* margin: 0 load-bearing: the Lua CBI gives a table CELL the .cbi-value-field class a form
|
|
* field carries, base indents it 20px — without the reset two cells no longer fit a line */
|
|
.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;
|
|
/* overflow-wrap: anywhere for opaque tokens (a real IPv6 GUA has no break point) — one
|
|
* ran 156px past a 360px viewport before this */
|
|
overflow-wrap: anywhere; white-space: normal;
|
|
}
|
|
/* the filter cells are .th, so the .td rule above never reaches them: one field per card
|
|
* line, and no row divider (.cbi-section .table .th's border-bottom) between fields */
|
|
.table.cbi-section-table .tr.cbi-section-table-filter > .th {
|
|
flex: 1 1 100%; min-width: 0; border: 0;
|
|
padding-block: var(--fs-space-1-5); padding-inline: 0 var(--fs-space-2-5);
|
|
}
|
|
/* a cell that only READS a value is short, two fit a line; a real widget keeps full width (a dropdown at half a phone's width is unusable) */
|
|
.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%; }
|
|
/* a copy of the data table's rule (theme/30-tables.css), needed under a CLASS there and
|
|
* under this @container here, and CSS cannot OR the two guards. Keep them identical by hand. */
|
|
.table.cbi-section-table .td[data-title]::before {
|
|
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);
|
|
}
|
|
/* the row's buttons are not a label/value pair: full width, sharing the line. Copy in theme/30-tables.css. */
|
|
.table.cbi-section-table .td.cbi-section-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);
|
|
}
|
|
.table.cbi-section-table .td.cbi-section-actions::before { content: none; }
|
|
/* LuCI groups the row's buttons in ONE inner div, so the td's own wrap never fires — the
|
|
* div is a single flex item. No grow: sized to its label like the ComboButton below.
|
|
* Copy in theme/30-tables.css; same trap on Startup. */
|
|
.table.cbi-section-table .td.cbi-section-actions > :not(.cbi-dropdown) {
|
|
flex: 0 1 auto; display: flex; flex-wrap: wrap; gap: var(--fs-space-2);
|
|
}
|
|
/* width: 0 — the label's nowrap min-content (288px for a Russian item at 360px) sizes the table-row-group
|
|
* past the card (td right 442 vs 315); basis `content` still sizes the item to its label (it was 266px, a whole card, with `1 1 auto`). Copy in theme/30-tables.css. */
|
|
.table.cbi-section-table .td.cbi-section-actions > .cbi-dropdown {
|
|
flex: 0 1 content; width: 0; min-width: 0;
|
|
}
|
|
.table.cbi-section-table .td .zone-forwards { width: 100%; }
|
|
}
|
|
|
|
/* the table card (theme/30-tables.css) clips with overflow: hidden, but a .cbi-dropdown's open
|
|
* list is absolutely positioned — drop the clip, restore corners via separated borders instead */
|
|
.table:has(.cbi-dropdown),
|
|
.table:has(.cbi-dynlist) {
|
|
overflow: visible;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
}
|
|
}
|