Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(SelectPanel): selected items should appear at the top #5867

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

francinelucca
Copy link
Member

@francinelucca francinelucca commented Apr 3, 2025

Closes https://github.com/github/primer/issues/2409

Adds logic to SelectPanel to automatically sort items, displaying the selected items first and then sorting alphabetically. The sorted list gets rearranged on:

  • Filter value is cleared
  • Items have changed
  • Panel is reopened

SingleSelect

Before

pre-changes single SelectPanel with one selected option and no division between selected and non-selected items

After

post-changes single SelectPanel with one selected option and line division between selected and non-selected items

MultiSelect

Before

pre-changes multi SelectPanel with two selected options and no division between selected and non-selected items

After

post-changes multi SelectPanel with two selected options and line division between selected and non-selected items

With Dividers

Before

pre-changes multi SelectPanel with item dividers, two selected options and no division between selected and non-selected items

After

post-changes multi SelectPanel with item dividers, two selected options and line division between selected and non-selected items

Full Screen

Before

pre-changes full-screen multi SelectPanel with two selected options and no division between selected and non-selected items

After

post-changes full-screen multi SelectPanel with two selected options and line division between selected and non-selected items

Changelog

New

  • CSS and sx styles for visual divider between selected and non-selected items
  • Added sorting logic to SelectPanel

Changed

  • Update VRT tests
  • Update Jest tests to account for new sorting logic
  • Updated test snapshots
  • Update ItemProps interface to extend from HtmlElement

Removed

  • Custom sorting logic from SelectPanel stories as it is no longer needed

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

SelectPanel stories

  • Ensure to turn on modern_action_list FF, as well with full_screen_on_narrow FF

Merge checklist

Copy link

changeset-bot bot commented Apr 3, 2025

🦋 Changeset detected

Latest commit: 334664d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Patch

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

@github-actions github-actions bot added the staff Author is a staff member label Apr 3, 2025
Copy link
Contributor

github-actions bot commented Apr 3, 2025

👋 Hi, this pull request contains changes to the source code that github/github depends on. If you are GitHub staff, we recommend testing these changes with github/github using the integration workflow. Thanks!

@github-actions github-actions bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Apr 3, 2025
Comment on lines +407 to +414
const itemASelected = selectedOnSort.some(item =>
Object.entries(item).every(([key, value]) => {
if (key === 'selected') {
return true
}
return itemA[key as keyof ItemProps] === value
}),
)
Copy link
Member Author

Choose a reason for hiding this comment

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

this seems expensive :(

Copy link
Member

Choose a reason for hiding this comment

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

Wanted to ask if it would be possible for the sort order to be maintained as a stateful value and compute it onClose or onOpen instead of having to run the sort every render?

It seemed like the logic that we have for this is something like:

  • When open, keep the ordering of items constant
  • When closing (or when re-opening), sort the items by:
    • selected items come first
      • sort selected items by group, if applicable
      • otherwise, sort by alphanumeric
    • otherwise, sort by alphanumeric

Does that track with what you're seeing?

Comment on lines +433 to +438
// Then order by text
if ((itemA.text ?? '') < (itemB.text ?? '')) {
return -1
} else if ((itemA.text ?? '') > (itemB.text ?? '')) {
return 1
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I baked in alphabetically ordering through text but now that I'm looking at the issue it doesn't really look like a requirement 🤔
I can remove and do selection-at-top only if this is not wanted/needed 👀
CC: @emilybrick

Copy link
Contributor

Choose a reason for hiding this comment

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

Consumers could want to sub-sort based on something besides alphabetical order. For example: sort most frequently used labels to the top.

I think we should remove this default alphanumeric sort.

Copy link
Member

Choose a reason for hiding this comment

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

Feel free to steal the alphanumeric stuff from data table btw if it's helpful! We have some sort helpers over there

Copy link
Contributor

github-actions bot commented Apr 3, 2025

size-limit report 📦

Path Size
packages/react/dist/browser.esm.js 104.74 KB (+0.4% 🔺)
packages/react/dist/browser.umd.js 105.08 KB (+0.33% 🔺)

@github-actions github-actions bot requested a deployment to storybook-preview-5867 April 3, 2025 04:09 Abandoned
},
} as ItemProps
})
.sort((itemA, itemB) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm coding this as the default behavior. Wondering if we should allow consumer to opt-out of this behavior or provide their custom sort function 👀
cc: @emilybrick

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel strongly that we should let consumers opt-out by providing their own sort function. I think DataTable has a nice API for handling this: https://github.com/primer/react/blob/main/packages/react/src/DataTable/column.ts#L59

@github-actions github-actions bot requested a deployment to storybook-preview-5867 April 3, 2025 04:17 Abandoned
@github-actions github-actions bot requested a deployment to storybook-preview-5867 April 3, 2025 04:22 Abandoned
@github-actions github-actions bot temporarily deployed to storybook-preview-5867 April 3, 2025 04:36 Inactive
Copy link
Member Author

Choose a reason for hiding this comment

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

visual updates are due to items being sorted now

@github-actions github-actions bot requested a deployment to storybook-preview-5867 April 7, 2025 20:53 Abandoned
@primer-integration
Copy link

🔴 golden-jobs completed with status failure.

@emilybrick
Copy link
Contributor

This looks good visually - though I think we should remove the border that appears under the selected items. The items being selected differentiates them enough from the normal list.
Screenshot 2025-04-08 at 12 40 11 PM

Copy link
Contributor

github-actions bot commented Apr 8, 2025

Uh oh! @emilybrick, at least one image you shared is missing helpful alt text. Check #5867 (comment) to fix the following violations:

  • Images should have meaningful alternative text (alt text) at line 2

Alt text is an invisible description that helps screen readers describe images to blind or low-vision users. If you are using markdown to display images, add your alt text inside the brackets of the markdown image.

Learn more about alt text at Basic writing and formatting syntax: images on GitHub Docs.

🤖 Beep boop! This comment was added automatically by github/accessibility-alt-text-bot.

Copy link
Contributor

@mperrotti mperrotti left a comment

Choose a reason for hiding this comment

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

I think it would feel better if the order of the button children matched the order of the selected items. How can we help consumers keep the ordering in sync?

Screenshot 2025-04-08 at 12 20 00 PM

},
} as ItemProps
})
.sort((itemA, itemB) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel strongly that we should let consumers opt-out by providing their own sort function. I think DataTable has a nice API for handling this: https://github.com/primer/react/blob/main/packages/react/src/DataTable/column.ts#L59

Comment on lines +433 to +438
// Then order by text
if ((itemA.text ?? '') < (itemB.text ?? '')) {
return -1
} else if ((itemA.text ?? '') > (itemB.text ?? '')) {
return 1
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Consumers could want to sub-sort based on something besides alphabetical order. For example: sort most frequently used labels to the top.

I think we should remove this default alphanumeric sort.

@@ -213,6 +213,22 @@
}
}

&:where([data-last-selected]:not(:last-of-type)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this CSS-only solution for visually separating selected and unselected items, but where did this design change come from? We didn't do this before.

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +220 to +228
position: absolute;
bottom: 0;
width: calc(100% + var(--base-size-16));
margin-left: calc(-1 * var(--base-size-8));
content: '';
/* stylelint-disable-next-line primer/borders */
border-bottom: 1px solid var(--borderColor-default);
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

A shorter way to do this would be:

Suggested change
position: absolute;
bottom: 0;
width: calc(100% + var(--base-size-16));
margin-left: calc(-1 * var(--base-size-8));
content: '';
/* stylelint-disable-next-line primer/borders */
border-bottom: 1px solid var(--borderColor-default);
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
position: absolute;
content: '';
inset: 100% calc(-1 * var(--base-size-8));
background-color: var(--borderColor-default);
height: var(--borderWidth-default);

@@ -213,6 +213,22 @@
}
}

&:where([data-last-selected]:not(:last-of-type)) {
padding-bottom: var(--base-size-4);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we reduce the padding for these items?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm actually adding padding to separate from the divider line, this is only added to the last selected item for such purposes

Copy link
Member

@joshblack joshblack left a comment

Choose a reason for hiding this comment

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

Just had some initial comments/questions, let me know if they make sense lol. Also happy to pair on this if you want!

}
}, [selected, setSelectedOnSort])

useEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

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

Some of these places where we're using an effect to reset the state might not be needed, this section from the docs came to mind: https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes

Maybe for some of them we could run after a user action (like when the overlay is closed / opened or when onFilterValue is changed so that filterValue is empty)

@@ -324,40 +336,146 @@ export function SelectPanel({
}
}, [placeholder, renderAnchor, selected])

const resetSort = useCallback(() => {
Copy link
Member

Choose a reason for hiding this comment

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

I think if we're not passing this as an argument to the effects below then we should not use useCallback here

Comment on lines +407 to +414
const itemASelected = selectedOnSort.some(item =>
Object.entries(item).every(([key, value]) => {
if (key === 'selected') {
return true
}
return itemA[key as keyof ItemProps] === value
}),
)
Copy link
Member

Choose a reason for hiding this comment

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

Wanted to ask if it would be possible for the sort order to be maintained as a stateful value and compute it onClose or onOpen instead of having to run the sort every render?

It seemed like the logic that we have for this is something like:

  • When open, keep the ordering of items constant
  • When closing (or when re-opening), sort the items by:
    • selected items come first
      • sort selected items by group, if applicable
      • otherwise, sort by alphanumeric
    • otherwise, sort by alphanumeric

Does that track with what you're seeing?

Comment on lines +433 to +438
// Then order by text
if ((itemA.text ?? '') < (itemB.text ?? '')) {
return -1
} else if ((itemA.text ?? '') > (itemB.text ?? '')) {
return 1
}
Copy link
Member

Choose a reason for hiding this comment

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

Feel free to steal the alphanumeric stuff from data table btw if it's helpful! We have some sort helpers over there

@francinelucca
Copy link
Member Author

Wanted to ask if it would be possible for the sort order to be maintained as a stateful value and compute it onClose or onOpen instead of having to run the sort every render?
It seemed like the logic that we have for this is something like:

When open, keep the ordering of items constant
When closing (or when re-opening), sort the items by:
selected items come first
sort selected items by group, if applicable
otherwise, sort by alphanumeric
otherwise, sort by alphanumeric
Does that track with what you're seeing?

A big one is we want to reset the sorting when the filter value is cleared, we also do want to sort on the first open, so I don't think it's as simple as onClose/onOpen. There's definitely some cleanup we might be able to do if we keep a state of the mapped items though 👀

@francinelucca
Copy link
Member Author

#5867 (comment)

@emilybrick double-checking, you're saying remove the line separation entirely, just keep the ordering on top functionality?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration-tests: failing Changes in this PR cause breaking changes in gh/gh staff Author is a staff member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants