ui: nine leaf files off Semantic, and the components they needed

Phase 2, part 2.  Baseline 72 -> 63.

Migrated: Tables, SortableTable, DonatePage, Share, Headline, ErrorBoundary,
and the QR code, VIN decoder, and temperature calculators.

New in the library, all in the gallery and tested:

  SearchBox     Semantic's `<Search category>`, keeping its grouped `results`
                shape because four call sites already build it.  A real
                combobox: arrows move, Enter takes the highlighted suggestion
                or submits the raw text, Escape closes without clearing.
  Pagination    Semantic's prop names, but onPageChange gets the page number
                rather than an (event, data) pair.
  TabBar        the strip and its house style only; routing stays at the call
                site, which is what decides what is active.
  IconStack     two glyphs composed into one symbol, named once for assistive
                technology (Semantic's IconGroup).
  Table.HeaderCell gains `sorted` and `onSort`.

That last one is a fix, not just a port.  Semantic's `sorted` prop set
`aria-sort`; migrating a table by hand dropped it, leaving a clickable <th>
that no keyboard could reach and no screen reader could describe.  Putting it
in the library means the next table gets it right without anyone remembering.

Night mode holes closed along the way: the QR codes on the donate, share, and
calculator pages are white by necessity — a scanner needs the contrast — and
they render as SVG, which the media filter does not target.  In night mode that
was a lit panel.  They now carry `.media`, so the filter remaps the block as a
unit and the contrast survives the mapping.

Also: `paypal` joins the icon map rather than being imported ad hoc in a page,
and FileBrowser stops passing Semantic-only table props into a DOM element.
This commit is contained in:
Roland 2026-07-30 09:31:15 -06:00 • committed by lrnselfreliance
parent efe84d17f1
commit 18dc90b4c2
19 changed files with 1030 additions and 224 deletions

View file

@ -26,6 +26,26 @@
}
}
.wrolpi-icon-stack {
position: relative;
display: inline-flex;
line-height: 0;
}
/*
* The corner glyph sits on the main one, so it carries the surface color behind
* it — without that the two sets of strokes cross and neither reads.
*/
.wrolpi-icon-stack-corner {
position: absolute;
right: -3px;
bottom: -3px;
display: flex;
background: var(--bg);
border-radius: 50%;
padding: 1px;
}
/* ---------------------------------------------------------------- Buttons */
/*
* Dashed borders mean "destructive or failed", and nothing else may use them.
@ -117,6 +137,188 @@ html[data-theme="amber"] .wrolpi-theme-option-active {
border-top: 1px solid var(--border);
}
/* ------------------------------------------------------------ Pagination */
.wrolpi-pagination button {
background: var(--panel);
color: var(--text);
border: 1px solid var(--border);
}
.wrolpi-pagination button[data-active] {
background: var(--blue);
color: var(--btn-text);
border-color: var(--blue);
}
/* Night and amber have no second hue to spend, so the current page is marked by a
heavier border and the panel surface instead of a filled block of color. */
html[data-theme="night"] .wrolpi-pagination button[data-active],
html[data-theme="amber"] .wrolpi-pagination button[data-active] {
background: var(--panel);
color: var(--text);
border: 2px solid var(--text);
}
.wrolpi-pagination button:disabled {
color: var(--muted);
background: var(--bg);
}
/* ---------------------------------------------------------------- Tab bar */
.wrolpi-tabs {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 12px;
border-bottom: 1px solid var(--border);
margin-bottom: 14px;
}
.wrolpi-tabs-links {
display: flex;
flex-wrap: wrap;
}
.wrolpi-tabs-right {
padding-bottom: 6px;
}
.wrolpi-tab {
display: inline-block;
padding: 8px 14px;
color: var(--muted);
text-decoration: none;
font-size: 13px;
border: 1px solid transparent;
/* Sits on the bar's own rule, so the active tab can erase it and read as
connected to the panel below. */
margin-bottom: -1px;
}
.wrolpi-tab:hover {
color: var(--text);
}
.wrolpi-tab-active {
color: var(--text);
background: var(--panel);
border-color: var(--border);
border-bottom-color: var(--panel);
font-weight: 600;
}
.wrolpi-tab:focus-visible {
outline: 2px solid var(--blue);
outline-offset: -2px;
}
/* ------------------------------------------------------------- Search box */
.wrolpi-searchbox {
position: relative;
width: 100%;
}
.wrolpi-searchbox-control {
display: flex;
align-items: center;
gap: 8px;
background: var(--panel);
border: 1px solid var(--border);
padding: 0 8px;
}
.wrolpi-searchbox-control:focus-within {
border-color: var(--blue);
}
html[data-theme="night"] .wrolpi-searchbox-control:focus-within,
html[data-theme="amber"] .wrolpi-searchbox-control:focus-within {
border-color: var(--text);
}
.wrolpi-searchbox-icon {
display: flex;
color: var(--muted);
}
.wrolpi-searchbox-input {
flex: 1;
min-width: 0;
padding: 8px 0;
background: none;
border: none;
outline: none;
color: var(--text);
font: inherit;
font-size: 13px;
}
.wrolpi-searchbox-input::placeholder {
color: var(--muted);
}
.wrolpi-searchbox-loading,
.wrolpi-searchbox-clear {
flex: none;
}
/*
* The suggestion list overlays the page rather than pushing it down — a search
* that reflows the results underneath it is unusable while typing.
*/
.wrolpi-searchbox-results {
position: absolute;
z-index: 20;
top: 100%;
left: 0;
right: 0;
max-height: 320px;
overflow-y: auto;
background: var(--panel);
border: 1px solid var(--border);
border-top: none;
}
.wrolpi-searchbox-group-name {
padding: 6px 10px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
background: var(--head);
}
.wrolpi-searchbox-result {
padding: 7px 10px;
cursor: pointer;
border-top: 1px solid var(--border);
}
.wrolpi-searchbox-result-active {
background: var(--head);
}
.wrolpi-searchbox-result-title {
font-size: 13px;
color: var(--text);
overflow-wrap: anywhere;
}
.wrolpi-searchbox-result-description {
font-size: 11px;
color: var(--muted);
}
.wrolpi-searchbox-empty {
padding: 10px;
font-size: 12px;
color: var(--muted);
}
/* ---------------------------------------------------------------- Modals */
/*
* Actions sit below the body, separated by a hairline rather than a raised bar.
@ -460,6 +662,41 @@ html[data-theme="night"] .wrolpi-status-failed {
/* ----------------------------------------------------------------- Tables */
/* A sortable header: a real button, so the column is reachable by keyboard. */
.wrolpi-th-sort {
display: inline-flex;
align-items: center;
gap: 5px;
background: none;
border: none;
padding: 0;
margin: 0;
font: inherit;
color: inherit;
cursor: pointer;
white-space: nowrap;
}
/* The unsorted columns show a faint arrow: the affordance is visible, but it does
not compete with the column actually doing the sorting. */
.wrolpi-th-sort-idle {
opacity: 0.35;
}
.wrolpi-th-sort:hover .wrolpi-th-sort-idle {
opacity: 0.7;
}
.wrolpi-th-sort:focus-visible {
outline: 2px solid var(--blue);
outline-offset: 2px;
}
html[data-theme="night"] .wrolpi-th-sort:focus-visible,
html[data-theme="amber"] .wrolpi-th-sort:focus-visible {
outline-color: var(--text);
}
/* A failed row carries the danger border, matching failed messages. */
.wrolpi-row-failed > td {
border-top: 1.5px dashed var(--red);