Skip to content

Commit a2a88be

Browse files
feat: init guide banner component (#8513)
* feat: init guide banner component * fix: testing * fix: lint * fix: build * fix: style import * fix: import * fix: remove marker * fix: icon and footer defaults --------- Co-authored-by: Jeff Longshore <[email protected]>
1 parent 4cc29b2 commit a2a88be

File tree

9 files changed

+452
-4
lines changed

9 files changed

+452
-4
lines changed

packages/ibm-products-styles/src/components/Guidebanner/_guidebanner.scss

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,9 @@ $purple-3: #7433e3;
6767

6868
.#{$block-class}__icon-idea {
6969
position: absolute;
70+
color: $gray-10;
7071
inset-block-start: $spacing-05;
7172
inset-inline-start: $spacing-05;
72-
73-
path {
74-
fill: $gray-10;
75-
}
7673
}
7774

7875
.#{$block-class}__title {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
*
4+
* Copyright IBM Corp. 2025
5+
*
6+
* This source code is licensed under the Apache-2.0 license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
import { LitElement, html } from 'lit';
11+
import { prefix } from '../../globals/settings';
12+
import HostListenerMixin from '@carbon/web-components/es/globals/mixins/host-listener.js';
13+
import { carbonElement as customElement } from '@carbon/web-components/es/globals/decorators/carbon-element.js';
14+
15+
export const blockClass = `${prefix}--guidebanner__element`;
16+
17+
@customElement(`${prefix}-guide-banner-element`)
18+
class CDSGuideBannerElement extends HostListenerMixin(LitElement) {
19+
render() {
20+
return html`
21+
<div class="${blockClass}">
22+
<slot></slot>
23+
</div>
24+
`;
25+
}
26+
}
27+
28+
export default CDSGuideBannerElement;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright IBM Corp. 2025, 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+
$css--plex: true !default;
9+
10+
@use '@carbon/styles/scss/theme' as *;
11+
@use '@carbon/styles/scss/spacing' as *;
12+
@use '@carbon/styles/scss/reset';
13+
@use '@carbon/styles/scss/type';
14+
@use '@carbon/ibm-products-styles/scss/config';
15+
@use '@carbon/ibm-products-styles/scss/components/Guidebanner' as *;
16+
17+
$prefix: config.$pkg-prefix;
18+
19+
:host(#{$prefix}-guide-banner) {
20+
details {
21+
display: flex;
22+
flex-direction: column-reverse;
23+
}
24+
25+
::marker,
26+
::-webkit-details-marker {
27+
display: none;
28+
content: '';
29+
}
30+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* @license
3+
*
4+
* Copyright IBM Corp. 2025
5+
*
6+
* This source code is licensed under the Apache-2.0 license found in the
7+
* LICENSE file in the root directory of this source tree.
8+
*/
9+
10+
import { html } from 'lit';
11+
import { fn } from 'storybook/test';
12+
import './index';
13+
import styles from './story-styles.scss?lit';
14+
15+
const argTypes = {};
16+
17+
const blockClass = 'guide-banner-story';
18+
19+
const renderTemplate = (args) => {
20+
const {
21+
'@c4p-guidebanner-ontoggle': handleToggle,
22+
'@c4p-guidebanner-onclose': handleOnClose,
23+
collapseText,
24+
expandText,
25+
titleText,
26+
open,
27+
} = args;
28+
return html`
29+
<style>
30+
${styles}
31+
</style>
32+
<c4p-guide-banner
33+
@c4p-guidebanner-toggle=${handleToggle}
34+
@c4p-guidebanner-close=${handleOnClose}
35+
class=${blockClass}
36+
collapseText=${collapseText}
37+
expandText=${expandText}
38+
?open=${open}
39+
titleText=${titleText}
40+
>
41+
<div slot="body">
42+
<div class="body-container">
43+
<c4p-guide-banner-element class="body-elm">
44+
<p>example body content</p>
45+
</c4p-guide-banner-element>
46+
<c4p-guide-banner-element class="body-elm">
47+
<p>example body content</p>
48+
</c4p-guide-banner-element>
49+
<c4p-guide-banner-element class="body-elm">
50+
<p>example body content</p>
51+
</c4p-guide-banner-element>
52+
</div>
53+
</div>
54+
</c4p-guide-banner>
55+
`;
56+
};
57+
58+
export const Default = {
59+
args: {
60+
'@c4p-guidebanner-ontoggle': fn(),
61+
'@c4p-guidebanner-onclose': fn(),
62+
collapseText: 'Read less',
63+
expandText: 'Read more',
64+
titleText: 'Page-related heading that can stand on its own',
65+
open: true,
66+
},
67+
argTypes,
68+
render: renderTemplate,
69+
};
70+
71+
const meta = {
72+
title: 'Components/GuideBanner',
73+
};
74+
75+
export default meta;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
import { describe, expect, it } from 'vitest';
8+
import { fixture, html, oneEvent } from '@open-wc/testing';
9+
import CDSGuideBanner, { blockClass } from './guide-banner';
10+
11+
const defaultEvents = {
12+
handleToggle: () => {},
13+
handleClose: () => {},
14+
};
15+
16+
const defaultProps = {
17+
collapseText: 'Collapse',
18+
expandText: 'Expand',
19+
open: false,
20+
titleText: 'Test title',
21+
};
22+
23+
const defaultSlots = {
24+
header: 'Test header content',
25+
body: 'Test body content',
26+
footer: 'Test footer content',
27+
};
28+
29+
const templateArgs = {
30+
events: defaultEvents,
31+
props: defaultProps,
32+
slots: defaultSlots,
33+
};
34+
35+
const template = (args = {}) => {
36+
const { props, slots, events } = { ...templateArgs, ...args };
37+
return html`
38+
<c4p-guide-banner
39+
@c4p-guidebanner-toggle=${events.handleToggle}
40+
@c4p-guidebanner-close=${events.handleClose}
41+
class=${blockClass}
42+
collapseText=${props.collapseText}
43+
expandText=${props.expandText}
44+
?open=${props.open}
45+
titleText=${props.titleText}
46+
>
47+
<div slot="header">
48+
<div class="body">${slots.header}</div>
49+
</div>
50+
<div slot="body">
51+
<div class="body">${slots.body}</div>
52+
</div>
53+
<div slot="footer">
54+
<div class="footer">${slots.footer}</div>
55+
</div>
56+
</c4p-guide-banner>
57+
`;
58+
};
59+
60+
describe('c4p-options-tile', () => {
61+
it('renders guidebanner', async () => {
62+
const el: CDSGuideBanner = await fixture(template());
63+
expect(el).toBeDefined();
64+
});
65+
66+
it('renders a title', async () => {
67+
const el: CDSGuideBanner = await fixture(template());
68+
expect(el).toBeDefined();
69+
expect(el.titleText).to.equal(defaultProps.titleText);
70+
const titleEl = el.shadowRoot?.querySelector(`.${blockClass}__title`);
71+
expect(titleEl).toBeDefined();
72+
});
73+
74+
it('renders a header', async () => {
75+
const el: CDSGuideBanner = await fixture(template());
76+
const slot = el.shadowRoot?.querySelector(
77+
`slot[name="header"]`
78+
) as HTMLSlotElement;
79+
expect(slot).toBeTruthy();
80+
const node = slot.assignedNodes()[0] as HTMLElement;
81+
const { innerText } = node;
82+
expect(innerText).toBe(defaultSlots.header);
83+
});
84+
85+
it('renders a body', async () => {
86+
const el: CDSGuideBanner = await fixture(
87+
template({ props: { open: true } })
88+
);
89+
const slot = el.shadowRoot?.querySelector(
90+
`slot[name="body"]`
91+
) as HTMLSlotElement;
92+
expect(slot).toBeTruthy();
93+
const node = slot.assignedNodes()[0] as HTMLElement;
94+
const { innerText } = node;
95+
expect(innerText).toBe(defaultSlots.body);
96+
});
97+
98+
it('renders a footer', async () => {
99+
const el: CDSGuideBanner = await fixture(
100+
template({ props: { open: true } })
101+
);
102+
const slot = el.shadowRoot?.querySelector(
103+
`slot[name="footer"]`
104+
) as HTMLSlotElement;
105+
expect(slot).toBeTruthy();
106+
const node = slot.assignedNodes()[0] as HTMLElement;
107+
const { innerText } = node;
108+
expect(innerText).toBe(defaultSlots.footer);
109+
});
110+
111+
it('fires toggle handler', async () => {
112+
const el: CDSGuideBanner = await fixture(template());
113+
const toggleBtn = el.shadowRoot?.querySelector(
114+
`.${blockClass}__toggle-button`
115+
) as HTMLElement;
116+
const listener = oneEvent(el, 'c4p-guidebanner-toggle');
117+
toggleBtn?.click();
118+
const { detail } = await listener;
119+
expect(detail).toBeTruthy();
120+
});
121+
122+
it('fires close handler', async () => {
123+
const el: CDSGuideBanner = await fixture(
124+
template({ props: { open: true } })
125+
);
126+
const closeBtn = el.shadowRoot?.querySelector(
127+
`.${blockClass}__close-button`
128+
) as HTMLElement;
129+
const listener = oneEvent(el, 'c4p-guidebanner-close');
130+
closeBtn?.click();
131+
const { detail } = await listener;
132+
expect(detail).toBeTruthy();
133+
});
134+
});

0 commit comments

Comments
 (0)