feat(Select): add renderTrigger for custom trigger elements - #655
Merged
Conversation
Select only supported its built-in trigger, so replacing it meant dropping down to DropdownMenu and losing value resolution, type-ahead and the hidden input. renderTrigger hands the flyout attributes out to the consumer instead, following the Accordion.Trigger and Resizable.Handle convention of passing spreadable attributes as the first argument and state as the second. The hidden input moves into a shared SelectHiddenInput so form submission keeps working when the default trigger is replaced, and the onClick prop is composed manually since Actionable is no longer there to merge it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B5bUMTZHMVrTywF9gTCJcf
size-limit report 📦
|
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.
Summary
Adds a
renderTriggerprop to the customSelect, letting consumers replace the built-in trigger while keeping value resolution, type-ahead, keyboard handling and form submission.The plumbing already existed one level down —
DropdownMenu.TriggerisFlyout.Trigger, which hands outTriggerAttributes.SelectCustomControlledjust hard-codedSelectTriggeras their only consumer, so this forwards that function outward.API shape follows the existing convention for render props that carry both attributes and state — spreadable attributes as the first positional argument, state as a second argument named
props, matchingAccordion.TriggerandResizable.Handle. The wrapped(args: {...})form used byrenderValue/renderDateValueis for value formatters where nothing is spreadable. The resolved value node goes inattributes.children(falling back toplaceholder), mirroring howActionable.renderincludeschildrenso a bare spread works.A
Select.Triggercompound child was considered and rejected:childrenis exclusively the option list thattraverseOptionListwalks, and hoisting a trigger out of it would reintroduce the child scanning that #608 deliberately removed.Changes:
Select.types.ts—TriggerRenderAttributesplusrenderTriggeron the four custom fragment members. Placed alongsiderenderValuesovaluenarrows tostringvsstring[]off themultiplediscriminant; typedneveron the native fragments.SelectHiddenInput.tsx(new) — extracted fromSelectTriggerso both paths share one place for the value serialization. Without it, a custom trigger would silently break form submission.SelectCustomControlled.tsx— the wiring.onClickis composed manually (Actionablewas merging Select'sonClickwith the flyout's for free) and guarded ondisabled.Select/index.tsandsrc/index.ts.RootPropstype — a vestigial render-prop sketch of this same feature, superseded here.Related Issue
Screenshots / Recordings
Covered by the new
renderTriggerstory (Components/Select), which renders anActionablewithneutral-fadedtext and a chevron driven byactive.Notes for Reviewers
Worth a look:
attributes.childrenplacement. Great for a raw{...attributes}DOM spread, slightly odd when spreading into a Reshaped component'sattributesprop —Actionableoverrides it with its own children, so consumers readattributes.childrenand place it themselves, as the story does. Moving it to the second argument is the alternative.<Text color="disabled">the default trigger uses, so custom triggers aren't forced into a look. Consumers distinguish value from placeholder via thevaluein the second argument.width="trigger"and focus return both key off whatever element receivesattributes.ref— spreading onto an inner node gives a wrong dropdown width and breaks focus-on-close. Worth calling out in docs, withpositionRefas the escape hatch.idstill lands on the hidden input, so aFormControllabel'shtmlForpoints at something unfocusable. Pre-existing, but more visible with a custom trigger — passingid/aria-labelledbythrough the render arguments would be a follow-up.size,variant,iconandstartSlotare silently ignored with a custom trigger, matching how Popover treats its trigger.Verification:
tsc --noEmitclean;vitest --project=storybookpasses 581/582, the one failure being a pre-existingImage.storiesload assertion that also fails on a clean checkout here.Generated by Claude Code