Repository navigation
feat(mosaic): add Combobox.Clear and consistent trigger interactions - #10689
prestonwebdev wants to merge 4 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: d8b633b The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
8609fe4 to
6befb08
Compare
b2fadc3 to
72a726b
Compare
27cdde5 to
47f369d
Compare
|
alexcarpenter
left a comment
There was a problem hiding this comment.
Two structural concerns (a trigger that switches between toggle and clear, and combobox logic in InputGroup), plus a few smaller notes inline.
| const state = { open }; | ||
| const { render, clearable = false, clearLabel = 'Clear', ...otherProps } = props; | ||
| const { open, setOpen, focusInput, popupId, triggerRef, hasValue, clear } = useComboboxContext(); | ||
| const clearing = clearable && hasValue; |
There was a problem hiding this comment.
This makes one button mean two things, and the clearing flag then changes the ARIA, the label, the click handler and the icon.
- Typing opens the list (
handleInputChange→setOpenState(true)incombobox-root.tsx) and also switches this into clearing mode. So the trigger dropsaria-expanded/aria-controlsexactly when the popup is open. - While there is text, the trigger can't toggle the list, and the rotated chevron is never visible.
- A pointer resting on the chevron becomes a Clear button as soon as the user types.
Base UI (1.5.0 is in our node_modules) ships Combobox.Clear and Combobox.Trigger as separate parts. A Combobox.Clear that calls clear() and renders only while there is a value would delete clearing, the conditional ARIA spread, the aria-label override, clearLabel, and the stacked-icon opacity swap. The trigger would stay as it was. Any reason not to go that way?
There was a problem hiding this comment.
Good callout, fixed this one.
| clearing: (value: boolean): Record<string, string> | null => (value ? { 'data-clearing': '' } : null), | ||
| }, | ||
| props: mergeProps<'button'>(defaultProps, otherProps), | ||
| props: mergeProps<'button'>(defaultProps, clearing ? { ...otherProps, 'aria-label': clearLabel } : otherProps), |
There was a problem hiding this comment.
This silently replaces a consumer's own aria-label while clearing. A separate Clear part with its own label avoids the override entirely.
| return; | ||
| } | ||
| inputElementRef.current?.focus(); | ||
| inputElementRef.current?.click(); |
There was a problem hiding this comment.
This puts combobox behavior into InputGroup: every InputGroup input now gets a synthetic click() on a padding click. There are 8 consumers outside the component itself, including the sign-in password step and the API key dialog. Could the styled combobox own this instead? It already knows the group element, since it uses it as the anchor.
There was a problem hiding this comment.
Should be fixed as well
| onChange(event: React.ChangeEvent<HTMLInputElement>) { | ||
| handleInputChange(event.target.value); | ||
| }, | ||
| onClick() { |
There was a problem hiding this comment.
This rebuilds Floating UI's useClick, which popover, menu, select and drawer already use (popover-root.tsx:103, menu-root.tsx:129). Adding useClick(floatingContext, { enabled: openOnInputClick && !inline, toggle: false }) to the root's useInteractions would remove this handler and the openOnInputClick context field. getReferenceProps is already spread onto the input.
| focusInput(); | ||
| }, [focusInput, setInputValue, setSelectedValue]); | ||
|
|
||
| const hasValue = inputValue !== '' || selectedValue !== null; |
There was a problem hiding this comment.
inputValue and selectedValue are already on the context, so hasValue duplicates them. With a separate Clear part, this field isn't needed.
| handleSelect, | ||
| handleInputChange, | ||
| setOpen, | ||
| openOnInputClick: Boolean(props.openOnInputClick) && !props.inline, |
There was a problem hiding this comment.
See the useClick note on combobox-input.tsx. With that, this context field and its two memo deps go away.
| ) { | ||
| const defaultRender: ComboboxTriggerProps['render'] = | ||
| render ?? | ||
| (children === undefined |
There was a problem hiding this comment.
Whether children were passed now decides which element renders. With clearable plus custom children, nothing renders the X, but the data-clearing behavior still applies. This goes away if clearing is its own part.
There was a problem hiding this comment.
Made clearing independent. I got the idea originally from the geist design system, but what I updated it with follows more closely to what Kumo does. I think it works just as well, but feels a bit better than what we had originally imo.
| expect(screen.getByRole('button', { name: 'Toggle fruit options' })).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('keeps toggling the list when the trigger is not clearable', async () => { |
There was a problem hiding this comment.
The name says it keeps toggling the list, but the test only checks that there's no Clear button. Either assert that the trigger opens and closes the list, or rename the test.
47f369d to
f1914c8
Compare
… interactions Adds an opt-in `clearable` Combobox.Trigger that swaps the chevron for a clear button, dims trailing chevrons until hover, rotates the combobox and phone input chevrons while open, gives the Select trigger an input-style hover border, and makes the phone input's whole country box the click target. Clicking outside a combobox now blurs the input. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ories Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… a 132px minimum Hidden copies of each label share the value slot, so the trigger no longer changes width when the selection does. The outline trigger is at least 132px. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adds a Combobox.Clear part that renders only while there is text or a selection, so the trigger stays a plain toggle with its own label and ARIA state. Opening on input click now uses Floating UI's useClick, and the styled combobox, not InputGroup, opens the list when the group padding is clicked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
b10ebf2 to
d8b633b
Compare


Description
Stacked on #10688 (which is stacked on #10690): this diff shows only the interaction changes, so merge those first.
Combobox.Clearpart. It renders only while the input has text or a selection, and clicking it empties both, keeps focus on the input and callsonValueChange(null). It has its ownaria-label,Clearby defaultCombobox.Triggerstays a plain toggle. It keeps its own label and itsaria-expandedandaria-controlswhile there is text. With norenderthe styled trigger renders a MosaicButtonwith a chevron, andchildrenonly change its content<button>whenrenderwas not passed.prefers-reduced-motion). The Select does not rotatereturnFocus={false}on the popup focus manageruseClick(openOnInputClick, on by default in the styled Combobox). Clicking an InputGroup's padding opens the list, handled by the styled Comboboxrendertriggers like pagination's page size, which grow slightly to fit their longest option. Multiple selects and triggers with custom children are unchanged. The 132px minimum applies to the default outline trigger only.Combobox.ClearbesideCombobox.Trigger, and the docs table lists the new partComponents to check:
To review later: the field hover border color
light-dark(#bebebe, #525252)is now defined locally in four components:input,radioandcheckboxalready on main, plusselecthere. This follows the existing pattern rather than adding a token. Whether a shared token (for example--cl-color-border-strong) is worth it is a separate question, given the StyleX guide preferscolor-mix()at the call site over new tokens.The changeset is empty, like the other recent Mosaic PRs.
Before and after screenshots: link to be added
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
🤖 Generated with Claude Code