Skip to content

Commit 590711c

Browse files
Change Breadcrumbs to accept a single breadcrumb instead of an array (#7990)
### WHY are these changes introduced? Breadcrumbs has only been using a single breadcrumb for a while now. Instead of allowing consumers to pass an array without knowing why only a single link is being used, we should remove the ability to pass an array and make the component easier to understand implicitly. ### WHAT is this pull request doing? UI will not change. The Breadcrumbs component was already only using the last breadcrumb link in the array. This PR changes the props for breadcrumbs and also the implementation on the Page component. ### Migration path Since this PR removes the `breadcrumbs[]` prop in favor of `breadcrumb` all uses of Page, and Breadcrumbs will need to be migrated. It will look something like this: ```ts //before const breadcrumbs = [ { content: 'Products', url: 'http://test.com', }, ]; <Page {...mockProps} breadcrumbs={breadcrumbs} /> //after const breadcrumb = { content: 'Products', url: 'http://test.com', }; <Page {...mockProps} breadcrumb={breadcrumb} /> ``` ### How to 🎩 Check the Breadcrumbs and Page components in storybook ### 🎩 checklist - [ ] Tested on [mobile](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md#cross-browser-testing) - [ ] Tested on [multiple browsers](https://help.shopify.com/en/manual/shopify-admin/supported-browsers) - [ ] Tested for [accessibility](https://github.com/Shopify/polaris/blob/main/documentation/Accessibility%20testing.md) - [ ] Updated the component's `README.md` with documentation changes - [ ] [Tophatted documentation](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting%20documentation.md) changes in the style guide
1 parent 1441b7a commit 590711c

File tree

9 files changed

+77
-108
lines changed

9 files changed

+77
-108
lines changed

.changeset/afraid-scissors-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/polaris': major
3+
---
4+
5+
Change breadcrumbs from an array to a single breadcrumb since only one is supported.

polaris-react/playground/DetailsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ export function DetailsPage() {
547547
const actualPageMarkup = (
548548
<Page
549549
fullWidth
550-
breadcrumbs={[{content: 'Products', url: '/products/31'}]}
550+
breadcrumb={{content: 'Products', url: '/products/31'}}
551551
title={title}
552552
titleMetadata={<Badge status="success">Success badge</Badge>}
553553
primaryAction={{

polaris-react/src/components/Breadcrumbs/Breadcrumbs.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,11 @@ import {Text} from '../Text';
1010
import styles from './Breadcrumbs.scss';
1111

1212
export interface BreadcrumbsProps {
13-
/** Collection of breadcrumbs */
14-
breadcrumbs: (CallbackAction | LinkAction)[];
13+
/** Breadcrumb link */
14+
breadcrumb: CallbackAction | LinkAction;
1515
}
1616

17-
export function Breadcrumbs({breadcrumbs}: BreadcrumbsProps) {
18-
const breadcrumb = breadcrumbs[breadcrumbs.length - 1];
19-
if (breadcrumb == null) {
20-
return null;
21-
}
22-
17+
export function Breadcrumbs({breadcrumb}: BreadcrumbsProps) {
2318
const {content} = breadcrumb;
2419

2520
const contentMarkup = (

polaris-react/src/components/Breadcrumbs/tests/Breadcrumbs.test.tsx

Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,25 @@ import {Text} from '../../Text';
88
describe('<Breadcrumbs />', () => {
99
describe('url', () => {
1010
it('uses <a> tags when passed a LinkAction', () => {
11-
const linkBreadcrumbs: LinkAction[] = [
12-
{
13-
content: 'Products',
14-
url: 'https://www.shopify.com',
15-
},
16-
];
17-
11+
const linkBreadcrumb: LinkAction = {
12+
content: 'Products',
13+
url: 'https://www.shopify.com',
14+
};
1815
const breadcrumbs = mountWithApp(
19-
<Breadcrumbs breadcrumbs={linkBreadcrumbs} />,
16+
<Breadcrumbs breadcrumb={linkBreadcrumb} />,
2017
);
2118

2219
expect(breadcrumbs).toContainReactComponentTimes('a', 1);
2320
});
2421

2522
it('passes the accessibilityLabel through to <a> tag', () => {
26-
const linkBreadcrumbs: LinkAction[] = [
27-
{
28-
content: 'Products',
29-
url: 'https://shopify.com',
30-
accessibilityLabel: 'Go to Products',
31-
},
32-
];
33-
23+
const linkBreadcrumb: LinkAction = {
24+
content: 'Products',
25+
url: 'https://shopify.com',
26+
accessibilityLabel: 'Go to Products',
27+
};
3428
const breadcrumbs = mountWithApp(
35-
<Breadcrumbs breadcrumbs={linkBreadcrumbs} />,
29+
<Breadcrumbs breadcrumb={linkBreadcrumb} />,
3630
);
3731

3832
expect(breadcrumbs).toContainReactComponent('a', {
@@ -43,31 +37,25 @@ describe('<Breadcrumbs />', () => {
4337

4438
describe('onAction()', () => {
4539
it('uses <button> tags when passed a CallbackAction', () => {
46-
const callbackBreadcrumbs: CallbackAction[] = [
47-
{
48-
content: 'Products',
49-
onAction: noop,
50-
},
51-
];
52-
40+
const callbackBreadcrumb: CallbackAction = {
41+
content: 'Products',
42+
onAction: noop,
43+
};
5344
const breadcrumbs = mountWithApp(
54-
<Breadcrumbs breadcrumbs={callbackBreadcrumbs} />,
45+
<Breadcrumbs breadcrumb={callbackBreadcrumb} />,
5546
);
5647

5748
expect(breadcrumbs).toContainReactComponentTimes('button', 1);
5849
});
5950

6051
it('passes accessibilityLabel through to <button> tag', () => {
61-
const callbackBreadcrumbs: CallbackAction[] = [
62-
{
63-
content: 'Products',
64-
onAction: noop,
65-
accessibilityLabel: 'Go to Products',
66-
},
67-
];
68-
52+
const callbackBreadcrumb: CallbackAction = {
53+
content: 'Products',
54+
onAction: noop,
55+
accessibilityLabel: 'Go to Products',
56+
};
6957
const breadcrumbs = mountWithApp(
70-
<Breadcrumbs breadcrumbs={callbackBreadcrumbs} />,
58+
<Breadcrumbs breadcrumb={callbackBreadcrumb} />,
7159
);
7260

7361
expect(breadcrumbs).toContainReactComponent('button', {
@@ -77,43 +65,32 @@ describe('<Breadcrumbs />', () => {
7765

7866
it('triggers the callback function when clicked', () => {
7967
const spy = jest.fn();
80-
const callbackBreadcrumbs: CallbackAction[] = [
81-
{
82-
content: 'Products',
83-
onAction: spy,
84-
},
85-
];
86-
68+
const callbackBreadcrumb: CallbackAction = {
69+
content: 'Products',
70+
onAction: spy,
71+
};
8772
const breadcrumbs = mountWithApp(
88-
<Breadcrumbs breadcrumbs={callbackBreadcrumbs} />,
73+
<Breadcrumbs breadcrumb={callbackBreadcrumb} />,
8974
);
9075

9176
breadcrumbs.find('button')!.trigger('onClick');
9277
expect(spy).toHaveBeenCalled();
9378
});
9479
});
9580

96-
const linkBreadcrumbs: LinkAction[] = [
97-
{
98-
content: 'Products',
99-
url: 'https://www.shopify.com',
100-
},
101-
];
81+
const linkBreadcrumb: LinkAction = {
82+
content: 'Products',
83+
url: 'https://www.shopify.com',
84+
};
10285

10386
it('renders breadcrumb content as a visually hidden label when the new design language is enabled', () => {
104-
const wrapper = mountWithApp(<Breadcrumbs breadcrumbs={linkBreadcrumbs} />);
87+
const wrapper = mountWithApp(<Breadcrumbs breadcrumb={linkBreadcrumb} />);
10588

10689
expect(wrapper).toContainReactComponent(Text, {
10790
children: 'Products',
10891
visuallyHidden: true,
10992
});
11093
});
111-
112-
it('renders nothing when empty', () => {
113-
const wrapper = mountWithApp(<Breadcrumbs breadcrumbs={[]} />);
114-
115-
expect(wrapper.html()).toBe('');
116-
});
11794
});
11895

11996
function noop() {}

polaris-react/src/components/Page/Page.stories.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default {
1010
export function Default() {
1111
return (
1212
<Page
13-
breadcrumbs={[{content: 'Products', url: '/products'}]}
13+
breadcrumb={{content: 'Products', url: '/products'}}
1414
title="3/4 inch Leather pet collar"
1515
titleMetadata={<Badge status="success">Paid</Badge>}
1616
subtitle="Perfect for any pet"
@@ -30,7 +30,6 @@ export function Default() {
3030
actionGroups={[
3131
{
3232
title: 'Promote',
33-
accessibilityLabel: 'Action group label',
3433
actions: [
3534
{
3635
content: 'Share on Facebook',
@@ -55,7 +54,7 @@ export function Default() {
5554
export function WithCustomPrimaryAction() {
5655
return (
5756
<Page
58-
breadcrumbs={[{content: 'Settings', url: '/settings'}]}
57+
breadcrumb={{content: 'Settings', url: '/settings'}}
5958
title="General"
6059
primaryAction={
6160
<Button
@@ -79,7 +78,7 @@ export function WithCustomPrimaryAction() {
7978
export function WithoutPrimaryActionInHeader() {
8079
return (
8180
<Page
82-
breadcrumbs={[{content: 'Orders', url: '/orders'}]}
81+
breadcrumb={{content: 'Orders', url: '/orders'}}
8382
title="#1085"
8483
secondaryActions={[
8584
{content: 'Print'},
@@ -159,7 +158,7 @@ export function WithToolTipAction() {
159158
export function WithSubtitle() {
160159
return (
161160
<Page
162-
breadcrumbs={[{content: 'Products', url: '/products'}]}
161+
breadcrumb={{content: 'Products', url: '/products'}}
163162
title="Invoice"
164163
subtitle="Statement period: May 3, 2019 to June 2, 2019"
165164
secondaryActions={[{content: 'Download', icon: ArrowDownMinor}]}
@@ -195,7 +194,7 @@ export function WithExternalLink() {
195194
export function WithoutPagination() {
196195
return (
197196
<Page
198-
breadcrumbs={[{content: 'Settings', url: '/settings'}]}
197+
breadcrumb={{content: 'Settings', url: '/settings'}}
199198
title="General"
200199
primaryAction={{content: 'Save'}}
201200
>
@@ -228,7 +227,7 @@ export function NarrowWidth() {
228227
return (
229228
<Page
230229
narrowWidth
231-
breadcrumbs={[{content: 'Orders', url: '/orders'}]}
230+
breadcrumb={{content: 'Orders', url: '/orders'}}
232231
title="Add payment method"
233232
primaryAction={{content: 'Save', disabled: true}}
234233
>
@@ -282,7 +281,7 @@ export function WithActionGroups() {
282281
export function WithContentAfterTitle() {
283282
return (
284283
<Page
285-
breadcrumbs={[{content: 'Products', url: '/products'}]}
284+
breadcrumb={{content: 'Products', url: '/products'}}
286285
title="Jar With Lock-Lid"
287286
titleMetadata={<Badge status="attention">Verified</Badge>}
288287
primaryAction={{content: 'Save', disabled: true}}
@@ -305,7 +304,7 @@ export function WithContentAfterTitle() {
305304
export function WithDivider() {
306305
return (
307306
<Page
308-
breadcrumbs={[{content: 'Settings', url: '/settings'}]}
307+
breadcrumb={{content: 'Settings', url: '/settings'}}
309308
title="General"
310309
divider
311310
>

polaris-react/src/components/Page/Page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function Page({
4040
rest.secondaryActions.length > 0) ||
4141
isReactElement(rest.secondaryActions))) ||
4242
(rest.actionGroups != null && rest.actionGroups.length > 0) ||
43-
(rest.breadcrumbs != null && rest.breadcrumbs.length > 0);
43+
rest.breadcrumb != null;
4444

4545
const contentClassName = classNames(
4646
!hasHeaderContent && styles.Content,

polaris-react/src/components/Page/components/Header/Header.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ export interface HeaderProps extends TitleProps {
4747
primaryAction?: PrimaryAction | React.ReactNode;
4848
/** Page-level pagination */
4949
pagination?: PaginationProps;
50-
/** Collection of breadcrumbs */
51-
breadcrumbs?: BreadcrumbsProps['breadcrumbs'];
50+
/** Breadcrumb link */
51+
breadcrumb?: BreadcrumbsProps['breadcrumb'];
5252
/** Collection of secondary page-level actions */
5353
secondaryActions?: MenuActionDescriptor[] | React.ReactNode;
5454
/** Collection of page-level groups of secondary actions */
@@ -74,7 +74,7 @@ export function Header({
7474
primaryAction,
7575
pagination,
7676
additionalNavigation,
77-
breadcrumbs = [],
77+
breadcrumb,
7878
secondaryActions = [],
7979
actionGroups = [],
8080
compactTitle = false,
@@ -97,12 +97,11 @@ export function Header({
9797
isReactElement(secondaryActions)) &&
9898
!actionGroups.length;
9999

100-
const breadcrumbMarkup =
101-
breadcrumbs.length > 0 ? (
102-
<div className={styles.BreadcrumbWrapper}>
103-
<Breadcrumbs breadcrumbs={breadcrumbs} />
104-
</div>
105-
) : null;
100+
const breadcrumbMarkup = breadcrumb ? (
101+
<div className={styles.BreadcrumbWrapper}>
102+
<Breadcrumbs breadcrumb={breadcrumb} />
103+
</div>
104+
) : null;
106105

107106
const paginationMarkup =
108107
pagination && !isNavigationCollapsed ? (
@@ -178,7 +177,7 @@ export function Header({
178177
navigationMarkup && styles.hasNavigation,
179178
actionMenuMarkup && styles.hasActionMenu,
180179
isNavigationCollapsed && styles.mobileView,
181-
!breadcrumbs.length && styles.noBreadcrumbs,
180+
!breadcrumb && styles.noBreadcrumbs,
182181
title && title.length < LONG_TITLE && styles.mediumTitle,
183182
title && title.length > LONG_TITLE && styles.longTitle,
184183
);

polaris-react/src/components/Page/components/Header/tests/Header.test.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,17 @@ describe('<Header />', () => {
4747
});
4848

4949
describe('breadcrumbs', () => {
50-
const breadcrumbs: LinkAction[] = [
51-
{
52-
content: 'Products',
53-
url: 'https://www.google.com',
54-
},
55-
];
50+
const breadcrumb: LinkAction = {
51+
content: 'Products',
52+
url: 'https://www.google.com',
53+
};
5654

5755
it('get passed into Breadcrumbs', () => {
5856
const header = mountWithApp(
59-
<Header {...mockProps} breadcrumbs={breadcrumbs} />,
57+
<Header {...mockProps} breadcrumb={breadcrumb} />,
6058
);
6159
expect(header).toContainReactComponent(Breadcrumbs, {
62-
breadcrumbs,
60+
breadcrumb,
6361
});
6462
});
6563
});
@@ -291,12 +289,10 @@ describe('<Header />', () => {
291289
{content: 'mock content 2'},
292290
];
293291

294-
const breadcrumbs: LinkAction[] = [
295-
{
296-
content: 'Products',
297-
url: 'https://www.google.com',
298-
},
299-
];
292+
const breadcrumb: LinkAction = {
293+
content: 'Products',
294+
url: 'https://www.google.com',
295+
};
300296

301297
it('does not render primary and secondary action wrapper divs', () => {
302298
const header = mountWithApp(
@@ -341,7 +337,7 @@ describe('<Header />', () => {
341337

342338
it('renders a default mobile layout', () => {
343339
const header = mountWithApp(
344-
<Header title="mmmmmmmmm" breadcrumbs={breadcrumbs} />,
340+
<Header title="mmmmmmmmm" breadcrumb={breadcrumb} />,
345341
{
346342
mediaQuery: {isNavigationCollapsed: true},
347343
},

polaris-react/src/components/Page/tests/Page.test.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,26 +234,24 @@ describe('<Page />', () => {
234234
});
235235

236236
describe('breadcrumbs', () => {
237-
const breadcrumbs = [
238-
{
239-
content: 'Products',
240-
onAction: noop,
241-
},
242-
];
237+
const breadcrumb = {
238+
content: 'Products',
239+
onAction: noop,
240+
};
243241

244242
it('renders a <Header /> when defined', () => {
245243
const page = mountWithApp(
246-
<Page {...mockProps} breadcrumbs={breadcrumbs} />,
244+
<Page {...mockProps} breadcrumb={breadcrumb} />,
247245
);
248246
expect(page).toContainReactComponent(Header);
249247
});
250248

251249
it('gets passed into the <Header />', () => {
252250
const page = mountWithApp(
253-
<Page {...mockProps} breadcrumbs={breadcrumbs} />,
251+
<Page {...mockProps} breadcrumb={breadcrumb} />,
254252
);
255253
expect(page).toContainReactComponent(Header, {
256-
breadcrumbs,
254+
breadcrumb,
257255
});
258256
});
259257
});

0 commit comments

Comments
 (0)