Comment cleanup
This commit is contained in:
parent
c6cbc8fe70
commit
f18276ab01
7 changed files with 336 additions and 97 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# yksa-web-kit
|
||||
|
||||
Common UI modules and packages (navbar, chrome, vendored libs). Should be used across all new projects in YKSA TM/TC family.
|
||||
Common UI: navbar, page chrome, and vendored front-end libs, shared across the
|
||||
YKSA TM/TC services.
|
||||
|
||||
## Install
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def test_the_navbar_and_footer_are_on_every_page():
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_the_service_cannot_reorder_the_chrome():
|
||||
"""navbar, then main, then footer — a service that wants otherwise has to
|
||||
"""navbar, then main, then footer: a service that wants otherwise has to
|
||||
stop extending the kit, which is the point."""
|
||||
html = Client().get("/privacy/").content.decode()
|
||||
assert html.index("<nav") < html.index("<main") < html.index("<footer")
|
||||
|
|
@ -94,6 +94,24 @@ def test_page_title_links_its_parent():
|
|||
assert "W-1" in html
|
||||
|
||||
|
||||
def test_page_title_walks_the_whole_trail_when_given_one():
|
||||
"""A page two levels down names its grandparent, so the trail reaches the
|
||||
section root instead of starting halfway along it."""
|
||||
html = render_to_string("yksa/ui/_page_title.html", {
|
||||
"title": "Status", "parent": "W-1", "parent_url": "/widgets/w-1/",
|
||||
"grandparent": "Widgets", "grandparent_url": "/widgets/",
|
||||
})
|
||||
assert html.index("/widgets/\"") < html.index("/widgets/w-1/")
|
||||
assert html.count("<a ") == 2
|
||||
|
||||
|
||||
def test_page_title_without_a_grandparent_is_unchanged():
|
||||
html = render_to_string("yksa/ui/_page_title.html", {
|
||||
"title": "W-1", "parent": "Widgets", "parent_url": "/widgets/",
|
||||
})
|
||||
assert html.count("<a ") == 1
|
||||
|
||||
|
||||
# --- the estate-wide rules --------------------------------------------------
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* =============================================================================
|
||||
YKSA UI kit. Ships with yksa-web-kit; see ecosystem/ui-kit.md.
|
||||
YKSA UI kit.
|
||||
|
||||
Requires Bootstrap >= 5.3: the state pills below are built on the
|
||||
--bs-*-bg-subtle / --bs-*-text-emphasis / --bs-*-border-subtle families, and
|
||||
|
|
@ -34,6 +34,23 @@
|
|||
--yksa-z-sticky: 1020;
|
||||
--yksa-z-chrome: 1030;
|
||||
--yksa-z-floating: 1035;
|
||||
|
||||
/* One hover treatment for the whole estate: the surface's ground goes one
|
||||
step darker and nothing else moves. Every hoverable thing -- table rows,
|
||||
list rows, step rows, catalog cards, disclosures -- uses this token, so a
|
||||
page cannot end up with three ways of saying "you are pointing at this".
|
||||
Borders, shadows and colour shifts are all reserved for *state*, which is
|
||||
a property of the record; hover is a property of the pointer. */
|
||||
--yksa-hover-bg: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
/* Bootstrap's own hoverable components, pointed at the same token. */
|
||||
.table {
|
||||
--bs-table-hover-bg: var(--yksa-hover-bg);
|
||||
}
|
||||
|
||||
.list-group {
|
||||
--bs-list-group-action-hover-bg: var(--yksa-hover-bg);
|
||||
}
|
||||
|
||||
/* Bootstrap declares --bs-dropdown-zindex on .dropdown-menu, not on :root, so it
|
||||
|
|
@ -43,10 +60,6 @@
|
|||
--bs-dropdown-zindex: var(--yksa-z-floating);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Navbar and footer -- layout-identical across every service
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
.custom-navbar {
|
||||
height: var(--navbar-height);
|
||||
padding-top: 0;
|
||||
|
|
@ -96,32 +109,66 @@
|
|||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* An open dropdown is as much "the item you are on" as an active one. Without
|
||||
this the toggle dropped back to the resting colours the moment its own menu
|
||||
appeared, which reads as the click having missed. */
|
||||
.nav-full-height.nav-link:hover,
|
||||
.nav-full-height.nav-link.active {
|
||||
.nav-full-height.nav-link.active,
|
||||
.nav-full-height.nav-link.show {
|
||||
color: #fff !important;
|
||||
background-color: var(--bs-primary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
State -- the sanctioned rendering of status (ui/_state.html)
|
||||
-------------------------------------------------------------------------- */
|
||||
/* Muted text inside a highlighted nav item takes the item's colour.
|
||||
|
||||
Bootstrap's text utilities set `color` with `!important`, so a muted span --
|
||||
a countdown, a hint -- kept its resting grey when the item turned solid primary
|
||||
underneath it: grey on blue, the one combination the theme has no contrast for.
|
||||
`inherit` needs `!important` here for the same reason the utility does.
|
||||
|
||||
Deliberately only the muted utility, not every descendant. A badge and a state
|
||||
pill are separate surfaces carrying their own background, and flattening them
|
||||
to the parent's colour would erase what they are for. */
|
||||
.nav-full-height.nav-link:hover .text-body-secondary,
|
||||
.nav-full-height.nav-link.active .text-body-secondary,
|
||||
.nav-full-height.nav-link.show .text-body-secondary,
|
||||
/* Same problem, same fix, one row down: a dropdown item with a muted hint or
|
||||
sub-line under its label -- the outcome menu on the pass console, the duty
|
||||
menu in the header -- highlights on hover, focus and keyboard selection, and
|
||||
the muted line stayed grey against it. Any menu that explains its options has
|
||||
this shape, so the rule belongs on `.dropdown-item` rather than on each one. */
|
||||
.dropdown-item:hover .text-body-secondary,
|
||||
.dropdown-item:focus .text-body-secondary,
|
||||
.dropdown-item:active .text-body-secondary,
|
||||
.dropdown-item.active .text-body-secondary {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* `inline-block`, not `inline-flex`, and this is the whole of the table-alignment
|
||||
fix. An inline-flex box's baseline is its last flex item's baseline, and its
|
||||
padding and border hang *below* that -- so in a table cell the pill sits low,
|
||||
and no amount of `vertical-align` corrects it without also fighting the line
|
||||
height it inherits. An inline-block shares the row's baseline with the text
|
||||
beside it and straddles it symmetrically, which is why Bootstrap's own `.badge`
|
||||
has always looked right in a table. The icon is centred by bootstrap-icons'
|
||||
`vertical-align: -.125em`, the same way it is in running text.
|
||||
|
||||
Nothing here sets `line-height`: the pill takes the surface's, so it matches
|
||||
the row it sits in rather than being a second type size. */
|
||||
.yksa-state {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
display: inline-block;
|
||||
font-size: .75rem;
|
||||
font-weight: 700;
|
||||
padding: .05rem .4rem;
|
||||
white-space: nowrap;
|
||||
border: 1px solid transparent;
|
||||
/* An inline-flex box baseline-aligns on its own last line box, which in a
|
||||
table cell or a run of text leaves it sitting a few pixels low. It is a
|
||||
self-contained box; align it on the middle instead. */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* health */
|
||||
.yksa-state > .bi,
|
||||
.yksa-chip > .bi {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
|
||||
.yksa-state-ok,
|
||||
.yksa-state-complete {
|
||||
color: var(--bs-success-text-emphasis);
|
||||
|
|
@ -157,23 +204,51 @@
|
|||
border-color: var(--bs-border-color);
|
||||
}
|
||||
|
||||
/* Neutral metadata chip. Not a coloured badge, because it carries no status. */
|
||||
/* Neutral metadata chip. Not a coloured badge, because it carries no status.
|
||||
Same box model as .yksa-state, for the same reason. */
|
||||
.yksa-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
display: inline-block;
|
||||
font-size: .75rem;
|
||||
padding: 0 .35rem;
|
||||
white-space: nowrap;
|
||||
color: var(--bs-secondary-color);
|
||||
background: var(--bs-tertiary-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Typography helpers
|
||||
-------------------------------------------------------------------------- */
|
||||
/* A borderless icon button for dense rows: a table row's remove control, an
|
||||
overflow `...` toggle, a field's clear. Bordered buttons in those places draw a
|
||||
box around every row and the boxes become the pattern the eye follows.
|
||||
|
||||
This exists because the alternative was being spelled out by hand, differently,
|
||||
in five templates -- `btn btn-sm btn-link p-0 text-body-secondary`, sometimes
|
||||
with `border-0`, sometimes with `px-1`, once with an `fs-5` on the glyph. It is
|
||||
a button, not a link: `.btn-link` is reserved for navigation, and a control
|
||||
styled as a link but wired to a POST invites a middle-click that silently
|
||||
does nothing. */
|
||||
.yksa-btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.75rem;
|
||||
min-height: 1.75rem;
|
||||
padding: 0 .25rem;
|
||||
color: var(--bs-secondary-color);
|
||||
background: none;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.yksa-btn-icon:hover,
|
||||
.yksa-btn-icon:focus-visible {
|
||||
color: var(--bs-body-color);
|
||||
background: var(--yksa-hover-bg);
|
||||
}
|
||||
|
||||
.yksa-btn-icon.is-danger:hover,
|
||||
.yksa-btn-icon.is-danger:focus-visible {
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
.yksa-label {
|
||||
font-size: .75rem;
|
||||
|
|
@ -207,6 +282,36 @@
|
|||
margin-inline: 0.3rem;
|
||||
}
|
||||
|
||||
/* A run of metadata separated by rules. Use this rather than dropping bare
|
||||
`.yksa-sep`s between text nodes.
|
||||
|
||||
The margins above are symmetric, but the *source* around them is not: a
|
||||
template that puts the separator at the start of a line contributes a collapsed
|
||||
space on its left and none on its right, so the rule sits visibly off-centre
|
||||
between the two items it divides. Nobody sees that while writing the template
|
||||
and everybody sees it on the page.
|
||||
|
||||
A flex container fixes it at the root: whitespace-only text nodes never become
|
||||
flex items, so indentation stops existing, and the gap is the only spacing.
|
||||
Each item goes in its own element -- an unwrapped text node *would* become an
|
||||
anonymous flex item, and then the whole run is one item and the gap does
|
||||
nothing. That is the reason for the spans. */
|
||||
.yksa-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0 .5rem;
|
||||
}
|
||||
|
||||
.yksa-meta-tight {
|
||||
gap: 0 .3rem;
|
||||
}
|
||||
|
||||
/* Inside the run the gap does the spacing; the rule's own margins would double it. */
|
||||
.yksa-meta > .yksa-sep {
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
/* Tabular numerals in the body face. Monospace is reserved for verbatim machine
|
||||
text -- hex dumps, TLE lines, command mnemonics. */
|
||||
.yksa-num {
|
||||
|
|
@ -228,10 +333,6 @@
|
|||
font-size: .8em;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Containers
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
/* A container that supplies its own padding must not also inherit the trailing
|
||||
margin of whatever it ends with. Bootstrap does this for `p` in a few places
|
||||
and not at all for `dl`, `ul` or `table`, so the fix was being applied by hand
|
||||
|
|
@ -248,16 +349,20 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Alerts
|
||||
/* A definition list ends with a `dd`, which carries a bottom margin of its own.
|
||||
`mb-0` on the `dl` does not reach it, so the gap survives the very utility
|
||||
applied to remove it -- which is how several fact lists ended up looking
|
||||
bottom-heavy inside an otherwise even card. */
|
||||
dl:last-child > dd:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
The estate theme paints alerts in solid colour, not the tinted panel Bootstrap
|
||||
ships by default. That changes what belongs inside one: a utility text colour
|
||||
is computed against the page ground and is unreadable on a saturated one, and
|
||||
an outline button drawn in `--bs-secondary` all but vanishes.
|
||||
-------------------------------------------------------------------------- */
|
||||
/* Alerts here paint in solid colour, not Bootstrap's default tinted panel: a
|
||||
utility text colour computed against the page ground is unreadable on a
|
||||
saturated one, and an outline button drawn in `--bs-secondary` all but
|
||||
vanishes -- hence the overrides below.
|
||||
|
||||
/* Muted text inside an alert is the alert's own colour, softened. All three of
|
||||
Muted text inside an alert is the alert's own colour, softened. All three of
|
||||
these resolve to --bs-secondary-color, which is computed against the page. */
|
||||
.alert .text-body-secondary,
|
||||
.alert .yksa-label,
|
||||
|
|
@ -269,26 +374,51 @@
|
|||
/* Outline buttons take their colour from the alert rather than from the palette,
|
||||
and invert to the alert's ground on hover. The semantic distinction between
|
||||
`-secondary` and `-danger` is lost here, which is correct: the alert already
|
||||
says which of those it is, and a red outline on a red ground says nothing. */
|
||||
says which of those it is, and a red outline on a red ground says nothing.
|
||||
|
||||
Every value comes from `--bs-alert-color` / `--bs-alert-bg`, which the alert
|
||||
variant declares and the button inherits, so one rule covers all six variants
|
||||
in both text directions. Not `currentColor`: inside `:hover` that resolves to
|
||||
the *hover* colour, so a hover background of `currentColor` paints the button
|
||||
the colour it is about to be and the hover state disappears. */
|
||||
.alert .btn-outline-primary,
|
||||
.alert .btn-outline-secondary,
|
||||
.alert .btn-outline-success,
|
||||
.alert .btn-outline-danger,
|
||||
.alert .btn-outline-warning {
|
||||
--bs-btn-color: inherit;
|
||||
--bs-btn-border-color: currentColor;
|
||||
--bs-btn-color: var(--bs-alert-color);
|
||||
--bs-btn-border-color: var(--bs-alert-color);
|
||||
--bs-btn-hover-color: var(--bs-alert-bg);
|
||||
--bs-btn-hover-bg: currentColor;
|
||||
--bs-btn-hover-border-color: currentColor;
|
||||
--bs-btn-hover-bg: var(--bs-alert-color);
|
||||
--bs-btn-hover-border-color: var(--bs-alert-color);
|
||||
--bs-btn-active-color: var(--bs-alert-bg);
|
||||
--bs-btn-active-bg: currentColor;
|
||||
--bs-btn-active-border-color: currentColor;
|
||||
--bs-btn-active-bg: var(--bs-alert-color);
|
||||
--bs-btn-active-border-color: var(--bs-alert-color);
|
||||
--bs-btn-disabled-color: var(--bs-alert-color);
|
||||
--bs-btn-disabled-border-color: var(--bs-alert-color);
|
||||
--bs-btn-focus-shadow-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Layout
|
||||
-------------------------------------------------------------------------- */
|
||||
/* A filled button on a solid alert is the alert's own colour inverted -- the same
|
||||
pair, the other way round. `btn-light` was doing this by hand and only happened
|
||||
to be right on the four dark variants. */
|
||||
.alert .btn-contrast {
|
||||
--bs-btn-color: var(--bs-alert-bg);
|
||||
--bs-btn-bg: var(--bs-alert-color);
|
||||
--bs-btn-border-color: var(--bs-alert-color);
|
||||
--bs-btn-hover-color: var(--bs-alert-bg);
|
||||
--bs-btn-hover-bg: var(--bs-alert-color);
|
||||
--bs-btn-hover-border-color: var(--bs-alert-color);
|
||||
--bs-btn-active-color: var(--bs-alert-bg);
|
||||
--bs-btn-active-bg: var(--bs-alert-color);
|
||||
--bs-btn-active-border-color: var(--bs-alert-color);
|
||||
--bs-btn-focus-shadow-rgb: 255, 255, 255;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.alert .btn-contrast:hover {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
/* A record in a list is a row with a left status marker, not a card. */
|
||||
.yksa-row {
|
||||
|
|
@ -300,39 +430,107 @@
|
|||
}
|
||||
|
||||
.yksa-row:hover {
|
||||
background: var(--bs-tertiary-bg);
|
||||
background: var(--yksa-hover-bg);
|
||||
}
|
||||
|
||||
.yksa-row.is-in-progress { border-left-color: var(--bs-primary); }
|
||||
.yksa-row.is-complete { border-left-color: var(--bs-success); }
|
||||
.yksa-row.is-pending { border-left-color: var(--bs-warning); }
|
||||
.yksa-row.is-failed { border-left-color: var(--bs-danger); }
|
||||
/* Withdrawn/skipped: still a record, but nothing here is work. The border
|
||||
stays the resting grey and the row's text steps back, which is the same
|
||||
thing `skipped` says in the state vocabulary. */
|
||||
.yksa-row.is-skipped { color: var(--bs-secondary-color); }
|
||||
|
||||
/* The one saturated element allowed on a surface: the active shift, the pass in
|
||||
contact, the source currently polling. Two on a page means one is wrong. */
|
||||
.yksa-row.is-active {
|
||||
border-left-color: var(--bs-success);
|
||||
background: var(--bs-success-bg-subtle);
|
||||
/* A table row that *is* a record: the row is the link to it. Sanctioned where a
|
||||
card's equivalent is not -- a row is one target, while a card is a container
|
||||
of several. `.stretched-link` goes on the cell that names the record; this
|
||||
class is only here to give that anchor something to stretch against.
|
||||
|
||||
Sibling links in the row are lifted above it, the same lift `.card-footer`
|
||||
needed and for the same reason: the stretched anchor covers the whole row, so
|
||||
without this a press on the station's own link silently opens the record
|
||||
instead. A row with no sibling links needs no lift and pays nothing for it. */
|
||||
.yksa-row-link {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Catalog card: a card summarising one record, where the whole card is the link
|
||||
to it. The hover treatment is defined once, here, because three services had a
|
||||
grid of these and none of them said what hovering meant -- the cursor changed
|
||||
over the title only, and the padding around it was dead. The card carries this
|
||||
class and the title anchor carries .stretched-link. */
|
||||
.yksa-row-link a:not(.stretched-link) {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* The one emphasised element allowed on a surface: the active shift, the pass in
|
||||
contact, the source currently polling. Two on a page means one is wrong.
|
||||
|
||||
It is a heavier border in the accent colour, not a tint. A row already carries
|
||||
a coloured left border and usually a state pill; adding a filled ground makes
|
||||
three ways of saying one thing, and the strongest of the three drowns the row's
|
||||
own content. Emphasis is the *weight* of the existing signal, not a new one. */
|
||||
.yksa-row.is-active {
|
||||
border-left-color: var(--bs-success);
|
||||
border-left-width: 6px;
|
||||
padding-left: calc(.5rem - 2px);
|
||||
}
|
||||
|
||||
/* Catalog card: a card summarising one record. The card is a container, not a
|
||||
control: its title is the link and nothing else in it moves under the pointer.
|
||||
Two earlier versions made the whole card hot -- one recoloured the border and
|
||||
title, one painted the hover ground -- and both put a card-sized reaction
|
||||
behind a card-sized target that also held its own footer buttons, so the page
|
||||
read as a grid of giant buttons and a press near a footer control was a coin
|
||||
toss. Hover is reserved for rows, which are one target each.
|
||||
|
||||
`position: relative` stays so a card can still host absolutely-positioned
|
||||
corner chrome; it no longer backs a .stretched-link. */
|
||||
.yksa-card-link {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.yksa-card-link:hover {
|
||||
border-color: var(--bs-primary);
|
||||
background: var(--bs-tertiary-bg);
|
||||
/* The title is a link and says so in the link colour, the way odms has always
|
||||
drawn it. `.text-decoration-none` on the anchor suppresses the underline at
|
||||
rest so a grid of forty titles is not forty underlines; hover brings it back,
|
||||
which is this card's one hover affordance. Both rules have to out-specify that
|
||||
utility, hence the element-qualified selectors. */
|
||||
.yksa-card-link .card-title a,
|
||||
.yksa-card-link h2 a,
|
||||
.yksa-card-link h3 a {
|
||||
color: var(--bs-link-color);
|
||||
}
|
||||
|
||||
/* The title is already the link; underlining it on hover would be the only
|
||||
moving part in a grid of forty. The border carries the affordance instead. */
|
||||
.yksa-card-link:hover .stretched-link {
|
||||
color: var(--bs-primary);
|
||||
.yksa-card-link .card-title a:hover,
|
||||
.yksa-card-link h2 a:hover,
|
||||
.yksa-card-link h3 a:hover,
|
||||
.yksa-card-link .card-title a:focus-visible,
|
||||
.yksa-card-link h2 a:focus-visible,
|
||||
.yksa-card-link h3 a:focus-visible {
|
||||
color: var(--bs-link-hover-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Row chrome that appears under the pointer: the retract on one entry of a long
|
||||
append-only stream, where forty always-visible trash icons would be the loudest
|
||||
thing on the page. It keeps its box at rest, so revealing it never reflows the
|
||||
row. Focus counts as pointing -- without that clause the control exists only
|
||||
for mouse users. */
|
||||
.yksa-reveal {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.yksa-reveal-host:hover .yksa-reveal,
|
||||
.yksa-reveal:focus-visible,
|
||||
.yksa-reveal-host:focus-within .yksa-reveal {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* The fact grid inside a catalog card: two columns of `.yksa-label` + figure.
|
||||
It was `.meta-col` in odms.css, defined against `.sat-row-card`, which is why
|
||||
ops built its own with a `dl.row` instead and got a different rhythm and a
|
||||
stray bottom margin. One card, one grid. */
|
||||
.yksa-card-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: .25rem .75rem;
|
||||
}
|
||||
|
||||
/* Empty state: a sentence plus, where there is one, the action that resolves
|
||||
|
|
@ -369,10 +567,6 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Cookie banner
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
.cookie-banner {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
|
|
@ -387,10 +581,6 @@
|
|||
color: #bfdbfe;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Responsive
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
@media (max-width: 991px) {
|
||||
/* Collapsed, the nav is a stacked list; the full-height tab treatment would
|
||||
put a border around every row. */
|
||||
|
|
@ -407,33 +597,40 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Chip filters (yksa/includes/chip_filters.html + src/chip-filters.js)
|
||||
-------------------------------------------------------------------------- */
|
||||
|
||||
/* A filter chip is a toggle, so it looks like one at both ends of its state: an
|
||||
outlined chip at rest, a filled one when it is on. It used to be `badge
|
||||
bg-secondary` at rest, which made an unselected filter a solid grey block --
|
||||
as loud as the selected one and easy to read as already applied. The class is
|
||||
self-contained now; callers need no `badge bg-*` alongside it. */
|
||||
.filter-chip {
|
||||
display: inline-block;
|
||||
font-size: .75rem;
|
||||
line-height: 1.5;
|
||||
padding: .05rem .5rem;
|
||||
color: var(--bs-body-color);
|
||||
background-color: var(--bs-body-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background-color 0.12s ease;
|
||||
}
|
||||
|
||||
.filter-chip:hover {
|
||||
filter: brightness(110%);
|
||||
background-color: var(--yksa-hover-bg);
|
||||
}
|
||||
|
||||
.filter-chip.active {
|
||||
background-color: var(--bs-primary) !important;
|
||||
color: #fff;
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Prose and tables
|
||||
|
||||
Long-form text rendered from the database (policies, descriptions) and the
|
||||
table cells that hold it. Everything else that used to live in main.css was
|
||||
qsl-only and did not come across.
|
||||
-------------------------------------------------------------------------- */
|
||||
.filter-chip.active:hover {
|
||||
background-color: var(--bs-primary);
|
||||
}
|
||||
|
||||
/* Long-form text rendered from the database (policies, descriptions) and the
|
||||
table cells that hold it. */
|
||||
.text-content {
|
||||
overflow: auto;
|
||||
hyphens: auto;
|
||||
|
|
@ -459,6 +656,10 @@ td ul:last-child {
|
|||
--bs-alert-border-color: #c42526;
|
||||
}
|
||||
|
||||
/* A quiet border for grouped buttons that sit inside another bordered surface --
|
||||
a catalog card's footer -- where the button group's own outline would read as a
|
||||
second box around the first. `--bs-border-color` rather than a black alpha: the
|
||||
alpha approximated exactly this colour on white and disappeared on anything else. */
|
||||
.btn-outline-bordered {
|
||||
border-color: rgba(0, 0, 0, 0.066);
|
||||
border-color: var(--bs-border-color);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@
|
|||
active.push(chip.dataset.value);
|
||||
});
|
||||
hidden.value = active.join(",");
|
||||
// Live surfaces (a server-side table, a chart) listen here rather than
|
||||
// waiting for a submit. A hidden input never fires `change` on its own.
|
||||
hidden.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
|
||||
document.querySelectorAll("[data-filter-group]").forEach(function (group) {
|
||||
|
|
@ -49,6 +52,7 @@
|
|||
var chip = event.target.closest(".filter-chip");
|
||||
if (!chip || !group.contains(chip)) return;
|
||||
chip.classList.toggle("active");
|
||||
chip.setAttribute("aria-pressed", chip.classList.contains("active") ? "true" : "false");
|
||||
syncHidden(group);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ The estate skeleton. Every service's own base.html extends this and fills the
|
|||
blocks; nothing here is meant to be copied into a service.
|
||||
|
||||
The navbar is written out here rather than {% include %}d because blocks inside
|
||||
an included template cannot be overridden — and the navbar's layout has to be
|
||||
an included template cannot be overridden, and the navbar's layout has to be
|
||||
identical everywhere while its items differ per service. Same for the footer's
|
||||
position. Fill `navbar_items` and `navbar_tools`; change nothing else.
|
||||
|
||||
|
|
@ -38,7 +38,13 @@ Blocks a service normally fills:
|
|||
<link rel="stylesheet" href="{% static 'yksa/css/kit.css' %}">
|
||||
{% block extra_head %}{% endblock extra_head %}
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100 bg-light">
|
||||
{% comment %}
|
||||
`bg-body-tertiary`, not `bg-light`. `bg-light` is a fixed near-white that does
|
||||
not follow `data-bs-theme`, so it was the one element standing between the
|
||||
estate and a working dark theme: every card and control below it recoloured
|
||||
and the page they sat on did not.
|
||||
{% endcomment %}
|
||||
<body class="d-flex flex-column min-vh-100 bg-body-tertiary">
|
||||
<nav class="navbar navbar-expand-lg custom-navbar border-bottom sticky-top">
|
||||
<div class="{% block navbar_container_class %}container{% endblock navbar_container_class %}">
|
||||
<a class="navbar-brand nav-full-height" href="{% block brand_url %}/{% endblock brand_url %}">
|
||||
|
|
@ -163,7 +169,6 @@ Blocks a service normally fills:
|
|||
{% include "yksa/includes/_footer.html" %}
|
||||
<script src="{% static 'yksa/src/bootstrap.bundle.min.js' %}"></script>
|
||||
<script src="{% static 'yksa/src/cookie-banner.js' %}"></script>
|
||||
{% comment %}Shows the toasts yksa/includes/_messages.html renders.{% endcomment %}
|
||||
<script src="{% static 'yksa/src/toast.js' %}"></script>
|
||||
{% block extra_js %}{% endblock extra_js %}
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,14 @@ Optional context:
|
|||
* empty_message: text shown when ``chips`` is empty (defaults to a generic
|
||||
placeholder via {{ empty_message|default:_("No options yet.") }}).
|
||||
|
||||
Each chip is a real <button>, so it is focusable and answers to Enter/Space; a
|
||||
<span> was reachable by mouse only.
|
||||
|
||||
The accompanying JS (static/src/chip-filters.js) toggles ``.filter-chip.active``
|
||||
on click and re-emits the comma-separated value into the hidden input keyed by
|
||||
``data-filter-csv``. Submitting the form sends the result; servers split on
|
||||
comma. No XHR -- keeps URLs bookmarkable.
|
||||
``data-filter-csv``, then fires `change` on it so a live table can redraw without
|
||||
a submit. Submitting the form sends the result; servers split on comma. No XHR by
|
||||
default -- keeps URLs bookmarkable.
|
||||
{% endcomment %}
|
||||
{% load i18n %}
|
||||
<div class="card mb-3">
|
||||
|
|
@ -29,8 +33,9 @@ comma. No XHR -- keeps URLs bookmarkable.
|
|||
{% if chips %}
|
||||
<div class="d-flex flex-wrap gap-1" data-filter-group="{{ name }}">
|
||||
{% for chip in chips %}
|
||||
<span class="badge bg-secondary filter-chip {% if chip.selected %}active{% endif %}"
|
||||
data-value="{{ chip.value }}">{{ chip.label }}</span>
|
||||
<button type="button" class="filter-chip {% if chip.selected %}active{% endif %}"
|
||||
aria-pressed="{% if chip.selected %}true{% else %}false{% endif %}"
|
||||
data-value="{{ chip.value }}">{{ chip.label }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,14 @@ fixed. Never pick a heading level for its size.
|
|||
{% trans "Widgets" as parent %}{% url 'public:widget-list' as parent_url %}
|
||||
{% include "yksa/ui/_page_title.html" with title=widget.name parent=parent parent_url=parent_url %}
|
||||
|
||||
A page one level deeper names its grandparent too, so the trail reaches the
|
||||
section root rather than starting halfway down it:
|
||||
|
||||
{% include "yksa/ui/_page_title.html" with title=_("Status") parent=widget.name parent_url=... grandparent=_("Widgets") grandparent_url=... %}
|
||||
|
||||
Put it inside the page header's flex row; the row owns the actions on the right.
|
||||
{% endcomment %}
|
||||
<h1 class="h3 mb-0">
|
||||
{% if parent %}<a class="text-decoration-none" href="{{ parent_url }}">{{ parent }}</a> / {% endif %}{{ title }}
|
||||
{% if grandparent %}<a class="text-decoration-none" href="{{ grandparent_url }}">{{ grandparent }}</a> / {% endif %}{% if parent %}<a class="text-decoration-none" href="{{ parent_url }}">{{ parent }}</a> / {% endif %}{{ title }}
|
||||
</h1>
|
||||
{% if subtitle %}<span class="text-body-secondary small">{{ subtitle }}</span>{% endif %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue