Skip to content

Hosting dashboard: Add a custom empty state to the site list #104544

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

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from

Conversation

p-jackson
Copy link
Member

@p-jackson p-jackson commented Jul 2, 2025

Part of DOTDASH-14

Proposed Changes

Note

In light of ARC-422 (merging dataviews back to core) there are parts of this PR that will need to reworked.

  • Creates new <DataViewsEmptyState>, based on this experimental DS component rykv85Ii9RkvP504dtqqaW-fi-158_50621 QOBjujZah0UlGDDdLKZhzi-fi-2587_192468
  • Adds an empty prop to <DataViews> to allow for arbitrary empty states to be added
  • Site list shows a DataViewsEmptyState in its DataViews if the user has no sites or if the filter/search returns no results
  • "Add new site" button opens the same modal as the header action button
  • "Clear search" button clears just the text search, not all filters
    • @matt-west should the button actually reset all filters to default?
  • Tweaks one of the data view padding styles that was missed in Dataviews: Use CSS to automatically to apply consistent 24px paddings when the DataViews component is placed inside a Card #104479
    • The data view's inline padding is 24px when viewed within a card. But that change should also apply to the loading spinner and empty state containers
  • Fixes sentence casing of "Add new site" button and modal title

Not in this PR

  • Adding a min-height to the dataview as discussed here p1751440760784079/1751428514.884869-slack-C05QAFZSFTL

New user with no sites

This can happen for example if a user created a WordPress.com account at some stage just to comment on a blog. And they come to the dashboard at a later date.

CleanShot 2025-07-04 at 16 19 58@2x

Text search returns no results (grid mode)

CleanShot 2025-07-04 at 16 17 12@2x

Text search returns no results (table mode)

CleanShot 2025-07-04 at 16 17 02@2x

@matt-west the text wrapping here doesn't match Figma, despite the fact I'm using the same container size. But I don't think it makes sense to sweat over the wrapping. The user's search term appears in the copy so there's not much we can do about it.

A filter returns no results, but there is no search term

CleanShot 2025-07-04 at 16 19 06@2x

Grid mode on phone

Table mode on phone

Why are these changes being made?

When a data view is empty, it can be useful to give the user a call-to-action, so they can start populating the data view.

The "empty state" component is marked as experimental, so I've added it to the dashboard code rather than promoting it to @automattic/components. The component also needs to have custom padding based on the type of data view, which is why I thought it was best to specifically call it DataViewsEmptyState for now.

Testing Instructions

  • Test the site list at calypso.localhost:3000/v2
  • Test the backport at calypso.localhost:3000/sites?flags=dashboard/v2/backport/sites-list

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • Have you tested accessibility for your changes? Ensure the feature remains usable with various user agents (e.g., browsers), interfaces (e.g., keyboard navigation), and assistive technologies (e.g., screen readers) (PCYsg-S3g-p2).
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages.
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

@p-jackson p-jackson self-assigned this Jul 2, 2025
Copy link

github-actions bot commented Jul 2, 2025

@matticbot
Copy link
Contributor

matticbot commented Jul 2, 2025

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

App Entrypoints (~44 bytes added 📈 [gzipped])

name                    parsed_size           gzip_size
entry-dashboard-dotcom       +134 B  (+0.0%)      +44 B  (+0.0%)
entry-dashboard-a4a          +134 B  (+0.0%)      +44 B  (+0.0%)
entry-subscriptions           +68 B  (+0.0%)      +23 B  (+0.0%)
entry-stepper                 +68 B  (+0.0%)      +23 B  (+0.0%)
entry-reauth-required         +68 B  (+0.0%)      +23 B  (+0.0%)
entry-main                    +68 B  (+0.0%)      +23 B  (+0.0%)
entry-login                   +68 B  (+0.0%)      +23 B  (+0.0%)
entry-domains-landing         +68 B  (+0.0%)      +23 B  (+0.0%)
entry-browsehappy             +68 B  (+0.0%)      +23 B  (+0.0%)

Common code that is always downloaded and parsed every time the app is loaded, no matter which route is used.

Sections (~46 bytes added 📈 [gzipped])

name                parsed_size           gzip_size
staging-site             +150 B  (+0.0%)      +46 B  (+0.0%)
sites-dashboard          +150 B  (+0.0%)      +46 B  (+0.0%)
site-settings            +150 B  (+0.0%)      +46 B  (+0.0%)
site-performance         +150 B  (+0.0%)      +46 B  (+0.0%)
site-monitoring          +150 B  (+0.0%)      +46 B  (+0.0%)
site-logs                +150 B  (+0.0%)      +46 B  (+0.0%)
plans                    +150 B  (+0.0%)      +46 B  (+0.0%)
overview                 +150 B  (+0.0%)      +46 B  (+0.0%)
hosting                  +150 B  (+0.0%)      +46 B  (+0.0%)
github-deployments       +150 B  (+0.0%)      +46 B  (+0.0%)
domains                  +150 B  (+0.0%)      +46 B  (+0.0%)

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.

Async-loaded Components (~35 bytes added 📈 [gzipped])

name                       parsed_size           gzip_size
async-load-v-2-sites-list        +66 B  (+0.1%)      +35 B  (+0.1%)

React components that are loaded lazily, when a certain part of UI is displayed for the first time.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@p-jackson p-jackson force-pushed the DOTDASH-14/add-empty-site-list-state branch from e153e47 to 659ce4e Compare July 4, 2025 03:50
@matticbot
Copy link
Contributor

matticbot commented Jul 4, 2025

This PR modifies the release build for the following Calypso Apps:

For info about this notification, see here: PCYsg-OT6-p2

  • help-center

To test WordPress.com changes, run install-plugin.sh $pluginSlug DOTDASH-14/add-empty-site-list-state on your sandbox.

@p-jackson p-jackson force-pushed the DOTDASH-14/add-empty-site-list-state branch from 659ce4e to 96458db Compare July 4, 2025 04:33
@p-jackson p-jackson marked this pull request as ready for review July 4, 2025 04:57
@p-jackson p-jackson requested review from a team and youknowriad as code owners July 4, 2025 04:57
@matticbot matticbot added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Jul 4, 2025
@arthur791004
Copy link
Contributor

We have to split the changes of the Dataviews to Gutenberg.

@@ -66,10 +67,27 @@ export default function Sites() {
const { data: filteredData, paginationInfo } = filterSortAndPaginate( sites ?? [], view, fields );
const [ isModalOpen, setIsModalOpen ] = useState( false );

const hasFilterOrSearch = ( view.filters && view.filters.length > 0 ) || view.search;

const emptyHeading = hasFilterOrSearch ? __( 'No sites found' ) : __( 'No sites' );
Copy link
Member Author

Choose a reason for hiding this comment

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

When choosing the copy to show, having some sort of filter takes precedence over having no sites. That might be counter intuitive.

My first instinct was that "No sites" should take precedence. If a user has zero sites and they type in a search, surely the more important message is that the user has no sites.

But is a little more nuanced. By default we do not show "hidden" sites. But then when the user starts typing, we assume that maybe they're looking for a specific hidden site and so start showing them.

So when the user sees the "No sites" message, we don't actually know whether the user has literally no sites 😳

And when the user starts searching, they might be looking for a hidden site, and so it is better to show the message "No sites found".

@p-jackson
Copy link
Member Author

We have to split the changes of the Dataviews to Gutenberg.

Yes, I added a note about that in the PR description. I'm hoping to get some feedback about the empty state, but ultimately I need to open some GB PRs to get this whole thing merged.

Tweaks one of the data view padding styles that was missed in #104479

@arthur791004 Since this PR also adds an extra style rule that should have been in #104479, do you think it makes sense to just merge that in to wp-calypso so all those style changes get considered as one when merging into upstream?

@p-jackson p-jackson changed the title Adds empty prop to DataViews component for rendering empty states Hosting dashboard: Add a custom empty state to the site list Jul 4, 2025
@arthur791004
Copy link
Contributor

so all those style changes get considered as one when merging into upstream?

It has been merged into upstream via WordPress/gutenberg#70567, so any further changes should now be made directly in the upstream 😅

Copy link
Contributor

@matt-west matt-west left a comment

Choose a reason for hiding this comment

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

Thanks @p-jackson!

should the button actually reset all filters to default?

Yes, because you could see the empty state after only applying filters. Ideally the button text would be conditional, so it would say Clear search, Clear filters, or Clear search and filters depending on what the user has applied.


the text wrapping here doesn't match Figma, despite the fact I'm using the same container size.

We could use text-wrap: balance; here to make the wrapping feel a bit better.

Screenshot 2025-07-04 at 10 04 32

We should drop the column headers when the empty state is shown.

Screenshot 2025-07-04 at 10 05 16

Let’s centre the empty state in the container. At the moment it appears to be top-aligned.

Screenshot 2025-07-04 at 10 08 10

I noticed a small error in the illustration which has now been fixed in Figma. Can you please grab the updated version here.

Screenshot 2025-07-04 at 10 13 14

@p-jackson
Copy link
Member Author

Adding an empty prop in GB WordPress/gutenberg#70637

@p-jackson
Copy link
Member Author

Adding the padding fix to GB WordPress/gutenberg#70638

@p-jackson p-jackson force-pushed the DOTDASH-14/add-empty-site-list-state branch from 96458db to d39cbee Compare July 8, 2025 05:23
@p-jackson
Copy link
Member Author

Abandoned WordPress/gutenberg#70637
Opened WordPress/gutenberg#70867 to create a consistent customisable empty state in all dataviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants