Skip to content

Commit 9888f70

Browse files
authored
feat(BreadcrumbSkeleton): added new props items, size, and noTrailingSlash (#20448)
* feat(react): add new props and story * fix(breadcrumb): fix proptypes typo * feat(wc): add new props and story * test(react): add test cases * fix: remove aria-label prop * test(wc): add cds-breadcrumb-skeleton test file * chore: update date * fix: pass ci
1 parent b939202 commit 9888f70

File tree

8 files changed

+228
-23
lines changed

8 files changed

+228
-23
lines changed

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,21 @@ Map {
469469
"className": {
470470
"type": "string",
471471
},
472+
"items": {
473+
"type": "number",
474+
},
475+
"noTrailingSlash": {
476+
"type": "bool",
477+
},
478+
"size": {
479+
"args": [
480+
[
481+
"sm",
482+
"md",
483+
],
484+
],
485+
"type": "oneOf",
486+
},
472487
},
473488
},
474489
"Button" => {

packages/react/src/components/Breadcrumb/Breadcrumb.Skeleton.tsx

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -16,6 +16,22 @@ export interface BreadcrumbSkeletonProps
1616
* Specify an optional className to add.
1717
*/
1818
className?: string;
19+
20+
/**
21+
* Specify the number of items
22+
*/
23+
items?: number;
24+
25+
/**
26+
* Optional prop to omit the trailing slash for the breadcrumbs
27+
*/
28+
noTrailingSlash?: boolean;
29+
30+
/**
31+
* Specify the size of the Breadcrumb. Currently
32+
* supports the following: `sm` & `md` (default: 'md')
33+
*/
34+
size?: 'sm' | 'md';
1935
}
2036

2137
function Item() {
@@ -28,15 +44,29 @@ function Item() {
2844
);
2945
}
3046

31-
function BreadcrumbSkeleton({ className, ...rest }: BreadcrumbSkeletonProps) {
47+
function BreadcrumbSkeleton({
48+
className,
49+
items = 3,
50+
noTrailingSlash,
51+
size,
52+
...rest
53+
}: BreadcrumbSkeletonProps) {
3254
const prefix = usePrefix();
33-
const classes = cx(`${prefix}--breadcrumb`, `${prefix}--skeleton`, className);
55+
const classes = cx(
56+
{
57+
[`${prefix}--breadcrumb`]: true,
58+
[`${prefix}--skeleton`]: true,
59+
[`${prefix}--breadcrumb--no-trailing-slash`]: noTrailingSlash,
60+
[`${prefix}--breadcrumb--sm`]: size === 'sm',
61+
},
62+
className
63+
);
3464

3565
return (
3666
<div className={classes} {...rest}>
37-
<Item />
38-
<Item />
39-
<Item />
67+
{Array.from({ length: items }, (_, i) => (
68+
<Item key={i} />
69+
))}
4070
</div>
4171
);
4272
}
@@ -46,6 +76,21 @@ BreadcrumbSkeleton.propTypes = {
4676
* Specify an optional className to add.
4777
*/
4878
className: PropTypes.string,
79+
80+
/**
81+
* Specify the number of items
82+
*/
83+
items: PropTypes.number,
84+
85+
/**
86+
* Optional prop to omit the trailing slash for the breadcrumbs
87+
*/
88+
noTrailingSlash: PropTypes.bool,
89+
90+
/**
91+
* Specify the size of the Breadcrumb. Currently supports the following: `sm` & `md` (default: 'md')
92+
*/
93+
size: PropTypes.oneOf(['sm', 'md']),
4994
};
5095

5196
export default BreadcrumbSkeleton;

packages/react/src/components/Breadcrumb/Breadcrumb.stories.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -108,8 +108,26 @@ BreadcrumbWithOverflowMenuSizeSmall.args = {
108108
size: 'sm',
109109
};
110110

111-
export const Skeleton = () => {
112-
return <BreadcrumbSkeleton />;
111+
export const Skeleton = (args) => {
112+
return <BreadcrumbSkeleton {...args} />;
113+
};
114+
115+
Skeleton.args = {
116+
items: 3,
117+
};
118+
119+
Skeleton.parameters = {
120+
controls: { exclude: ['aria-label'] },
121+
};
122+
123+
Skeleton.argTypes = {
124+
...sharedArgTypes,
125+
items: {
126+
description: 'Specify the number of items',
127+
table: {
128+
defaultValue: { summary: 3 },
129+
},
130+
},
113131
};
114132

115133
export const BreadcrumbWithOverflowVisualSnapshots = (args) => (

packages/react/src/components/Breadcrumb/Breadcrumb.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ Breadcrumb.propTypes = {
8383
noTrailingSlash: PropTypes.bool,
8484

8585
/**
86-
* Specify the size of the Breadcrumb. Currently supports the following:
86+
* Specify the size of the Breadcrumb. Currently
87+
* supports the following: `sm` & `md` (default: 'md')
8788
*/
8889
size: PropTypes.oneOf(['sm', 'md']),
8990
};

packages/react/src/components/Breadcrumb/__tests__/Breadcrumb.Skeleton-test.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

88
import React from 'react';
99
import { BreadcrumbSkeleton } from '../';
10-
import { render } from '@testing-library/react';
10+
import { screen, render } from '@testing-library/react';
11+
12+
const prefix = 'cds';
1113

1214
describe('BreadcrumbSkeleton', () => {
1315
it('should support a custom `className` prop on the outermost element', () => {
@@ -19,4 +21,28 @@ describe('BreadcrumbSkeleton', () => {
1921
const { container } = render(<BreadcrumbSkeleton data-testid="test" />);
2022
expect(container.firstChild).toHaveAttribute('data-testid', 'test');
2123
});
24+
25+
it('should render the specified number of skeleton items', () => {
26+
const { container } = render(<BreadcrumbSkeleton items={5} />);
27+
const items = container.querySelectorAll(`.${prefix}--breadcrumb-item`);
28+
expect(items).toHaveLength(5);
29+
});
30+
31+
it('should respect size prop', () => {
32+
const { container } = render(<BreadcrumbSkeleton size="sm" />);
33+
expect(container.firstChild).toHaveClass(`${prefix}--breadcrumb--sm`);
34+
});
35+
36+
it('should accept a `noTrailingSlash` and omit the trailing slash', () => {
37+
const { container } = render(<BreadcrumbSkeleton noTrailingSlash />);
38+
39+
// The slashes are implemented with pseudo elements that can't be detected in jsdom.
40+
// So we have to settle here for just validating against the class. Pseudo elements
41+
// should be tested in the browser/e2e tests.
42+
// https://testing-library.com/docs/dom-testing-library/api-configuration/#computedstylesupportspseudoelements
43+
// https://github.com/jsdom/jsdom/issues/1928
44+
expect(container.firstChild).toHaveClass(
45+
`${prefix}--breadcrumb--no-trailing-slash`
46+
);
47+
});
2248
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Copyright IBM Corp. 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import '@carbon/web-components/es/components/breadcrumb/breadcrumb-skeleton.js';
9+
import { expect, fixture, html } from '@open-wc/testing';
10+
11+
describe('cds-toggle', () => {
12+
it('should support a custom class on the host', async () => {
13+
const el = await fixture(
14+
html`<cds-breadcrumb-skeleton
15+
class="custom-class"></cds-breadcrumb-skeleton>`
16+
);
17+
expect(el.classList.contains('custom-class')).to.be.true;
18+
});
19+
20+
it('should spread additional attributes on the host element', async () => {
21+
const el = await fixture(
22+
html`<cds-breadcrumb-skeleton
23+
data-testid="skeleton"></cds-breadcrumb-skeleton>`
24+
);
25+
expect(el.getAttribute('data-testid')).to.equal('skeleton');
26+
});
27+
28+
it('should render the specified number of skeleton items', async () => {
29+
const el = await fixture(
30+
html`<cds-breadcrumb-skeleton items="5"></cds-breadcrumb-skeleton>`
31+
);
32+
const items = el.shadowRoot?.querySelectorAll('.cds--breadcrumb-item');
33+
expect(items.length).to.equal(5);
34+
});
35+
36+
it('should respect the `size` attribute', async () => {
37+
const el = await fixture(
38+
html`<cds-breadcrumb-skeleton size="sm"> </cds-breadcrumb-skeleton>`
39+
);
40+
41+
const container = el.shadowRoot?.querySelector('.cds--breadcrumb');
42+
const classList = container?.classList || [];
43+
expect(
44+
Array.from(classList).some((cls) => cls.includes('--breadcrumb--sm'))
45+
).to.be.true;
46+
});
47+
48+
it('should accept a `no-trailing-slash` and omit the trailing slash', async () => {
49+
const el = await fixture(html`
50+
<cds-breadcrumb-skeleton no-trailing-slash> </cds-breadcrumb-skeleton>
51+
`);
52+
53+
const container = el.shadowRoot?.querySelector('.cds--breadcrumb');
54+
const items = container.querySelectorAll('.cds--breadcrumb-item');
55+
expect(items.length).to.be.greaterThan(0);
56+
57+
const lastItem = items[items.length - 1];
58+
59+
const lastItemStyle = window.getComputedStyle(lastItem, ':after');
60+
expect(lastItemStyle.content).to.equal('""');
61+
});
62+
});

packages/web-components/src/components/breadcrumb/breadcrumb-skeleton.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
/**
2-
* Copyright IBM Corp. 2019, 2024
2+
* Copyright IBM Corp. 2019, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import { classMap } from 'lit/directives/class-map.js';
89
import { LitElement, html } from 'lit';
10+
import { property } from 'lit/decorators.js';
911
import { prefix } from '../../globals/settings';
12+
import { BREADCRUMB_SIZE } from './defs';
1013
import styles from './breadcrumb.scss?lit';
1114
import { carbonElement as customElement } from '../../globals/decorators/carbon-element';
1215

@@ -23,10 +26,35 @@ const renderItem = () => {
2326
*/
2427
@customElement(`${prefix}-breadcrumb-skeleton`)
2528
class CDSBreadcrumbSkeleton extends LitElement {
29+
/**
30+
* Specify the number of items
31+
*/
32+
@property({ type: Number, reflect: true })
33+
items = 3;
34+
35+
/**
36+
* Optional prop to omit the trailing slash for the breadcrumbs
37+
*/
38+
@property({ type: Boolean, reflect: true, attribute: 'no-trailing-slash' })
39+
noTrailingSlash = false;
40+
41+
/**
42+
* Specify the size of the Breadcrumb. Currently
43+
* supports the following: `sm` & `md` (default: 'md')
44+
*/
45+
@property({ type: BREADCRUMB_SIZE, reflect: true })
46+
size = BREADCRUMB_SIZE.MEDIUM;
47+
2648
render() {
49+
const classes = classMap({
50+
[`${prefix}--breadcrumb`]: true,
51+
[`${prefix}--skeleton`]: true,
52+
[`${prefix}--breadcrumb--no-trailing-slash`]: this.noTrailingSlash,
53+
[`${prefix}--breadcrumb--sm`]: this.size === BREADCRUMB_SIZE.SMALL,
54+
});
2755
return html`
28-
<div class="${prefix}--breadcrumb ${prefix}--skeleton">
29-
${renderItem()} ${renderItem()} ${renderItem()}
56+
<div class="${classes}">
57+
${[...Array(this.items)].map(() => renderItem())}
3058
</div>
3159
`;
3260
}

packages/web-components/src/components/breadcrumb/breadcrumb.stories.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2019, 2024
2+
* Copyright IBM Corp. 2019, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -123,22 +123,32 @@ export const BreadcrumbWithOverflowMenu = {
123123
`;
124124
},
125125
};
126-
const skeletonArgs = { className: '' };
126+
const skeletonArgs = { items: 3, ...args };
127127
const skeletonArgTypes = {
128-
className: {
129-
control: 'text',
130-
description:
131-
'Specify an optional className to be applied to the container node.',
128+
items: {
129+
control: 'number',
130+
description: 'Specify the number of items',
132131
},
132+
...argTypes,
133133
};
134134

135135
export const Skeleton = {
136136
args: skeletonArgs,
137137
argTypes: skeletonArgTypes,
138+
parameters: {
139+
controls: {
140+
exclude: ['aria-label'],
141+
},
142+
},
138143
render: (args) => {
139-
const { className } = args ?? {};
144+
const { className, noTrailingSlash, size, items } = args ?? {};
140145
return html`
141-
<cds-breadcrumb-skeleton .class="${className}"> </cds-breadcrumb-skeleton>
146+
<cds-breadcrumb-skeleton
147+
.size="${size}"
148+
.class="${className}"
149+
?no-trailing-slash="${noTrailingSlash}"
150+
items="${items}">
151+
</cds-breadcrumb-skeleton>
142152
`;
143153
},
144154
};

0 commit comments

Comments
 (0)