Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function Tabs<T extends string>({ name, tabs, active, onSelect, label }:
};

return <>
<div className="segmented tab-bar" role="tablist" aria-label={label} ref={listRef} style={{ ["--tab-count" as string]: tabs.length }}>
<div className="tab-bar" role="tablist" aria-label={label} ref={listRef}>
{tabs.map((tab, index) => <Button
key={tab.id}
type="button"
Expand Down
44 changes: 31 additions & 13 deletions src/ui/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -320,22 +320,41 @@ main { min-width: 0; }
.teaching-demo { max-width: 100%; padding: 16px; }
.teaching-caption { font-size: 18px; }

/* --- Segmented choices and tabs --------------------------------------------- */
.segmented { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; min-width: 0; margin: 0 0 22px; border: 0; padding: 0; background: transparent; }
.segmented.compact { width: 100%; margin: 0; }
.segmented .btn { color: var(--muted); border-color: var(--border); background: var(--surface); }
.segmented .btn:is([aria-pressed="true"], [aria-selected="true"]) { border: 2px solid var(--brand); color: var(--on-brand-container); background: var(--brand-container); box-shadow: inset 0 0 0 1px var(--brand-border); }

.tab-bar { width: 100%; margin-bottom: 24px; }
.tab-bar .btn { position: relative; padding: 12px 8px; }
/* Positioned siblings paint in order, so lift a focused tab or the selected
neighbour overpaints one edge of its focus ring. */
.tab-bar .btn:focus-visible { z-index: 1; }
/* --- Choices -----------------------------------------------------------------
An option group is a row of equal buttons. The selected one fills with the
brand container and carries a drawn tick, so it differs from focus (a blue
ring outside the button) by shape as well as colour. The tick is an empty
pseudo-element, so it never becomes part of the button's name. */
.segmented { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; width: 100%; min-width: 0; margin: 0; }
.segmented.five { grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); }
.segmented .btn, .setup-choices .btn { color: var(--text); border-color: var(--border-strong); background: var(--surface); font-weight: 500; }
.segmented .btn[aria-pressed="true"], .setup-choices .btn[aria-pressed="true"] { border: 2px solid var(--brand); padding-inline: 18px; color: var(--on-brand-container); background: var(--brand-container); font-weight: 700; }
.segmented .btn:enabled:not([aria-pressed="true"], fieldset:disabled *):hover, .setup-choices .btn:enabled:not([aria-pressed="true"]):hover { background: var(--surface-hover); }
.segmented .btn[aria-pressed="true"]::before, .setup-choices .btn[aria-pressed="true"]::before { flex: none; width: 7px; height: 13px; margin: -4px 2px 0 0; border: solid currentColor; border-width: 0 3px 3px 0; transform: rotate(45deg); content: ""; }

/* --- Tabs ----------------------------------------------------------------------
Tabs split one row equally; a long label wraps inside its own tab first,
and, because no tab may be narrower than its longest word, the row itself
wraps only when single words cannot fit (extreme zoom). Every label plus
its padding is wider than the 64px target, so min-content keeps that too,
so no tab, selection or attention marker is ever scrolled out of sight. The selected tab
is marked by a brand underline and ink colour rather than a filled box, so
tabs never read as a set of buttons competing with the page's own actions.
Every tab keeps one weight, so changing tabs never shifts its neighbours. */
.tab-bar { display: flex; flex-wrap: wrap; gap: 4px; width: 100%; margin-bottom: 24px; border-bottom: 1px solid var(--border); }
.tab-bar .btn { position: relative; flex: 1 1 0; min-width: min-content; text-align: center; border: 0; border-radius: var(--radius-control) var(--radius-control) 0 0; padding: 12px 16px; color: var(--muted); background: transparent; font-weight: 600; }
.tab-bar .btn:hover { color: var(--text); background: var(--surface-hover); }
.tab-bar .btn[aria-selected="true"] { color: var(--text); box-shadow: inset 0 -4px 0 var(--brand); }
/* Inset, so the ring sits within the tab above the shared underline. */
.tab-bar .btn:focus-visible { outline-offset: -3px; }
.tab-panel:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px; }
/* Out of flow so a marker never wraps the label and grows the tab row. The
transparent outline is invisible normally and becomes a visible ring under
forced colours, where the background alone would be flattened away. */
.tab-attention { position: absolute; top: 5px; right: 6px; width: 8px; height: 8px; border-radius: 50%; outline: 1px solid transparent; background: var(--status-error); }
.tab-attention { position: absolute; top: 8px; right: 6px; width: 8px; height: 8px; border-radius: 50%; outline: 1px solid transparent; background: var(--status-error); }
@media (forced-colors: active) {
.tab-bar .btn[aria-selected="true"] { border-bottom: 4px solid Highlight; }
}

/* --- Colour pickers --------------------------------------------------------- */
.color-options, .scanner-colours { display: flex; flex-wrap: wrap; gap: 12px; }
Expand Down Expand Up @@ -467,7 +486,6 @@ main { min-width: 0; }
.setup-content { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 16px 4px; }
.setup-content p { margin-top: 8px; color: var(--muted); }
.setup-choices { margin-top: 22px; }
.setup-choices .btn[aria-pressed="true"] { border-color: var(--brand); color: var(--on-brand-container); background: var(--segment-selected); box-shadow: inset 0 0 0 1px var(--brand-border); }
.setup-privacy { margin-top: 18px; color: var(--brand-label); }
.setup-navigation-guidance { flex: 0 0 auto; margin: 0; color: var(--muted); line-height: 1.5; }
.setup-navigation-guidance:not(:empty) { padding-bottom: 16px; }
Expand Down
Loading