feat(dropdown-menu,select): persistent items, a search query hook, and one search implementation - #166
Merged
Conversation
…d one search implementation Adds what consumers were hand-rolling around in-menu search, and closes the remaining gaps between DropdownMenu and Select search: - A persistent prop on DropdownMenu items and separators, SelectItem and SelectSeparator: visible while searching, never a result, skipped by Enter - useDropdownMenuSearchQuery, for components inside the menu that act on the matches, such as a "Select all" scoped to the search - DropdownMenuSearch shows on open by default, as SelectSearch does - A searchable FilterDropdown keeps "Clear all" through a search, as a persistent item, instead of leaving an empty strip - DropdownMenu keeps the side it opened on while searching; the side lock and the interacted-since-open flag move into list-search, so Select and DropdownMenu share one implementation of each - Backspace on a focused DropdownMenu item edits the query, as in Select - Opening inside a Dialog reliably focuses the search: 4.3.1 recovered focus only on the menu's first focus event, which the input could claim first The search context and hook live in their own module so dropdown-menu.tsx exports only components. Docs, demos and the skill's name translation table cover the new API. 4.4.0, with a CHANGELOG entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…stray semicolon Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-up for the persistent items branch: - VersionSwitcher keeps alwaysVisible, so @eqtylab/docs behaves the same on the ^4.1.0 peer range it declares - useDropdownMenuSearchQuery moves into dropdown-menu.tsx, exported as form.tsx exports useFormField, and the barrel is plain export * again - useSelectSearchQuery, so Select has the same query hook; both wrap one useSearchQuery in list-search - One handleListKeyDown in list-search replaces the key handling that DropdownMenuContent and SelectContent each duplicated; the two content components now share one shape - useFilterableItem and useSearchSideLock take options objects rather than trailing positional arguments - Docs: the persistent items snippet matches its demo, the hooks are documented in prose like useFormField, and separator props follow the Slots order - Tests: ArrowUp to the search in DropdownMenu, Backspace in Select, the controlled-open interaction reset, and both hooks throwing outside their root; fixtures and helpers mirror each other across the two files - Comments trimmed to warnings; the skill table gains one row without re-padding Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
What
Follow-up to #163, from a review of Guardian Studio's adoption of 4.3.x. It adds what Studio was hand-rolling around in-menu search, and closes the gaps left between
DropdownMenuandSelectsearch.Added
persistentprop onDropdownMenuItem,DropdownMenuCheckboxItem,DropdownMenuRadioItem,DropdownMenuSeparator,SelectItemandSelectSeparator. A persistent item stays visible while searching but is never a result. It doesn't count towards the empty state or the result count, and Enter in the search box skips it.useDropdownMenuSearchQuery(), which returns{ query, isSearching, matches }to components inside aDropdownMenu. Studio's project picker uses it so "Select all" selects only the projects a search shows.useSelectSearchQuery(), the same hook for components inside aSelect.Changed
DropdownMenuSearchshows on open by default, asSelectSearchdoes.alwaysVisible={false}keeps reveal-on-typing.FilterDropdownkeeps "Clear all" visible during a search, as a persistent item. Previously the footer's padding stayed behind as an empty strip.Fixed
DropdownMenu, and soFilterDropdownandRadioDropdown, flipped sides mid-search, the same bug fix(select,filter-dropdown): keep Clear all's menu open and a searching Select's side #163 fixed forSelect. In Studio at 1280×560, the Activity Logs project filter opened above its trigger, dropped below on a query with no matches, and went back on clear.DropdownMenuitem focused, Backspace now edits the query, as inSelect.Dialognow reliably focuses the search. The 4.3.1 fix only reacted to the menu's first focus event, and the input could claim that before the dialog's focus trap pulled focus back out. In Studio's Manage Roles dialog it failed 2–4 of every 20 opens. It now pulls focus back whenever the menu itself is focused before any interaction, asSelectContentdoes.Structure
useSearchSideLock), the interacted-since-open flag, the query hook (useSearchQuery) and the key handling on list items (handleListKeyDown) now live inlist-search.ts, soSelectandDropdownMenushare one implementation of each.resetForOpenclears the lock and the flag.DropdownMenuContentandSelectContentnow have the same shape.useFilterableItemanduseSearchSideLocktake options objects rather than trailing positional arguments.react-hooks/refs.data-persistentis set after{...props}on every item, so consumer props can't override it.export { … }under aneslint-disable-next-line react-refresh/only-export-components, asform.tsxexportsuseFormField, so the barrel staysexport *.alwaysVisibleprops are gone fromFilterDropdownandRadioDropdown. The docsVersionSwitcherkeeps it:@eqtylab/docsdeclares a^4.1.0peer range, and before 4.4.0 the default is reveal-on-typing.Docs
DropdownMenuandSelectpages have Persistent items sections, each with a live demo and a snippet that matches it, and the props tables are updated in Slots order. The hooks are documented in prose, asuseFormFieldis, including that they throw outside their root.DropdownMenudemo variants are renamed to matchSelect(with-searchis the default,with-search-revealreveals on typing).VersionSwitcherand the test fixtures.Selectpage that onlySelectshows its search by default. Fixed theFilterDropdownpage, which still said "Clear all" closes the menu.Tests
63 passing (was 40). New coverage:
useDropdownMenuSearchQueryanduseSelectSearchQuery, including throwing outside their root;DropdownMenureveal-on-keystroke withalwaysVisible={false};DropdownMenuitem back to the search box;resetForOpen, viarenderHook, since jsdom has no layout;openbecomes true;Verification
pnpm format:check,pnpm lint,pnpm test,tsc --noEmit, and the ui and demo builds.4.4.0, with a CHANGELOG entry.
🤖 Generated with Claude Code