feat!: add BorderedTextField; migrate fields to ControlPresentation#1039
Draft
pawelgrimm wants to merge 18 commits into
Draft
Conversation
A presentational layout shell for input-like controls. Provides border chrome, focus/hover/disabled/readonly/invalid styling, and two slots (startSlot, endSlot) flanking an arbitrary focusable control passed as children. Forwards a ref to the wrapper and forwards click events to the inner control (toggleable via forwardClickToControl). State-driven styling fires from :has() selectors that match three signaling conventions — native HTML, ARIA, and data-attr — on the inner control. The single source of truth is the a11y attributes the control needs anyway. :invalid is deliberately omitted (fires pre-interaction). Spacing is explicit conditional padding: 10px outer on each side by default; left shrinks to 6px when startSlot is present (with 6px gap to control); right shrinks to 4px when endSlot is present (with 6px gap). ControlActionButton ships alongside as a compact 24x24 button variant sized to fit the chrome alongside a 16px icon glyph. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover the public API surface (slot rendering, click-forwarding, ref forwarding, attribute passthrough, exceptionallySetClassName) plus each state-styling signaling convention (native HTML, ARIA, data-attr) and the slot-marker classes that drive the conditional outer padding. jest-axe smoke test for accessibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace margin-inline-{end,start} on .startSlot / .endSlot with a single
gap: 6px declaration on the .container flex wrapper. The rendered
geometry is identical (flex gap only applies between rendered children,
so absent slots still produce no gap), but the spacing rule lives in one
place and can't drift between the two sides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the wrapper background is clicked, handleWrapperClick synthesizes control.click() to activate the inner control. That synthetic click bubbles back up to the wrapper and re-enters the same handler, which called onClick a second time. Track the dispatched re-entry via a ref and short-circuit it so consumer onClick fires exactly once per user gesture. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
30 → 12 tests. The pruned tests were either (a) asserting React's own attribute-passthrough behavior (the entire state-styling describe block — JSDOM doesn't evaluate the :has() CSS that gives those attributes visual meaning), (b) locking in implementation details (role attributes, CSS-module class names on slot wrappers), or (c) duplicating other tests' coverage. Visual concerns (conditional padding, :has()-driven state styling) stay verified by Storybook/Chromatic — Jest is the wrong tool for CSS behavior. Added three small prop-guardrail tests so every documented prop has at least one direct test: forwardClickToControl, onClick, ref forwarding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion props Click handlers belong on the inner control, not on the wrapper. The wrapper's click-to-focus behavior is internal — there's no real need for consumers to disable it (forwardClickToControl) or hook into it (onClick) for the wrap pattern. The implementation still extracts onClick out of rest (via a type cast) to compose with the focus-forwarding handler — this path exists only for render-prop use, where Ariakit (or similar) forwards its own onClick to the wrapper-as-trigger. Consumers of ControlPresentation directly won't see onClick in autocomplete; TypeScript rejects passing it at call sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace `Omit<ComponentProps<typeof Box>, 'className'>` on FCC with explicit `onClick` + `ObfuscatedClassName`. Move `display:flex`, `align-items:center`, `overflow:hidden` into FCC's CSS. CP keeps its loose public type (Box props) but only forwards `onClick` to FCC; other Box props (maxWidth, etc.) are silently dropped pending a follow-up CP API tightening. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ghten ControlPresentation type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove variant, BaseFieldVariantProps, supportsStartAndEndSlots, endSlot, endSlotPosition, and bordered chrome from BaseField; strip .bordered.* CSS rules; update all callers (password-field, bordered-text-field, select-field, text-field). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7687126 to
14f7734
Compare
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.
Short description
Follows up on #1037. Migrates the existing field components to compose
ControlPresentationand introduces a new public sibling,BorderedTextField, that replaces the outlined-label-inside layout previously offered by<TextField variant="bordered">.New public component
BorderedTextField— outlined text-field layout: the label sits inside a rounded chrome above the input; optionalendSlotrendered as a full-height side column. ComposesBaseField+OutlinedControlContainer(the private chrome primitive added in feat: add ControlPresentation primitive #1037). Sibling toTextField, not a variant of it.Field migrations
TextFielddropsvariantandendSlotPosition. Inline wrapper JSX is replaced with<ControlPresentation>. LocalhandleClickanduseMergeRefsare removed (CP/OCC handle click-to-focus).SelectFielddropsvariant. The absolutely-positioned chevron moves into CP'sendSlot.selectWrapperchrome CSS goes away — CP owns it.TextAreadropsvariant. Wraps the<textarea>inOutlinedControlContainerdirectly (not CP — multi-line layout doesn't fit CP's fixed 32px single-row chrome). Auto-expand grid trick stays scoped to TextArea.PasswordFieldunchanged in source but inherits theTextFieldrefactor automatically (no morevariant).BaseFieldscoped downvariant,BaseFieldVariantProps, thesupportsStartAndEndSlotsdiscriminated union,endSlot,endSlotPosition, and the bordered outer container rendering.aria-describedby/aria-invalid), optional label, character count state machine, message rendering, Stack layout,maxWidth/hidden.Breaking changes
feat!:— major version bump. Migration is mechanical:<TextField variant="bordered" .../>→<BorderedTextField .../>. DropstartSlot,endSlotPosition, andcharacterCountPosition="inline"if used — none of these are supported in the outlined layout. If you need a leading icon, useTextField(default). If you need a side action, useBorderedTextField'sendSlot(full-height).<TextField variant="default" .../>→ drop thevariantprop (no-op rename — default was always the default).<SelectField variant="bordered" .../>→ drop thevariant. NoBorderedSelectFieldis shipped (no demand surfaced).<TextArea variant="bordered" .../>→ drop thevariant. NoBorderedTextAreais shipped.BaseFielddirectly: dropvariant,supportsStartAndEndSlots,endSlot,endSlotPositionif used. Render slot rows viaControlPresentationinstead.What's not here
CheckboxField/SwitchFieldare unrelated chrome and are not touched.PR Checklist
endSlotPosition)