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

Closed
wants to merge 2 commits into from
Closed

Dashboard v2: implement breadcrumbs #103294

wants to merge 2 commits 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)?

Copy link

github-actions bot commented May 9, 2025

@matticbot
Copy link
Contributor

matticbot commented May 9, 2025

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

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

name                    parsed_size           gzip_size
entry-dashboard-dotcom    +101854 B  (+9.1%)   +29426 B  (+8.5%)
entry-dashboard-a4a       +101854 B  (+9.2%)   +29681 B  (+8.7%)
entry-login                  -914 B  (-0.0%)     +164 B  (+0.0%)
entry-stepper                +395 B  (+0.0%)      -14 B  (-0.0%)
entry-main                   +242 B  (+0.0%)     -211 B  (-0.0%)
entry-domains-landing        +213 B  (+0.0%)      +32 B  (+0.0%)
entry-browsehappy            +213 B  (+0.1%)      +32 B  (+0.1%)
entry-subscriptions          +200 B  (+0.0%)       +1 B  (+0.0%)
entry-reauth-required         -31 B  (-0.0%)     -308 B  (-0.1%)

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

Sections (~3475 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%)       +7 B  (+0.0%)
plans                            +122 B  (+0.0%)      +75 B  (+0.0%)
google-my-business               +103 B  (+0.0%)      +38 B  (+0.0%)
async-step-unified-domains        +90 B  (+0.0%)     -290 B  (-0.1%)
async-step-unified-plans          +19 B  (+0.0%)     -274 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

matticbot commented May 9, 2025

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

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

  • 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 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

Copy link
Contributor Author

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?

Thanks for the feedback! I ended up putting the label inside staticData instead, with a small caveat:

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

It doesn't work with the approach in:

as the translation is not ready yet when the routing is constructed, so I made it a function instead, e.g.:

const siteSettingsRoute = createRoute( {
	...
	staticData: {
		breadcrumbItemLabel: () => __( 'Settings' ),
	},

'!@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
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.

Copy link
Member

Choose a reason for hiding this comment

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

I opened a PR for this: #103321

Copy link
Member

Choose a reason for hiding this comment

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

see if I can properly call TanStack router navigation with it.

If you do want to shortcut #103321 you can implement the onClick handler like

export default function Breadcrumbs() {
	const matches = useMatches();
	const navigate = useNavigate();

	const items = [] as BreadcrumbItemProps[];

	let hasParent = false;
	matches.forEach( ( match ) => {
		if ( match.loaderData?.breadcrumbItemLabel || hasParent ) {
			items.push( {
				label: match.loaderData?.breadcrumbItemLabel || '',
				href: match.pathname,
				onClick: ( e ) => {
					e.preventDefault();
					navigate( { to: match.pathname } );
				},
			} );
			hasParent = true;
		}
	} );

	return <BreadcrumbsComponent items={ items } />;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@p-jackson The downside of this approach is that it doesn't support "preloading". See #103283 and same issue also being solved for DataViews #103328

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks all. I understand that we're discussing how we will support route links for the breadcrumbs in the linked PR above. To keep progressing, can we then move forward with the existing Breadcrumbs component behavior in this PR, and then we can follow-up later when we have decided how to support route links? At least, in this PR, we managed to collect the breadcrumbs and show them in the appropriate location.

@fushar fushar force-pushed the v2-breadcrumbs branch 2 times, most recently from 07aea7f to 008ea30 Compare May 13, 2025 04:56
let hasParent = false;
matches.forEach( ( match ) => {
const breadcrumbItemLabel = match.staticData?.breadcrumbItemLabel;
if ( breadcrumbItemLabel || hasParent ) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd expect extracting the breadcrumbs based on how we register the routes (route.addChildren). Isn't that possible? It seems weird to have a hasParent flag based on breadcrumbItemLabel existence.

const breadcrumbItemLabel = match.staticData?.breadcrumbItemLabel;
if ( breadcrumbItemLabel || hasParent ) {
items.push( {
label: breadcrumbItemLabel?.() || '',
Copy link
Member

Choose a reason for hiding this comment

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

It wouldn't make any sense to have an empty label, so we could move this check before pushing the item.

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 was a "hack" because the individual settings subpage needs to be in the breadcrumb items; even if it's not shown (because it's the last item).

Let me think on how to make it cleaner...

@@ -109,6 +110,9 @@ const siteSettingsRoute = createRoute( {
path: 'settings',
loader: ( { params: { siteSlug } } ) =>
queryClient.ensureQueryData( siteSettingsQuery( siteSlug ) ),
staticData: {
Copy link
Member

Choose a reason for hiding this comment

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

I don't have the full picture yet regarding the proper implementation, but here we decouple the route breadcrumb label with the label we use for the menus in the app. Would it be possible and/or preferable to have a single label in the route and extract this in the menus?

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 already the case right now, we're duplicating the page title (e.g. Overview, Deployments) in the menu and in the individual component for that menu. Let me think how to best address this...

@fushar
Copy link
Contributor Author

fushar commented May 13, 2025

Hello hello @youknowriad @ntsekouras @ellatrix, I am a bit stuck here, wanted to get your guidance. Here is my brain dump...

When I first implemented this PR, I was not aware of @ntsekouras's PageHeader PR. Looking at the PR, it seems that the idea is render the breadcrumbs inside the page header.

Looking at the proposed Figma designs, it looks like for now, we only show the breadcrumbs in the Settings tab from the "Sites" dashboard. @ellatrix raised a good point above: if that is the only place, can we just statically render that without any logic?

That translates to roughly something like this:

export function SubscriptionGiftingSettings() {
  return (
    <PageLayout ...>
      <PageHeader
        breadcrumbs={[
          { label: __('Settings'), href:`/sites/{$siteSlug}/settings`},
          { label: '', href: '#'}
        ]}
        title={__('Accept subscription gifting')} />
    ...
  );
}

, which means we're duplicating the breadcrumbs in EVERY subpages of the Settings tabs.


Maybe that works for now. If we're not comfortable with that, we can take one step further and extract the breadcrumbs logic centrally, as hooks / helpers. So, something like this...

export function useSettingsBreadcrumbs() {
return [
          { label: __('Settings'), href:`/sites/{$siteSlug}/settings`},
          { label: '', href: '#'}
        ];
}

...

export function SubscriptionGiftingSettings() {
  const breadcrumbs = useSettingsBreadcrumbs();
  return (
    <PageLayout ...>
      <PageHeader
        breadcrumbs={breadcrumbs}
        title={__('Accept subscription gifting')} />
    ...
  );
}

but that means each and every page need to decide whether to show breadcrumbs. In our case, the problem is the same: each subpage of the Settings tab needs to remember to pass that breadcrumbs.


Another alternative is, which is the one proposed in this PR, is to render the breadcrumbs component at the "layout" level; in this case, the <PageLayout /> component. This makes breadcrumbs available to all pages automatically. Then, we need a "global" way to declare the breadcrumb items. Here, I am proposing to put that information in the router layer. I am proposing to put it inside staticData, something like this:

const siteSettingsRoute = createRoute( {
        ...,
	staticData: {
		breadcrumbItemLabel: () => __( 'Settings' ),
	},
} 

then, we create a component called Breadcrumbs that traverse the current route matches, and show the breadcrumbs starting the route that declares the label:

export default function Breadcrumbs() {
	const matches = useMatches();

	// construct the breadcrumb items based on match(es) that declares the label
        // and then return the Breadcrumbs component
}

then we can simply show it from <PageLayout />:

export function PageLayout() {
  ...
  return (
    <>
      <Breadcrumbs />
      {children}
    </>
  );
}

However, it needs to take @ntsekouras 's PR

into account. This means rendering the breadcrumbs inside that <PageHeader /> component. Or, if we're not comfortable having that logic inside this "pure" component, we'd need to extract that and put that inside <PageLayout /> (as demonstrated above).

That's the gist of this PR!


This PR also gets a good feedback from @ntsekouras above about how to share the breadcrumb string between the page title, the breadcrumb, and the nav (menu), which is something I haven't solved yet. Need help on this -- or maybe we don't need to solve it now?


This has been a long comment, but my point is: there's a virtually infinite way to do this. I'd appreciate a suggestion -- which direction do you think should we take to move us forward with the dashboard MVP? I didn't anticipate spending too much time on this PR 😄

@ellatrix
Copy link
Contributor

which means we're duplicating the breadcrumbs in EVERY subpages of the Settings tabs.

Tbh, I don't think that's a big deal for now, it's fine to duplicate it a few times? I also agree with building breadcrumbs into the header. This will be something we'll have to provide view transitions for eventually.

Currently navigation link labels are also not tied to the routes. Should they? I think it would make more sense to attach it to the route if it were the same thing for navigation links for example. Meaning: I'm not sure right now if it should, and maybe some duplication is fine for now and we can revisit that later.

if ( ! isExactMatch ) {
return <Outlet />;
}

return (
<PageLayout title={ __( 'Settings' ) } size="small">
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be the same label as the breadcrumb? Could it be more generally the route label?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually yes. @ntsekouras also had a similar comment in that it should also match with the nav menus (

<ResponsiveMenu.Item to={ `/sites/${ siteSlug }` } activeOptions={ { exact: true } }>
{ __( 'Overview' ) }
</ResponsiveMenu.Item>
<ResponsiveMenu.Item to={ `/sites/${ siteSlug }/deployments` }>
{ __( 'Deployments' ) }
</ResponsiveMenu.Item>
<ResponsiveMenu.Item to={ `/sites/${ siteSlug }/performance` }>
{ __( 'Performance' ) }
</ResponsiveMenu.Item>
<ResponsiveMenu.Item to={ `/sites/${ siteSlug }/settings` }>
{ __( 'Settings' ) }
</ResponsiveMenu.Item>
).

If we want to use route label, given we want to put the labels in the router level as proposed in this PR, this will then become something like

<PageLayout title={siteSettingsRoute.options.staticData.breadcrumbItemLabel()} />

but I'm not sure if it's a good pattern in TanStack. Another way is maybe write some hook called usePageTitle() then get the label of the last matching route. But then I wonder if this logic should be in <PageLayout /> directly instead.

In any case, it's a good follow-up thing to do, whatever approach we take!

if ( ! siteData || ! settings ) {
return null;
}

const isExactMatch = match.id === matches[ matches.length - 1 ].id;
if ( ! isExactMatch ) {
return <Outlet />;
Copy link
Contributor

@ellatrix ellatrix May 13, 2025

Choose a reason for hiding this comment

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

I don't understand why <Outlet /> is needed here. It doesn't seem like the right place especially after already calling useQuery. Why is it needed here, but not for ${siteSlug} (Overview) and {$siteSlug}/sub-page?

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 want the subpage routes (in this case siteSettingsSubscriptionGiftingRoute) to be a child of siteSettingsRoute for some reasons:

  • so that they share the parent route prefix (i.e. settings/)
  • so that they share the parent loader logic (i.e. preloading siteSettingsQuery())
  • (especially for this PR) so that the parent route MATCHES so its route label will be collected properly to construct the breadcrumbs. In other words, we want /sites/${siteSlug}/settings to be always matched in the subpages.

So, if it's an exact match, we want to render the Settings page, otherwise, we should render <Outlet /> so that it renders the child component instead (renders the settings subpages).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why is it needed here, but not for ${siteSlug} (Overview) and {$siteSlug}/sub-page?

siteRoute is already a parent of its children routes. It's already always shown in the subroutes. It renders as the site navigation menu (Overview, Deployments, etc).

On the other hands, we cant really compose Settings and its subpages. In /sites/${slug}/settings we render the list of setting summaries; in the child pages we render the settings of the subpages only. Hence the conditionals.

@youknowriad
Copy link
Contributor

So my understanding here is that this PR is trying to build the breadcrumb based on the route hierarchy, and in doing so, it's forcing a special set of parent routes... and enhancing the route definition to be able to do so.

I'm not convinced that breadcrumbs generation/rendering should be tied to the route hierarchy. I understand the appeal but this largely feels to me as early abstraction. I'd be in favor of just rendering the Breadcrumb component as a prop to PageHeader or PageLayout. The simplest the better.

<PageHeader breadcrumb={ <Breadcrumb ... /> } />

@fushar
Copy link
Contributor Author

fushar commented May 14, 2025

Cool, thanks all for the feedback. I believe, the consensus is to build the simplest thing possible without early abstraction. I also understand that there might be some future changes to the breadcrumbs based on puPv3-pw6-p2.

I'll raise another PR instead and close this one for posterity. Thanks again!

@fushar fushar closed this May 14, 2025
@github-actions github-actions bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label May 14, 2025
@ntsekouras
Copy link
Member

I'm not convinced that breadcrumbs generation/rendering should be tied to the route hierarchy. I understand the appeal but this largely feels to me as early abstraction.

It's fine to consider this an earyl abstraction, but @youknowriad I'm curious why you think that way for the first part. Isn't the breadcrumbs all about route hierarchy? Currently you can just pass the breadcrumb items independently of course, but in the context of the dashboard IMO these should be tied with an extra wrapper or hook that would do it based on routes. This is also related to the comments about routes and hardcoding title in menus or page titles. Shouldn't be the same in all places? We could have a way to override them in apps, but probably we shouldn't.

@youknowriad
Copy link
Contributor

Isn't the breadcrumbs all about route hierarchy? Currently you can just pass the breadcrumb items independently of course, but in the context of the dashboard IMO these should be tied with an extra wrapper or hook that would do it based on routes. This is also related to the comments about routes and hardcoding title in menus or page titles. Shouldn't be the same in all places?

You are right, but by making it an abstraction now, you're creating a lot of constraints:

  • Routes need to use "children/Outlet" even if visually it's not needed (as they replace the whole screen)
  • You remove all flexibility from breadcrumbs designs (imagine a site switcher integrated within the breadcrumb)

This is also related to the comments about routes and hardcoding title in menus or page titles

These are also for me small things and I'm not sure abstracting is important. Some titles might require extra icons or things like that. When you start abstracting these things, you're forced to over abstract everything.


I'm not saying these abstractions are necessarily bad, I'm saying that we're too early design wise to introduce them, especially since the alternative is so simple, just render a reusable component.

@youknowriad youknowriad deleted the v2-breadcrumbs branch May 14, 2025 07:50
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.

6 participants