Form control roundout - #50
Merged
Merged
Conversation
- Remove select's label slot; Select and ComboBox render labels from the field recipe like every other field (the two had drifted). - Fix the dead disabled-label rule: RAC never marks the Label, so "&[data-disabled]" matched nothing; now "[data-disabled] > &" plus FormLabel's opacity transition (playbook gotcha #45). - Export FieldLabel (isRequired, css override, id/htmlFor passthrough) and render all six fields through it. - Disabled story on Forms/Field chrome and an it.each asserting every field's label carries field().label.
Chakra FormLabel's `medium` never rendered: no font in the family's stack has a 500 face, so macOS/Windows drew 400 regardless, and the only call sites that cared (settings rows) overrode to `normal` — as their Chakra originals did. The default moves, the overrides go at the next release. Visible only on platforms whose sans-serif has a real Medium (Roboto/Noto) or under a future webfont brand preset; accepted without a device check. Playbook: expected-deltas entry, and the field-chrome roadmap bullet updated to the decisions in docs/form-controls.md (it predated them and argued for keeping `medium`).
Decision record and implementation plan from the 2026-08-05/06 sessions: size variants with label auto-follow, labelPosition="side" on the four single-control fields, NativeSelectField alongside the bare export, context-free FieldHelperText/FieldErrorMessage, and the app-PR list.
The missing half of the size scale (docs/form-controls.md): - NumberField takes `size` (input recipe), as TextField does. The stepper column stays 24px at every size, as Chakra's did; only the arrow glyphs scale (0.75 x the field's font size). - The select recipe's trigger gets the input recipe's size ladder, step for step, so a Select sits level with a TextField at every size. Its hard-coded h/px move into the variants; fontSize md at the default is new but a no-op at every call site (non-dense apps inherit 1rem, and classroom's `classroom` variant sets its own and wins ties — verified against the generated CSS order). - The field recipe gets a size variant driving label fontSize, threaded from the same prop, so one `size` scales the whole row. FieldLabel takes `size` for the composites. Helper/error text stays sm, as Chakra's did. - staticCss entries for field and numberField now they have variants. - Sizes story on Forms/Field chrome; tests that size reaches the label on all four fields and the input on NumberField.
The settings-row pattern as a field variant (docs/form-controls.md): the label absorbs the free space, the control keeps its own width (give it one via groupCss/wrapperCss/css), and helper/error text drops to a full-width line below the pair. Named after React Spectrum's prop — `orientation` is RAC RadioGroup's radio-layout axis, which is also why the group fields don't take it. The field recipe's root becomes the single owner of field-root layout: Select, ComboBox and NumberField wear it alongside their own root slot, which now carries none — two recipes fighting over flexDirection would leave the variant at the mercy of emission order. NumberField's root gains width:100% from it, matching the other fields (its one call site already set that by hand). SideLabels story; tests that the variant reaches root, label and helper on all four fields. Both new stories screenshot-checked.
NativeSelectField pairs the bare NativeSelect with the field chrome, as TextField pairs with Input: label association, aria-describedby and the root's data-disabled wired by hand, since there is no RAC context to do it. With labelPosition="side" it is the settings row python-editor's and ml-trainer's SelectFormControls each hand-rolled (docs/form-controls.md); the bare export stays for the six control-only call sites. FieldHelperText/FieldErrorMessage are the non-RAC escape hatch decided over documenting the recipe's slot classes: plain styled elements, the caller owning the wiring RAC would otherwise do (aria-describedby, when the error shows). No errorMessage on NativeSelectField yet - no consumer needs one. NativeSelectField joins the Field chrome stories (native required/disabled props there); tests cover the hand-wiring piece by piece.
The plan is done to the release boundary, so the working doc goes; what still matters lives in the playbook's field-chrome roadmap bullet, recast around what remains: the decisions taken (weight, size follows, labelPosition, NativeSelectField beside the bare export, context-free helper/error) and the app-PR checklist (python-editor's dialog aligns on md, both SelectFormControls deleted, the data-microbit-org adoptions). Source comments now point at the playbook.
Five unrelated fields crammed on one row clipped and wrapped; now it is one small form per column at sm/md/lg, so the scale is compared like for like. The TextField/Select pair keeps the cross-recipe level check as the first row.
The tier-2 gap: standalone Checkbox and Switch had no helper-text story, so both settings dialogs hand-rolled a Text underneath (never reaching aria-describedby, and drifting to gray.700 against the chrome's gray.600), and ml-trainer bent Switch into a settings row by reaching into .switch__label. Now: - helperText on Checkbox and Switch, wired to aria-describedby and rendered by FieldHelperText; the component gains a wrapping div. - labelPosition="start" on Switch: label first, switch at the row's end. Values are start/end, not the field's top/side - a toggle's label is already beside it. The label keeps an end margin (the SelectFormControl translation lesson). - Radio stays helper-less (always grouped); Slider/Textarea parked deliberately - no consumer exists. Naming, while breaking is cheap (owner-approved): - css no longer means a different element per component: Select and ComboBox css -> triggerCss (pairs with contentCss); NumberField css -> rootCss (pairs with NativeSelectField). Bare controls keep css for their root, which is the control there. TS-guided rename; classroom's four Select/ComboBox sites are the ones outside the queued app PRs. - Files follow their contents: Field.recipe.ts (the field recipe was in TextField.recipe.ts), Field.tsx (FieldSupport.tsx held the whole chrome family), RadioGroup.tsx split out to mirror CheckboxGroup. switchRecipe keeps its name - switch is a reserved word. Stories: standalone toggles join HelperText, the analytics Switch row joins SideLabels; both screenshot-checked. Tests cover the toggle aria-describedby wiring and the start-position classes.
Deploying ui with
|
| Latest commit: |
cfb2b2a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://74a52971.ui-2wg.pages.dev |
| Branch Preview URL: | https://form-control-roundout.ui-2wg.pages.dev |
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.
No description provided.