11import { Fragment } from 'react' ;
2- import type { Theme } from '@emotion/react' ;
3- import { css } from '@emotion/react' ;
4- import styled from '@emotion/styled' ;
52
6- import { Chevron } from 'sentry/components/chevron' ;
3+ import { Container , Flex } from '@sentry/scraps/layout' ;
4+ import { Text } from '@sentry/scraps/text' ;
5+
76import type { LinkProps } from 'sentry/components/core/link' ;
87import { Link } from 'sentry/components/core/link' ;
98import GlobalSelectionLink from 'sentry/components/globalSelectionLink' ;
10- import { space } from 'sentry/styles/space' ;
11-
12- const BreadcrumbList = styled ( 'nav' ) `
13- display: flex;
14- align-items: center;
15- padding: ${ space ( 1 ) } 0;
16- ` ;
9+ import { IconChevron } from 'sentry/icons' ;
10+ import { trackAnalytics } from 'sentry/utils/analytics' ;
1711
1812export interface Crumb {
1913 /**
2014 * Label of the crumb
2115 */
22- label : React . ReactNode ;
23-
24- /**
25- * Component will try to come up with unique key, but you can provide your own
26- * (used when mapping over crumbs)
27- */
28- key ?: string ;
16+ label : NonNullable < React . ReactNode > ;
2917
3018 /**
3119 * It will keep the page filter values (projects, environments, time) in the
@@ -39,103 +27,98 @@ export interface Crumb {
3927 to ?: LinkProps [ 'to' ] | null ;
4028}
4129
42- interface Props extends React . HTMLAttributes < HTMLDivElement > {
43- /**
44- * Array of crumbs that will be rendered
45- */
30+ interface BreadcrumbsProps extends React . HTMLAttributes < HTMLDivElement > {
4631 crumbs : Crumb [ ] ;
47-
48- /**
49- * As a general rule of thumb we don't want the last item to be link as it most likely
50- * points to the same page we are currently on. This is by default false, so that
51- * people don't have to check if crumb is last in the array and then manually
52- * assign `to: null/undefined` when passing props to this component.
53- */
54- linkLastItem ?: boolean ;
5532}
5633
5734/**
5835 * Page breadcrumbs used for navigation, not to be confused with sentry's event breadcrumbs
5936 */
60- export function Breadcrumbs ( { crumbs, linkLastItem = false , ...props } : Props ) {
37+ export function Breadcrumbs ( { crumbs, ...props } : BreadcrumbsProps ) {
6138 if ( crumbs . length === 0 ) {
6239 return null ;
6340 }
6441
65- if ( ! linkLastItem ) {
66- crumbs [ crumbs . length - 1 ] ! . to = null ;
67- }
68-
6942 return (
70- < BreadcrumbList { ...props } data-test-id = "breadcrumb-list" >
43+ < Flex
44+ as = "nav"
45+ gap = "xs"
46+ align = "center"
47+ padding = "md 0"
48+ data-test-id = "breadcrumb-list"
49+ { ...props }
50+ >
7151 { crumbs . map ( ( crumb , index ) => {
72- const { label, to, preservePageFilters, key} = crumb ;
73- const labelKey = typeof label === 'string' ? label : '' ;
74- const mapKey =
75- key ?? ( typeof to === 'string' ? `${ labelKey } ${ to } ` : `${ labelKey } ${ index } ` ) ;
76-
7752 return (
78- < Fragment key = { mapKey } >
79- { to ? (
80- < BreadcrumbLink
81- to = { to }
82- preservePageFilters = { preservePageFilters }
83- data-test-id = "breadcrumb-link"
84- >
85- { label }
86- </ BreadcrumbLink >
87- ) : (
88- < BreadcrumbItem data-test-id = "breadcrumb-item" > { label } </ BreadcrumbItem >
89- ) }
90-
91- { index < crumbs . length - 1 && < BreadcrumbDividerIcon direction = "right" /> }
53+ < Fragment key = { index } >
54+ < BreadCrumbItem
55+ crumb = { { ...crumb , to : index === crumbs . length - 1 ? undefined : crumb . to } }
56+ variant = { index === crumbs . length - 1 ? 'primary' : 'muted' }
57+ />
58+ { index < crumbs . length - 1 ? (
59+ < Flex align = "center" justify = "center" flexShrink = { 0 } >
60+ < IconChevron size = "xs" direction = "right" color = "subText" />
61+ </ Flex >
62+ ) : null }
9263 </ Fragment >
9364 ) ;
9465 } ) }
95- </ BreadcrumbList >
66+ </ Flex >
9667 ) ;
9768}
9869
99- const getBreadcrumbListItemStyles = ( p : { theme : Theme } ) => css `
100- ${ p . theme . overflowEllipsis }
101- color : ${ p . theme . subText } ;
102- width : auto;
70+ interface BreadCrumbItemProps {
71+ crumb : Crumb ;
72+ variant : 'primary' | 'muted' ;
73+ }
10374
104- & : last-child {
105- color : ${ p . theme . textColor } ;
75+ function BreadCrumbItem ( props : BreadCrumbItemProps ) {
76+ function onBreadcrumbLinkClick ( ) {
77+ if ( props . crumb . to ) {
78+ trackAnalytics ( 'breadcrumbs.link.clicked' , { organization : null } ) ;
79+ }
10680 }
107- ` ;
10881
109- interface BreadcrumbLinkProps {
110- to : LinkProps [ 'to' ] ;
82+ return (
83+ < Container maxWidth = "400px" width = "auto" >
84+ { styleProps => {
85+ return props . crumb . to ? (
86+ < BreadcrumbLink
87+ to = { props . crumb . to }
88+ preservePageFilters = { props . crumb . preservePageFilters }
89+ data-test-id = "breadcrumb-link"
90+ onClick = { onBreadcrumbLinkClick }
91+ { ...styleProps }
92+ >
93+ < Text ellipsis variant = { props . variant } >
94+ { props . crumb . label }
95+ </ Text >
96+ </ BreadcrumbLink >
97+ ) : (
98+ < Text
99+ ellipsis
100+ variant = { props . variant }
101+ data-test-id = "breadcrumb-item"
102+ { ...styleProps }
103+ >
104+ { props . crumb . label }
105+ </ Text >
106+ ) ;
107+ } }
108+ </ Container >
109+ ) ;
110+ }
111+
112+ interface BreadcrumbLinkProps extends LinkProps {
111113 children ?: React . ReactNode ;
112114 preservePageFilters ?: boolean ;
113115}
114116
115- const BreadcrumbLink = styled (
116- ( { preservePageFilters, to, ...props } : BreadcrumbLinkProps ) =>
117- preservePageFilters ? (
118- < GlobalSelectionLink to = { to } { ...props } />
119- ) : (
120- < Link to = { to } { ...props } />
121- )
122- ) `
123- ${ getBreadcrumbListItemStyles }
124- max-width: 400px;
125-
126- &:hover,
127- &:active {
128- color: ${ p => p . theme . subText } ;
117+ function BreadcrumbLink ( props : BreadcrumbLinkProps ) {
118+ const { preservePageFilters, ...rest } = props ;
119+ if ( preservePageFilters ) {
120+ return < GlobalSelectionLink { ...rest } /> ;
129121 }
130- ` ;
131122
132- const BreadcrumbItem = styled ( 'span' ) `
133- ${ getBreadcrumbListItemStyles }
134- max-width: 400px;
135- ` ;
136-
137- const BreadcrumbDividerIcon = styled ( Chevron ) `
138- color: ${ p => p . theme . subText } ;
139- margin: 0 ${ space ( 0.5 ) } ;
140- flex-shrink: 0;
141- ` ;
123+ return < Link { ...rest } /> ;
124+ }
0 commit comments