Files
technitium-console/src/ui/Form.module.css
T
DenozordecandClaude Sonnet 5.5 87281a87d3
CI / check (push) Failing after 37s
CI / image (push) Skipped
Match the Technitium canvas: palette, shell, KPI strips, framed tables
The live UI did not follow the canvas because its base layer differed: dark
palette in hex (now the canvas oklch values), 13 px body text, flat Frame panels.

- Tokens: canvas palette, 14 px body, 1.4 line height; Frame without `dense`
  gets the 3 px ring; Panel takes a description line.
- Shell: 56 px brand button with version, 11 px group headings, user card at the
  foot, versions moved into the header status popover, Home / group / section
  crumbs, amber "Attention" status when blocking is off or paused.
- New KpiStrip and PageTabs; Dashboard KPIs in three frames and a Refresh button.
- Zones: KPI strip, tab with count, views, framed table with the canvas columns.
- Apps: Installed / App Store tabs and a grid instead of cards (store inline).
- Blocking: state banner and tab counts; one "Blocking" title.
- Settings: actions in the header, label-and-help rows with the control on the right.
- DHCP, Administration, DNS Client, Logs, Cache, About: subtitles, framed tables,
  KPI strips where the data exists; Login cover with the three-point list.
- docs/compose.shnt.example.yaml: production compose with the console image.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 12:47:27 +07:00

211 lines
7.1 KiB
CSS

/*
The label + control row, a single one. There were four grids (210 px on pages,
180 px in the Zones modals, 170 px in the Administration ones, 110 px in the Logs
filter) and two different labels: 600 and `--ink` on pages, 400 and `--mute` in
the Zones modals. With the muted one, in "Add Zone" the label receded behind the
value; with the strong one, in "Add User" it competed with it.
The strong one wins: in a long form the label is what you look for when scanning,
not what you read afterwards.
*/
/*
THREE columns: label, control and help.
It is the dense-form design's decision, and the one that holds it up: in a dense form
**the surface is the text**. `General` has 41 paragraphs of help against 39
controls, and under each field they made a page twice as long in which the text
weighed more than the fields.
The third column is `--help-col` and **only `.row` carries it**. `.mrow` (the
dialog) stays at two: there are 560 px of width there, and the design measured a
panel, not a modal.
*/
.row {
display: grid;
grid-template-columns: 210px minmax(0, 1fr) var(--help-col);
gap: var(--s-8);
padding: var(--s-7) var(--s-9);
align-items: start;
border-bottom: 1px solid var(--line);
}
/* The help as the control's sibling and not inside it: that is what moves it into
its own column. In the modal it stays nested, and that is why it still falls
underneath. */
.rowHelp {
grid-column: 3;
}
.row:last-child { border-bottom: 0; }
/* Inside a modal there is less room and fewer rows: no separator, no 210 px. */
.mrow {
display: grid;
grid-template-columns: 180px minmax(0, 1fr);
gap: var(--s-8);
padding: var(--s-3) 0;
align-items: start;
}
.rowLabel,
.mrowLabel {
font-size: var(--t-4);
color: var(--ink);
font-weight: 600;
padding-top: var(--s-5);
line-height: 1.35;
}
.rowCtl,
.mrowCtl {
min-width: 0;
display: flex;
flex-direction: column;
gap: var(--s-3);
}
/*
A one-line field gains nothing from being 640 px wide.
The control column is `1fr`, so it eats everything left over, and that did not
show while every dialog was 560 wide. Once "User Details" got the 880 its sessions
table asks for, the box for typing "admin" grew to 640 px. The cap is 420, which
is what the page forms were already using in practice: measured across the
console's 46 fields, the median is 174 px and the maximum 420.
The cap also reaches a table that is a row's control: "Member Of" in My Profile is
ONE column and measured 831 px for an 82 px group name. Upstream does not stretch
it either: it puts it in a `col-sm-7`. What stays outside is the `textarea` (IP
lists, which do use the width) and tables that are NOT a row's control, like the
sessions one, which needs everything it can get.
*/
.mrow input:not([type='checkbox']):not([type='radio']),
.mrow select,
.mrow table {
max-width: 420px;
}
/*
The breakpoints, and why the panel and the modal no longer share one.
This breakpoint was a SINGLE one at 720 for both rows. What motivated it is still
true and is written down: at 390 the 210 px grid left 20 px for the field and made
seventeen Settings fields invisible. **But that proves 390 had to be protected,
not that 720 was the right threshold.**
So they part company:
· `.row` follows the breakpoints the dense-form design measured: 1180 and 560.
· `.mrow` **keeps its 720 and its geometry**: the design measured a dense form,
not a dialog, and does not authorise touching the modal. That 720 stands as
**modal** debt, with its figure, and not mixed in with the dense form.
At 1180 the help leaves the third column and drops to a second line of its own row.
It stays visible and stays complete: **no width hides it**.
*/
@media (max-width: 1180px) {
.row {
grid-template-columns: 210px minmax(0, 1fr);
}
/* Second line, aligned with the control and not with the label. */
.rowHelp {
grid-column: 2;
}
}
/* At 560 it stacks completely: label, control and help, in that order. */
@media (max-width: 560px) {
.row {
grid-template-columns: minmax(0, 1fr);
gap: var(--s-4);
padding: var(--s-7) var(--s-8);
}
.rowLabel { padding-top: 0; }
.rowHelp { grid-column: 1; }
}
/* 720: the MODAL's, which stays where it was. */
@media (max-width: 720px) {
.mrow {
grid-template-columns: minmax(0, 1fr);
gap: var(--s-4);
}
.mrowLabel { padding-top: 0; }
}
/*
The help under a field, and the group of checkboxes in a row.
Both were defined three times (in Settings, in DHCP and in Administration) with
the same values copied by hand. It is the same story as the table and the panel:
what gets repeated ends up drifting apart.
*/
.help {
composes: withCode from './text.module.css';
font-size: var(--t-3);
color: var(--mute);
line-height: 1.55;
max-width: 720px;
}
.help a { color: var(--acc-ink); }
/* Several checkboxes or radios inside a single row. */
.group {
display: flex;
flex-direction: column;
gap: var(--s-6);
}
/*
The row switched off by its master switch.
An amber edge on the side, and nothing else here: the opacity already comes with
the control's own `disabled`, and the pill that NAMES it is drawn by `Row` next to
the label. The three pieces together are the master-switch signal, and none is
spare: the edge is seen without reading, the pill says which switch it is, and the
opacity says it is not active.
Amber and not red, and amber and not a padlock: *amber = you can*. The user can
switch this on themselves. If the permission were missing as well, **the padlock
wins** and this edge is not drawn: what you cannot touch does not need two
explanations of why it is off.
The edge is an INNER SHADOW and not a `border-left`, and the measurement decided
that, not taste. With `border-left: 2px` the grid of those five rows became
`210px 540px 360px` against the `210px 542px 360px` of the other 34: the border
eats two pixels of the box and **shifts the control**. In a dense form that is a
column that stops being aligned, and `dev/uniformity.js` caught it as a third
`grid-panel` signature that existed only in `/settings/general/`.
A state signal cannot move the content it points at. The inner shadow paints
exactly the same edge and takes up no grid: the family is back to two signatures
and the 39 rows stay aligned.
*/
.dependent { box-shadow: inset 2px 0 0 0 var(--acc-ink); }
.withPill { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }
/*
The canvas's row (`.frow`): the label with its help under it on the left, the control
on the right, a hairline between rows. The help stays a sibling of the control, so
the grid puts it under the label without moving it in the DOM.
*/
.row {
grid-template-columns: minmax(0, 1fr) minmax(220px, 440px);
grid-template-areas: 'label ctl' 'help ctl';
gap: var(--s-1) var(--s-10);
padding: 14px var(--s-10);
align-items: center;
}
.row > .rowLabel { grid-area: label; padding-top: 0; font-weight: 500; font-size: 14px; }
.row > .rowCtl { grid-area: ctl; align-self: center; }
.row > .rowHelp { grid-area: help; grid-column: auto; max-width: 640px; font-size: 13px; color: var(--mute); }
@media (max-width: 900px) {
.row { grid-template-columns: minmax(0, 1fr); grid-template-areas: 'label' 'help' 'ctl'; }
}