feat(fortal): add FortalCheckboxGroupItem and share the focus ring - #120
Merged
Conversation
RemixCheckboxGroupItem was the only Remix widget owning a Styler with no Fortal recipe. Unlike menu, select, segmented control, and toggle group — whose parent specs carry an `item` slot the Fortal parent recipe fills — RemixCheckboxGroup is behavioral and has no styler, so nothing pushed item styling down. Callers hand-attached fortalCheckboxStyle() to every item, and a missed one in a loop rendered unstyled beside its styled siblings. fortalCheckboxGroupItemStyle delegates to fortalCheckboxStyle rather than restating any of it, so it inherits the checkbox family's verified parity and introduces no new visual surface. The Radix checkbox-group family stays unmapped: the root gap is still caller-owned, which is what the manifest's reopenCondition is about. Also extracts fortalFocusRing/fortalFocusRingBox from four hand-rolled copies in accordion, toggle, toggle group, and tabs. Strictly behavior-preserving — tabs keeps its solid focus-8 where the others use focus-a8, preserved rather than unified because the pinned Chromium probes capture computed styles only, not :focus-visible, so the reference cannot settle whether it is intentional. Verified: 281 remix_fortal tests, 2552 remix tests, 25 dashboard tests, parity check (25 mapped families, 1 audited unmapped), docs validation, and clean generation reproducing all 27 artifacts byte-for-byte.
Both entry points are now `.fortalFocusRing()`, disambiguated by receiver type, instead of a function plus a `Box`-suffixed sibling. Reads as the fluent chain the surrounding recipes already use, and drops a name whose suffix carried no meaning to the reader. Deliberately not `fortalFocusRingStyle`: the `*Style` suffix is load-bearing for the MixWidget generator, which strips it to derive a widget class name. Naming a non-recipe helper that way would advertise a FortalFocusRing widget that does not exist. The other helpers in this file follow the same rule -- fortalFocusOutline, fortalInsetSurface, fortalModeAwareFilter.
Rendered from the real recipes at 2x with Roboto loaded, so it shows actual Fortal output rather than a mock. Available for docs/fortal.mdx; referenced from #120 as visual evidence for FortalCheckboxGroupItem.
The comment documents [color] and [strokeAlign], which became method parameters when this converted from a function to an extension. Dartdoc cannot resolve parameter references from the extension declaration.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last missing Fortal generated component, and de-duplicates the focus ring while in the same code.
No
remixAPI was touched.1.
FortalCheckboxGroupItemRemixCheckboxGroupItemwas the only Remix widget owning aStylerwith no Fortal recipe. The reason it was orphaned is structural:itemslot?RemixToggleGroupItemRemixSegmentedControlItemRemixSelectItemRemixMenuItemRemixCheckboxGroupItemEvery other item is a data class whose parent spec carries an
itemslot the Fortal parent recipe fills.RemixCheckboxGroupis behavioral and has no styler, so nothing pushed item styling down — callers hand-attached the recipe to each item, and a missed one in a loop rendered unstyled beside its styled siblings.Before / after
fortalCheckboxGroupItemStyledelegates tofortalCheckboxStylerather than restating any of it, so it inherits the checkbox family's verified parity and introduces no new visual surface. That is also why no new Chromium probe was needed — the reference fixture's 25 probes already covercheckbox.Parity is unchanged. The Radix
checkbox_groupfamily stays inunmappedUpstreamFamilies: the root gap is still caller-owned, which is what the manifest'sreopenConditionis about. Only thesupportedRemixCompositionprose was updated to name the new wrapper, keeping the per-itemstyleescape hatch documented.2. Shared focus ring
fortalFocusRing/fortalFocusRingBoxreplace four hand-rolled copies in accordion, toggle, toggle group, and tabs. Strictly behavior-preserving — verified per call site againstgit show HEAD.Tabs keeps its solid
focus-8where the other three use alphafocus-a8. That difference is preserved, not unified: the pinned Chromium probes capture computed styles only, not:focus-visible, so the reference cannot settle whether it is intentional. A test fails if someone unifies it, forcing the decision to be deliberate.Two entry points because
FlexBoxStyleris a Mix type that sits outside Remix'sRemixBoxStylerAnchorsinterface — a compiler-enforced boundary, not a style choice.Rendered output
Every component below is the real recipe, rendered at 2x with Roboto loaded — not a mock.
Checkbox groupis the row this PR adds.Usage for each family shown above
Re-scope to restyle any subtree without touching the widget — this is how the dashboard renders status badges:
Tests
test/fortal/focus_ring_test.dart— pins all four rings to their pre-refactor literals.test/components/checkbox/checkbox_group_widget_test.dart— delegation across the fullvariant × size × highContrastmatrix, plus group-selection plumbing.The matrix test compares resolved specs rather than stylers, because
CheckboxStyleris not value-comparable — see the note below.Verification
remix_fortaltestsremixtestsapps+packages)fortal:parity:checkdocs:checkFollow-ups found while doing this (not in this PR)
RemixToggledoes not forwardexcludeSemantics.CheckboxStyleris the only Fortal recipe that is not value-comparable. Root cause is not codegen — the generatedprops/==are correct.CheckboxStyler.onIndeterminate(packages/remix/lib/src/components/checkbox/checkbox_style.dart:10-18) allocates a freshContextVariantwith a closure on every call, andContextVarianthas no==override, unlike its subclassWidgetStateVariantthatonSelected/onFocused/onDisableduse. Bisected:onSelected,onFocused,onDisabledall preserve equality; onlyonIndeterminatebreaks it.accordion_style.dart:73,85,97,109has the same latent pattern.fortalFocusOutlinesecond pass —checkbox,card,radio, andswitchrecipes each inline the CSS-outline pattern the existing helper already produces.