Skip to content

Added top 5 suggestion buttons to selectors - #728

Open
kevinmdd wants to merge 2 commits into
masterfrom
feat-show-top-relation-filter-options
Open

Added top 5 suggestion buttons to selectors#728
kevinmdd wants to merge 2 commits into
masterfrom
feat-show-top-relation-filter-options

Conversation

@kevinmdd

Copy link
Copy Markdown
Contributor

Fixes #672

Summary: Added EntityFilterSelector and EntityFilterSuggestionButtons components to show top 5 entity suggestions as toggleable buttons above the typeahead for Territory, Language, Language Family, and Writing System filters.

Changes

  • User experience
    • Territory, Language, Language Family, and Writing System filters now show the top 5 results as clickable toggle buttons above the typeahead
    • Selecting a suggestion from the typeahead highlights the matching button if it's in the top 5
  • Logical changes
    • EntityFilterSuggestionButtons fetches top 5 suggestions on load and renders them as FilterList style buttons
    • EntityFilterSelector manages shared state and filtered suggestions for the typeahead
    • Selected value is reflected in button highlight state
  • Data
    • No changes
  • Refactors
    • TerritoryFilterSelector, LanguageFilterSelector, LanguageFamilyFilterSelector, WritingSystemFilterSelector all simplified to use EntityFilterSelector
    • Duplicate label/input/provider structure removed from each selector

Out of scope/Future work: Discuss whether top 5 languages should be shown in the typeahead or just shown as buttons.

Test Plan and Screenshots

How to test the changes in this PR: How to test: Run npm run dev, open the Filter panel, verify Territory/Language/Writing System/Language Family filters show 5 clickable buttons above the typeahead. Select one from the typeahead and verify the matching button highlights.

Description of Changes Screenshot Before Screenshot After
Top suggestions shown as toggleable buttons above the typeahead image image

Feel free to check off or just delete items in this section as you have completed them.

Summary

  • Clear description of what and why
  • Scope kept focused; note follow-ups if any
  • Set yourself as assignee
  • Mention the issue (usually by writing "Fixes #ISSUE_NUMBER" or "Closes #ISSUE_NUMBER")

Testing

  • npm run lint
  • npm run build
  • npm run test
  • npm run dev -- tried out the website directly
    • Include screenshots as noted below

Changes

Visual changes

  • Add screenshots to the table template at the top of this file. You can include images inside the table
    • Drag and drop images in the GitHub PR comment box to upload screenshots

Data changes

  • TSV, SVG, etc. edits in public/data/
  • Corresponding readmes updated in public/data/
  • Load/connect/compute updates in src/features/data/ including how we aggregate data or compute derived values

Internal changes

  • Logical changes
  • Refactors, moving files around
  • If you notice any changes that require explanations, make sure to include the explanations in the code as well.

Docs

  • Code is self-documenting, or if not, comments are added where needed.
  • Updated markdown readme files documenting how the code behaves or how to develop in case there are any relevant changes to make.

@kevinmdd kevinmdd self-assigned this Jul 24, 2026
@kevinmdd
kevinmdd requested a review from a team as a code owner July 24, 2026 01:48
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Pages preview

Comment on lines +30 to +36
onSubmit,
value,
pageParameter,
}) => {
const { display: inheritedDisplay } = useSelectorDisplay();
const display = manualDisplay ?? inheritedDisplay;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing in onSubmit, the function is always the same (just dependent on pageParameter, which is also passed in).

Also, let's change it so a repeat click turns it off.

Suggested change
onSubmit,
value,
pageParameter,
}) => {
const { display: inheritedDisplay } = useSelectorDisplay();
const display = manualDisplay ?? inheritedDisplay;
value,
pageParameter,
}) => {
const { display: inheritedDisplay } = useSelectorDisplay();
const display = manualDisplay ?? inheritedDisplay;
const onSubmit = useCallback(
(value: string) => {
if (params[pageParameter] === value) params.updatePageParams({ [pageParameter]: '' });
else params.updatePageParams({ [pageParameter]: value });
},
[params.updatePageParams, params[pageParameter], pageParameter],
);

Comment on lines +17 to 19
import EntityFilterSelector from './EntityFilterSelector';

type Props = { display?: SelectorDisplay };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the display parameter for all of these -- you force them all to be FilterList anyway

Suggested change
import EntityFilterSelector from './EntityFilterSelector';
type Props = { display?: SelectorDisplay };
import EntityFilterSelector from './EntityFilterSelector';
type Props = { };

Comment on lines +37 to +58
return (
<SelectorDisplayProvider display={display}>
<div className="selector filterList" style={{ display: 'flex', flexWrap: 'wrap' }}>
<SelectorLabel label={selectorLabel} description={selectorDescription} />
<EntityFilterSuggestionButtons
getSuggestions={getSuggestions}
onSubmit={onSubmit}
value={value}
/>
<div>
<TextInput
inputStyle={{ minWidth: '8em' }}
placeholder="Name or code"
getSuggestions={getSuggestions}
onSubmit={onSubmit}
pageParameter={pageParameter}
value={value}
/>
</div>
</div>
</SelectorDisplayProvider>
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to contain it in a SelectorDisplayProvider because you already used the filterList className and you don't need the extra flex styling it is already comes from the selector class.

With this change, it will clean up the code, but we need to update ClearButton in TextInput.tsx where it does display === SelectorDisplay.ButtonList and make it display === SelectorDisplay.ButtonList || display === SelectorDisplay.FilterList so the clear button formats correctly.

Also, in general, I regret doing so much manual CSS for the selectors, ideally this should be in .css files but I wouldn't worry about that refactor.

Suggested change
return (
<SelectorDisplayProvider display={display}>
<div className="selector filterList" style={{ display: 'flex', flexWrap: 'wrap' }}>
<SelectorLabel label={selectorLabel} description={selectorDescription} />
<EntityFilterSuggestionButtons
getSuggestions={getSuggestions}
onSubmit={onSubmit}
value={value}
/>
<div>
<TextInput
inputStyle={{ minWidth: '8em' }}
placeholder="Name or code"
getSuggestions={getSuggestions}
onSubmit={onSubmit}
pageParameter={pageParameter}
value={value}
/>
</div>
</div>
</SelectorDisplayProvider>
);
return (
<div className="selector filterList">
<SelectorLabel label={selectorLabel} description={selectorDescription} />
<EntityFilterSuggestionButtons
getSuggestions={getSuggestions}
onSubmit={onSubmit2}
value={value}
/>
<TextInput
inputStyle={{ minWidth: '8em' }}
placeholder="Name or code"
getSuggestions={getSuggestions}
onSubmit={onSubmit2}
pageParameter={pageParameter}
value={value}
/>
</div>
);

Comment on lines +27 to +40
return (
<SelectorDisplayProvider display={SelectorDisplay.FilterList}>
<div className="selector filterList">
{suggestions.map((suggestion) => (
<SelectorOption
key={suggestion.searchString}
option={suggestion.searchString}
onClick={onSubmit}
isSelected={suggestion.searchString === value}
getOptionLabel={() => suggestion.label}
/>
))}
</div>
</SelectorDisplayProvider>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As previously mentioned, you have lots of redundant containers. No worries, there is a lot to learn about these code patterns.

Suggested change
return (
<SelectorDisplayProvider display={SelectorDisplay.FilterList}>
<div className="selector filterList">
{suggestions.map((suggestion) => (
<SelectorOption
key={suggestion.searchString}
option={suggestion.searchString}
onClick={onSubmit}
isSelected={suggestion.searchString === value}
getOptionLabel={() => suggestion.label}
/>
))}
</div>
</SelectorDisplayProvider>
return suggestions.map((suggestion) => (
<SelectorOption
key={suggestion.searchString}
option={suggestion.searchString}
onClick={onSubmit}
isSelected={suggestion.searchString === value}
getOptionLabel={() => suggestion.label}
/>
))

Comment on lines +19 to +25

// When component loads, call getSuggestions('') and store results
useEffect(() => {
getSuggestions('').then((results) => {
setSuggestions(results.slice(0, 5));
});
}, [getSuggestions]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is not pretty, but have the effect also respond to the other filters changing. It doesn't always work but you can see for example the "Written In" filter changes the languages that appear.

Image
Suggested change
// When component loads, call getSuggestions('') and store results
useEffect(() => {
getSuggestions('').then((results) => {
setSuggestions(results.slice(0, 5));
});
}, [getSuggestions]);
const {
languageFamilyFilter,
languageFilter,
languageScopes,
territoryFilter,
territoryScopes,
writingSystemFilter,
} = usePageParams();
// When component loads, call getSuggestions('') and store results
useEffect(() => {
getSuggestions('').then((results) => {
setSuggestions(results.slice(0, 5));
});
}, [
getSuggestions,
languageFamilyFilter,
languageFilter,
languageScopes,
territoryFilter,
territoryScopes,
writingSystemFilter,
]);

@conradarcturus conradarcturus left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for messing the git history -- I was testing the PR and accidentally pushed when I was trying to pull >_< oh well, I updated the screenshot baselines.

Nonetheless it looks great! thanks for making the update. There are code changes to make but its pretty solid.

Right now you have a lot of manual code passing down a display value. Since they are passed in a context you don't need to pass down display variables. Can you update AllApplicableFilterSelectors in FilterSelector to just have 1 SelectorDisplayProvider (replacing <>) that passes down the FilterList display option instead of passing it as a variable everywhere & re-specifying SelectorDisplayProvider.

))}
</div>
</SelectorDisplayProvider>
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also indent these, you can pass in the attribute optionStyle={{ marginLeft: '1em' }} to SelectorOption

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Filters: Show top relation filter options

2 participants