Skip to content

feat(ui): add multi-select and bulk actions for packages#1672

Merged
serhalp merged 61 commits intonpmx-dev:mainfrom
MatteoGabriele:feat/action-bar
Mar 17, 2026
Merged

feat(ui): add multi-select and bulk actions for packages#1672
serhalp merged 61 commits intonpmx-dev:mainfrom
MatteoGabriele:feat/action-bar

Conversation

@MatteoGabriele
Copy link
Copy Markdown
Member

@MatteoGabriele MatteoGabriele commented Feb 26, 2026

🔗 Linked issue

resolves #1509

🧭 Context

Added a multi-select feature to the search page that allows users to select multiple packages and perform bulk actions on them. Currently supports comparing selected packages, with the possibility of adding more actions in the future.

📚 Description

  • Selection UI: Added checkboxes to package cards that appear on hover. Selected cards are visually indicated with a border highlight and checkbox state.
  • Persistent counter: New "View selected (X)" button in the toolbar shows active selections and navigates to a dedicated view for managing them.
  • Floating action bar: When items are selected, a floating action bar appears at the bottom with the selection count, primary action (Compare), and clear button.
  • Selection state management: Uses composable to maintain selections across view changes (card/table/selections view), allowing users to continue browsing while keeping their selections.
  • Selection view: It's a separate component view that retrieves each saved component similar to the Compare page request logic.
  • Accessibility: Includes aria-live announcements for selection changes and keyboard shortcuts ("b" key) to focus the action bar.

At the moment, the logic is locked at a maximum of 4 selectable items. This won't scale, but for now, to reduce complexity, it will mimic what's needed by the Compare page, which is the only current functionality available in the multi-select.
My take is to re-think this along the way, when and if another action gets added.

Screen.Recording.2026-02-27.at.18.33.58.mov

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 16, 2026 9:50pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 16, 2026 9:50pm
npmx-lunaria Ignored Ignored Mar 16, 2026 9:50pm

Request Review

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 26, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/en.json Source changed, localizations will be marked as outdated.
i18n/locales/it-IT.json Localization changed, will be marked as complete. 🔄️
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 60.74766% with 42 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/composables/usePackageSelection.ts 34.21% 19 Missing and 6 partials ⚠️
app/components/Package/ActionBar.vue 47.36% 7 Missing and 3 partials ⚠️
app/components/Package/ListToolbar.vue 50.00% 3 Missing and 1 partial ⚠️
app/router.options.ts 33.33% 2 Missing ⚠️
app/components/Package/TableRow.vue 75.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Member

@knowler knowler left a comment

Choose a reason for hiding this comment

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

Looks good to me from an accessibility perspective. We can always iterate/tweak things later. Others should review the Vue/JS before we merge.

Copy link
Copy Markdown
Member

@serhalp serhalp left a comment

Choose a reason for hiding this comment

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

This is an awesome feature! It looks and feels great. Just found a few issues.

Comment on lines +15 to +18
// Don't scroll for other param changes (filters, pagination, etc.)
if (to.path === from.path) {
return false
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe this breaks the hash anchor navigation logic just below this, by returning too early before we can check it.

My hunch is always to add a test for the behaviour that almost broke when this occurs, as it's a sign we're missing valuable coverage!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Navigation with hashes works. I can add tests for the scroll behavior: should I go with e2e or unit tests?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I must admit I don't understand why navigation from /foo#bar to /foo#baz still works based on reading this code, but it does seem to work!


I would vote for some sort of a test with a browser. (We can add the test in a follow-up PR, doesn't need to block this!)

const selectedPackagesParam = useRouteQuery<string>('selection', '', { mode: 'push' })
const showSelectionViewParam = useRouteQuery<string>('view', '', { mode: 'push' })

// Parse URL param into array of package names
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 I'm all for the URL-as-source-of-truth-for-state school of thought, but I wonder if selection within search results falls into the ephemeral state bucket that should not be in the URL and consequently in the browser navigation history?

When I play around with it, it admittedly looks cool to hit back and forward across those selections/deselections, but it feels surprising.

I'd be curious to hear other opinions, but my hunch would be not to include it in the URL.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I need to include it in the URL; otherwise, I won't be able to refresh and get the list from your selection. This is especially helpful if you refresh, navigate to other pages, and then come back. It also makes the selection page shareable. I agree that perhaps it shouldn't be pushed into history, but it can be replaced. Let me know what you think.
I have already made this change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah yeah, the selection page feels different to me than the existing search results page with selections.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Is that a good thing or a bad thing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry, that wasn't very clear. I meant that on the new selection view I agree selections should be part of the URL and navigation history, but on the normal full search results view selection state feels more ephemeral. 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I tried using useState in a previous version, but the saved state felt wrong, and the data would appear when you didn't expect it to. I believe this one is best suited to a URL: I don't think this functionality fits any other storage options that don't require complex babysitting. URL, in a way, is straightforward, managed by navigation, and predictable. You go back and find what you left; you arrive there from another route and see a clean slate. That said, if you have another idea, I'm happy to try it out and see how it feels.

Copy link
Copy Markdown
Member

@serhalp serhalp left a comment

Choose a reason for hiding this comment

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

🚀

Comment on lines +15 to +18
// Don't scroll for other param changes (filters, pagination, etc.)
if (to.path === from.path) {
return false
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I must admit I don't understand why navigation from /foo#bar to /foo#baz still works based on reading this code, but it does seem to work!


I would vote for some sort of a test with a browser. (We can add the test in a follow-up PR, doesn't need to block this!)

@MatteoGabriele
Copy link
Copy Markdown
Member Author

@serhalp I made the changes, and someone just added a preserveScrollOnQuery feature to the router, so I replaced my logic with that to prevent the page from scrolling up on query update. Perfect timing.

Copy link
Copy Markdown
Member

@serhalp serhalp left a comment

Choose a reason for hiding this comment

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

Thanks for all your work on this! Can't wait to see all the bulk actions we come up with later

@serhalp serhalp added this pull request to the merge queue Mar 17, 2026
Merged via the queue into npmx-dev:main with commit c5202d0 Mar 17, 2026
20 checks passed
@github-actions github-actions bot mentioned this pull request Mar 17, 2026
@mbtools
Copy link
Copy Markdown

mbtools commented Mar 20, 2026

very cool feature. thx! 🏆

@coderabbitai coderabbitai bot mentioned this pull request Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ux Related to wider UX decisions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Select packages to compare from the search results view

4 participants