Skip to content

Dashboard v2: implement breadcrumbs #103294

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 1 commit into
base: trunk
Choose a base branch
from
Open

Dashboard v2: implement breadcrumbs #103294

wants to merge 1 commit into from

Conversation

fushar
Copy link
Contributor

@fushar fushar commented May 9, 2025

Part of DOTCOM-13067

Proposed Changes

This PR implements breadcrumbs and show it on the child pages of Settings as per design (p1746709722501659/1746709518.587029-slack-C08JZTRS6NA)

Before After
image image

Implementation details

  • Added a new optional breadcrumbItemLabel prop to the route's context. Any route can append this prop by returning this in its loader().-
  • Make siteSettingsSubscriptionGiftingRoute (and future setting routes) as a child of siteSettingsRoute.
  • Return the breadcrumb item label in siteSettingsRoute.
  • Show the breadcrumbs inside <PageLayout />.
    • Why here, you may ask? Because the breadcrumbs must follow the page's width so that it is aligned with the page's title...

Open discussions

  • Clicking the breadcrumb items will trigger a page reload. This is because it's a regular link, not a router link. I don't have any good idea yet how to solve it. Need help from @Automattic/io.
  • For consistency, should I move <PageLayout /> to app/ not components/? Because it is not a "pure" component anymore; it contains logic (breadcrumbs).

Testing instructions

  1. Go to /v2/sites
  2. Click any site with paid plan.
  3. Go to Settings
  4. Go to Accept a gift subscription
  5. Verify you see the breadcrumbs

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 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)?

@matticbot
Copy link
Contributor

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

App Entrypoints (~14219 bytes removed 📉 [gzipped])

name                    parsed_size           gzip_size
entry-dashboard-dotcom    +101872 B  (+9.1%)   +29413 B  (+8.5%)
entry-dashboard-a4a       +101872 B  (+9.2%)   +29667 B  (+8.7%)
entry-login                  -914 B  (-0.0%)     +160 B  (+0.0%)
entry-stepper                +395 B  (+0.0%)      -23 B  (-0.0%)
entry-main                   +242 B  (+0.0%)     -216 B  (-0.0%)
entry-domains-landing        +213 B  (+0.0%)      +26 B  (+0.0%)
entry-browsehappy            +213 B  (+0.1%)      +26 B  (+0.0%)
entry-subscriptions          +200 B  (+0.0%)       -6 B  (-0.0%)
entry-reauth-required         -31 B  (-0.0%)     -316 B  (-0.1%)

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

Sections (~3467 bytes added 📈 [gzipped])

name                        parsed_size           gzip_size
jetpack-app                      +215 B  (+0.1%)     +832 B  (+0.7%)
migrate                          +196 B  (+0.1%)     +564 B  (+0.5%)
plugins                          +188 B  (+0.0%)     +438 B  (+0.1%)
signup                           +172 B  (+0.1%)       +6 B  (+0.0%)
plans                            +122 B  (+0.0%)      +77 B  (+0.0%)
google-my-business               +103 B  (+0.0%)      +30 B  (+0.0%)
async-step-unified-domains        +90 B  (+0.0%)     -290 B  (-0.1%)
async-step-unified-plans          +19 B  (+0.0%)     -275 B  (-0.1%)
a8c-for-agencies-plugins          -13 B  (-0.0%)      +47 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 (~1565 bytes added 📈 [gzipped])

name                                                            parsed_size           gzip_size
async-load-signup-steps-domains                                       -82 B  (-0.0%)     -294 B  (-0.1%)
async-load-signup-steps-plans-theme-preselected                       -62 B  (-0.0%)     -287 B  (-0.2%)
async-load-signup-steps-plans                                         -62 B  (-0.0%)     -282 B  (-0.2%)
async-load-signup-steps-woocommerce-install-step-business-info        +26 B  (+0.0%)     +107 B  (+0.3%)

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.

@matticbot
Copy link
Contributor

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

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

  • blaze-dashboard
  • notifications
  • wpcom-block-editor

To test WordPress.com changes, run install-plugin.sh $pluginSlug v2-breadcrumbs on your sandbox.

@fushar fushar assigned fushar and unassigned fushar May 9, 2025
@fushar fushar marked this pull request as ready for review May 9, 2025 15:32
@fushar fushar requested review from youknowriad and a team as code owners May 9, 2025 15:32
@matticbot matticbot added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels May 9, 2025
loader: async ( { params: { siteSlug } } ) => {
await queryClient.ensureQueryData( siteSettingsQuery( siteSlug ) );
return {
breadcrumbItemLabel: __( 'Settings' ),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this have to be in the loader? Can't it be added as route meta or something?

Copy link
Contributor Author

@fushar fushar May 9, 2025

Choose a reason for hiding this comment

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

It needs to call __(), so it can't be a static data. Also, I imagine other screens might have dynamic breadcrumb item label such as plugin name in the Plugins dashboard (it's the case now in v1 if you go to https://wordpress.com/plugins)

BTW as per docs, meta has been replaced with https://tanstack.com/router/latest/docs/framework/react/guide/static-route-data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is my conclusion after spending a day reading the docs and consulting with AI 🥲 but I'm open if you have better ideas! This is literally my first project with TanStack stack.

Copy link
Contributor

Choose a reason for hiding this comment

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

__() can be called anywhere, it doesn't have to be called at render time.

But if you really want, you could create a function that maps a route to a label:

function getRouteBreadcrumbLabel( route ) {
    switch ( route.path ) {
        case 'setting': return __( 'Settings' );
    }
}

The loader function doesn't seem meant for this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would that work with dynamic label such as plugins (as I mentioned above)

image

We will need to fetch some data first (e.g. in the loader) before being able to return the label.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you point me to the designs for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The design is not available yet in v2 actually. I just wanted to prepare for this case in case we want to bring this pattern to v2 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think yeah we can try to put that in staticData (formerly meta) first and try to figure out the dynamic case when it's actually needed in the design...

__() can be called anywhere, it doesn't have to be called at render time.

I was afraid the translation might not be ready yet when we construct the routing, but I'm happy to be wrong. I can try.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, imo we shouldn't think about use cases that might not exist. If for now a simple link with a slash works, then imo that's good for now

Copy link
Contributor

Choose a reason for hiding this comment

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

I was afraid the translation might not be ready yet when we construct the routing, but I'm happy to be wrong. I can try.

For @wordpress/i18n in GB, I don't think it matters, I'm not sure about Calypso. But if you want to be sure it could be a function that maps it instead that is called during render

'!@automattic/components/src/core-badge',
'!@automattic/components/src/breadcrumbs',
Copy link
Contributor

Choose a reason for hiding this comment

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

Clicking the breadcrumb items will trigger a page reload. This is because it's a regular link, not a router link. I don't have any good idea yet how to solve it. Need help from @Automattic/io.

How difficult is it to just create the breadcrumb links ourselves? In this case, it looks like there will only ever be a single breadcrumb link, looking at the designs, so this doesn't seem that complex to add a single {link} / above the header?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I honestly initially thought about that... but then why are we creating a full-blown <Breadcrumbs /> DS component if it's never going to be used beyond the above simple case? That thinking made me end up trying to come up with a more general breadcrumbs system 🥲

Even if we have to hardcode the router link, we will need to copy-paste the styles from the <Breadcrumbs /> component. Not sure if that's a good idea design-wise 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, if we're only ever going to add one link then we can remove that component eventually. I don't think we should necessarily import styles from Calypso components, we should focus on using @wordpress styles with limited customisation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, this is not what I anticipated; I thought the idea is to make use of the reusable DS components. I'll see what I can do next week; it's getting late for me 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or I can try to pass something to the link's onClick props and see if I can properly call TanStack router navigation with it.

<a href={ href } onClick={ onClick } className="a8c-components-breadcrumbs__item">

I'd love to get input from @ntsekouras as the component author 🙏

Copy link
Contributor

Choose a reason for hiding this comment

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

Apparently Breadcrumbs was created for the hosting dashboard, but I don't have more information than that, so ignore my comments for now.

Copy link
Member

@ntsekouras ntsekouras May 9, 2025

Choose a reason for hiding this comment

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

Or I can try to pass something to the link's onClick props and see if I can properly call TanStack router navigation with it.

For start that what I'd try to do personally, just for now. Or it might had to do with the path (relative/full, etc..).

Breadcrumbs will be part of the PageHeader component, that will land soon. My plan was to implement what this PR does, after the merge of PageHeader and try to see if we can make it work with a tags, but if it can't, we'll add support in the Breadcrumbs component to pass the element we want instead of a ( in this case Link). This was mentioned in the Breadcrumbs PR already.

So, for me the important part here is to create the functionality to retrieve the proper breadcrumb items and for now we could just use directly a Link and not the Breadcrumbs if not possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Hosting Dashboard [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