Breadcrumbs.tsx raw
1 import { Fragment } from "react";
2 import { Link, useMatches } from "react-router";
3 import {
4 Breadcrumb,
5 BreadcrumbItem,
6 BreadcrumbLink,
7 BreadcrumbList,
8 BreadcrumbSeparator,
9 } from "src/components/ui/breadcrumb";
10
11 type MatchWithCrumb = {
12 pathname: string;
13 handle?: {
14 crumb?: () => React.ReactNode;
15 };
16 };
17
18 function Breadcrumbs() {
19 const matches = useMatches() as MatchWithCrumb[]; // Type-cast useMatches result to MatchWithCrumb array
20
21 const crumbs = matches
22 // First, get rid of any matches that don't have a handle or crumb
23 .filter(
24 (
25 match
26 ): match is MatchWithCrumb & {
27 handle: { crumb: () => React.ReactNode };
28 } => Boolean(match.handle?.crumb)
29 );
30
31 // Compare pathnames of index routes to remove duplicates
32 const isIndexRoute =
33 crumbs.length >= 2 && crumbs[crumbs.length - 1].pathname
34 ? crumbs[crumbs.length - 1].pathname.slice(0, -1) ===
35 crumbs[crumbs.length - 2].pathname
36 : false;
37
38 // Remove the last item if it's an index route to prevent e.g. Wallet > Wallet
39 const filteredCrumbs = isIndexRoute ? crumbs.slice(0, -1) : crumbs;
40
41 // Skip rendering for breadcrumbs consisting of 2 (or less) items
42 if (filteredCrumbs.length < 3) {
43 return null;
44 }
45
46 return (
47 <>
48 <Breadcrumb>
49 <BreadcrumbList>
50 {filteredCrumbs.map((crumb, index) => (
51 <Fragment key={index}>
52 <BreadcrumbItem>
53 {index + 1 < filteredCrumbs.length ? (
54 <BreadcrumbLink asChild>
55 <Link to={crumb.pathname}>{crumb.handle.crumb()}</Link>
56 </BreadcrumbLink>
57 ) : (
58 <>{crumb.handle.crumb()}</>
59 )}
60 </BreadcrumbItem>
61 {index + 1 < filteredCrumbs.length && <BreadcrumbSeparator />}
62 </Fragment>
63 ))}
64 </BreadcrumbList>
65 </Breadcrumb>
66 </>
67 );
68 }
69
70 export default Breadcrumbs;
71