Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6f1f89d
feat(quiz): strict window-leave level on a reusable three-position sw…
claude Oct 13, 2026
76158a4
test(css): budget the three-position switch's pill radius
claude Oct 13, 2026
c144cd2
feat(admin): steps control replaces the three-position switch
claude Oct 13, 2026
40efebc
feat(admin): ordered three-level settings use the steps control
claude Oct 13, 2026
72642fe
fix(admin): give the steps fieldset's phpcs annotation a real reason
claude Oct 13, 2026
1f4b91e
Merge branch 'claude/quiz-reset-levels' into claude/steps-settings
claude Oct 13, 2026
4205e54
feat(admin): the on/off toggle joins the ffc-control family
claude Oct 13, 2026
337d4d7
fix(admin): toggle mark layers on the token scale; dashboard radius b…
claude Oct 13, 2026
5064ae6
feat(admin): segmented control for small unordered choices
claude Oct 13, 2026
3cd3cf8
Merge remote-tracking branch 'origin/develop' into claude/steps-settings
claude Oct 13, 2026
ec7dd54
Merge branch 'claude/steps-settings' into claude/control-toggle
claude Oct 13, 2026
92c31a3
Merge branch 'claude/control-toggle' into claude/segmented-control
claude Oct 13, 2026
895d87c
Merge remote-tracking branch 'origin/develop' into claude/control-toggle
claude Oct 13, 2026
a461a96
Merge branch 'claude/control-toggle' into claude/segmented-control
claude Oct 13, 2026
9f18b5e
fix(admin): steps and segmented controls keep their layout in a .form…
claude Oct 13, 2026
b3fbc65
Merge remote-tracking branch 'origin/develop' into claude/segmented-c…
claude Oct 13, 2026
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The format follows [Keep a Changelog] (https://keepachangelog.com/en/1.1.0/).
- **Required form fields are a list of their own** (#1741). Settings → Certificates keeps the layout tags and, apart, the fields every form needs with label and type (default `name`, `cpf_rf`, `email`; Email stays). New forms start with them, their type is locked, and the editor adds missing ones.
- **Ordered three-level settings use the steps control** (#1745). The four geofence display modes, the two private-calendar displays and the IP API fallback store the same values as before; an unknown stored value shows the old default.
- **The on/off toggle joins the control family** (#1748). `.ffc-toggle` is now `.ffc-control-toggle` everywhere, with the shared hover and pressed halo, a greyed disabled state instead of a faded one, and an optional check / cross mark on the knob.
- **Small unordered choices use a segmented control** (#1750). Dark Mode, Code Editor Theme and the geofence Time Behavior are one row of options instead of a select or stacked radios, with the same stored values; new `AdminUI::render_segmented()` takes 2 to 6 options.

### Fixed

Expand All @@ -38,6 +39,7 @@ The format follows [Keep a Changelog] (https://keepachangelog.com/en/1.1.0/).
- **A certificate form submits again** (#1739). A script printed before `ffc-core.js` that created the `FFC` namespace made the core skip its own API, so every submit failed on `FFC.request is not a function`; the core now installs its API and keeps what was already there.
- **A quiz attempt that did not pass is no longer a certificate** (#1744). The first attempt was saved as issued and its certificate link emailed before its result was known; its code also verified. The email, verification by code or link, the REST lookup and the user dashboard now skip it.
- **The dashboard's notification toggles can be reached by keyboard** (#1748). They used a second toggle that hid its checkbox with `display: none`; they now use the plugin's one toggle.
- **The steps control lines up inside a settings table** (#1750). WordPress makes every label in a `.form-table` fieldset inline-block, which pushed each stop's mark to the left of its rail; the stops and the segmented options now outrank that rule.

## [6.38.0] (2026-10-12) — `529e80a1`

Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -804,12 +804,13 @@ A setting that picks among a few values is drawn by a control from the `ffc-cont
| --- | --- | --- |
| two (on/off) | toggle | `AdminUI::render_toggle()` |
| an ORDERED scale of 3 to 11 | steps | `AdminUI::render_steps()` |
| 2 to 6 with no order | segmented | `AdminUI::render_segmented()` (planned) |
| 2 to 6 with no order | segmented | `AdminUI::render_segmented()` |
| anything else | a `<select>` | — |

- **One toggle, everywhere** (#1748). `.ffc-control-toggle` is the only on/off control, the user dashboard's included — it had a second implementation that hid its checkbox with `display: none`, which took it out of keyboard reach. A toggle built in JS prints the same markup `render_toggle()` does. `mark` adds a check or a cross on the knob; the knob is white in both themes, so the mark's ink is fixed too, and the drawings come from `Core\Icons` through `--ffc-icon-mask-*` custom properties (`Icons::MASKS`).
- **Order is the test for steps.** A rail and a fill say "more of the same thing"; three unrelated choices on a rail lie about their relation. Off → Moderate → Strict is a scale; Dark / Light / Auto is not.
- **Out of range is handled, not refused.** `render_steps()` given fewer or more options than it draws prints a select, so the field still works, and calls `_doing_it_wrong()` naming the control that fits — which surfaces under `WP_DEBUG`, where the developer is.
- **A segment's name is short; the explanation is a description.** A select hid the long option text until opened; a segmented row shows every name at once, so `On (always dark)` became `On` and the sentence moved to `descriptions`, of which only the chosen one shows (#1750).
- **Out of range is handled, not refused.** `render_steps()` or `render_segmented()` given fewer or more options than it draws prints a select, so the field still works, and calls `_doing_it_wrong()` naming the control that fits — which surfaces under `WP_DEBUG`, where the developer is.
- **Every control is native inputs plus CSS.** Radios or a checkbox carry the value, so the form posts without a script, the arrow keys and screen readers work, and autosave (`data-ffc-autosave-key` / `-form-key` on the inputs) saves the group as one field with no change to the widget.
- **Parameters for what varies, custom properties for fine tuning.** Palette, size, shape, width and which labels show are parameters that become classes; anything finer is a `--ffc-control-*` property set by a module class or inline. A palette or tone colours through tokens only and stays inside `:where()`, so a state such as disabled always wins over the colour of the chosen value.
- **One state language.** Hover and pressed draw a halo (`--ffc-control-halo`), keyboard focus the 2px primary ring, and disabled takes the colour away instead of fading — `opacity` would multiply down the contrast `DarkModeCssTest` measures.
Expand Down
6 changes: 0 additions & 6 deletions assets/css/ffc-admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -864,12 +864,6 @@
text-transform: uppercase;
}

/* Time description indent */
.ffc-time-description {
margin-left: var(--ffc-spacing-2xl);
margin-top: var(--ffc-spacing-2xs);
}

/* List style */
.ffc-list-disc {
list-style: disc;
Expand Down
2 changes: 1 addition & 1 deletion assets/css/ffc-admin.min.css

Large diffs are not rendered by default.

179 changes: 178 additions & 1 deletion assets/css/ffc-common.css
Original file line number Diff line number Diff line change
Expand Up @@ -2027,7 +2027,8 @@
========================================================================== */
/* The family's shared hover / pressed halo. */
.ffc-control-toggle,
.ffc-control-steps {
.ffc-control-steps,
.ffc-control-segmented {
--ffc-control-halo: color-mix(in srgb, var(--ffc-primary) 18%, transparent);
--ffc-control-knob-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
/* The ink of a mark drawn on the toggle's knob. The knob is white in both
Expand Down Expand Up @@ -2231,6 +2232,15 @@
cursor: pointer;
}

/* Inside a .form-table, WordPress's `.form-table td fieldset label` makes
every label inline-block with a forced margin (!important). A stop is a
label in a fieldset, so it would lose its column and its mark would sit
left of the rail it belongs on. */
.form-table td fieldset .ffc-control-steps-stop {
display: flex;
margin: 0 !important;
}

.ffc-control-steps-stop input {
/* Visually hidden but reachable by keyboard + assistive tech. */
position: absolute;
Expand Down Expand Up @@ -2468,6 +2478,173 @@
}
}

/* ==========================================================================
SEGMENTED (#1750) — AdminUI::render_segmented(): 2 to 6 choices with NO
order between them, joined in one pill. One native radio per segment, so
it posts and works without a script. The chosen segment is filled with
the accent and set in bold, so colour is never the only signal.
========================================================================== */
.ffc-control-segmented {
--ffc-control-segmented-pad-y: var(--ffc-spacing-2xs);
--ffc-control-segmented-pad-x: var(--ffc-spacing-lg);
--ffc-control-segmented-font: var(--ffc-font-size-sm);
--ffc-control-segmented-shape: var(--ffc-radius-pill);

display: inline-block;
vertical-align: top;
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}

.ffc-control-segmented-track {
display: inline-flex;
gap: var(--ffc-spacing-3xs);
padding: var(--ffc-spacing-3xs);
border: 1px solid var(--ffc-border);
border-radius: var(--ffc-control-segmented-shape);
background: var(--ffc-gray-100);
}

.ffc-control-segmented-option {
position: relative;
display: flex;
cursor: pointer;
}

/* The same WordPress rule as for the steps stops above. */
.form-table td fieldset .ffc-control-segmented-option {
display: flex;
margin: 0 !important;
}

.ffc-control-segmented-option input {
/* Visually hidden but reachable by keyboard + assistive tech. */
position: absolute;
width: 1px;
height: 1px;
margin: 0;
overflow: hidden;
clip-path: inset(50%);
}

.ffc-control-segmented-label {
flex: 1 1 auto;
padding: var(--ffc-control-segmented-pad-y) var(--ffc-control-segmented-pad-x);
border-radius: var(--ffc-control-segmented-shape);
color: var(--ffc-text);
font-size: var(--ffc-control-segmented-font);
line-height: 1.4;
text-align: center;
white-space: nowrap;
transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.ffc-control-segmented-option:hover input:not(:checked) + .ffc-control-segmented-label {
background: var(--ffc-control-halo);
}

.ffc-control-segmented-option input:checked + .ffc-control-segmented-label {
background: var(--ffc-primary);
color: var(--ffc-text-on-primary);
font-weight: 600;
box-shadow: var(--ffc-shadow-sm);
}

.ffc-control-segmented-option:active input:checked + .ffc-control-segmented-label {
box-shadow: 0 0 0 4px var(--ffc-control-halo);
}

.ffc-control-segmented-option input:focus-visible + .ffc-control-segmented-label {
outline: 2px solid var(--ffc-primary);
outline-offset: 2px;
}

/* Sizes and width. */
.ffc-control-segmented-sm {
--ffc-control-segmented-pad-y: var(--ffc-spacing-3xs);
--ffc-control-segmented-pad-x: var(--ffc-spacing-sm);
--ffc-control-segmented-font: var(--ffc-font-size-xs);
}

.ffc-control-segmented-lg {
--ffc-control-segmented-pad-y: var(--ffc-spacing-xs);
--ffc-control-segmented-pad-x: var(--ffc-spacing-xl);
--ffc-control-segmented-font: var(--ffc-font-size-base);
}

.ffc-control-segmented-fluid {
display: block;
}

.ffc-control-segmented-fluid .ffc-control-segmented-track {
display: flex;
}

.ffc-control-segmented-fluid .ffc-control-segmented-option {
flex: 1 1 0;
}

/* Disabled: the colour leaves, nothing fades. */
.ffc-control-segmented:disabled .ffc-control-segmented-option {
cursor: not-allowed;
}

.ffc-control-segmented:disabled .ffc-control-segmented-option input:checked + .ffc-control-segmented-label {
background: var(--ffc-gray-200);
color: var(--ffc-text-muted);
box-shadow: inset 0 0 0 1px var(--ffc-gray-300);
}

/* Only the chosen option's description shows. */
.ffc-control-segmented-desc {
display: none;
margin-top: var(--ffc-spacing-xs);
}

.ffc-control-segmented:has(.ffc-control-segmented-option:nth-child(1) input:checked) .ffc-control-segmented-desc[data-option="1"],
.ffc-control-segmented:has(.ffc-control-segmented-option:nth-child(2) input:checked) .ffc-control-segmented-desc[data-option="2"],
.ffc-control-segmented:has(.ffc-control-segmented-option:nth-child(3) input:checked) .ffc-control-segmented-desc[data-option="3"],
.ffc-control-segmented:has(.ffc-control-segmented-option:nth-child(4) input:checked) .ffc-control-segmented-desc[data-option="4"],
.ffc-control-segmented:has(.ffc-control-segmented-option:nth-child(5) input:checked) .ffc-control-segmented-desc[data-option="5"],
.ffc-control-segmented:has(.ffc-control-segmented-option:nth-child(6) input:checked) .ffc-control-segmented-desc[data-option="6"] {
display: block;
}

/* A phone: the segments share the width and may wrap their names. */
@media (max-width: 480px) {
.ffc-control-segmented {
display: block;
}

.ffc-control-segmented .ffc-control-segmented-track {
display: flex;
}

.ffc-control-segmented .ffc-control-segmented-option {
flex: 1 1 0;
}

.ffc-control-segmented .ffc-control-segmented-label {
white-space: normal;
}
}

@media (prefers-reduced-motion: reduce) {
.ffc-control-segmented-label {
transition: none;
}
}

@media (forced-colors: active) {
.ffc-control-segmented-option input:checked + .ffc-control-segmented-label {
forced-color-adjust: none;
background: Highlight;
color: HighlightText;
}
}

@media (prefers-reduced-motion: reduce) {
.ffc-control-steps-mark::after {
transition: none;
Expand Down
2 changes: 1 addition & 1 deletion assets/css/ffc-common.min.css

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions assets/js/ffc-admin-autosave.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,10 @@
// the track on toggle-on — so the toggle visually stays "off"
// even after the save succeeds. Anchor the badge AFTER the
// wrapping label instead so the track stays adjacent.
// A steps control (#1745) is one field made of one radio per step:
// its badge goes after the whole control, once.
var $anchor = $field.closest('.ffc-control-toggle, .ffc-control-steps');
// A steps (#1745) or segmented (#1750) control is one field made of
// one radio per option: its badge goes after the whole control, once.
// Inside it the badge would also break `input:checked + label`.
var $anchor = $field.closest('.ffc-control-toggle, .ffc-control-steps, .ffc-control-segmented');
if (!$anchor.length) {
$anchor = $field;
}
Expand Down
2 changes: 1 addition & 1 deletion assets/js/ffc-admin-autosave.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading