diff --git a/packages/ibm-products-web-components/src/components/side-panel/_story-assets/index.ts b/packages/ibm-products-web-components/src/components/side-panel/_story-assets/index.ts
index a2fe8714703..90119fbd634 100644
--- a/packages/ibm-products-web-components/src/components/side-panel/_story-assets/index.ts
+++ b/packages/ibm-products-web-components/src/components/side-panel/_story-assets/index.ts
@@ -129,26 +129,43 @@ export const getActionToolbarItems = (index) => {
// TODO: There are problems switching this
export const getActionItems = (index) => {
switch (index) {
- case 1:
- return html`Primary`;
- case 2:
+ case 2: // One button (ghost)
+ return html`Ghost`;
+ case 3: // One button (danger)
+ return html`Danger`;
+ case 4: // Two buttons
+ return html`
+ Secondary
+ Primary
+ `;
+ case 5: // Two buttons with ghost
return html`
Ghost
Primary
`;
- case 3:
- return html` Danger
Primary`;
- case 4:
- return html` Ghost
{
Primary`;
- case 5:
- return html`Danger
- Secondary
- Primary`;
- case 6:
- return html`Danger
- TertiarySecondary
- Secondary
- Primary`;
default:
diff --git a/packages/ibm-products-web-components/src/components/side-panel/defs.ts b/packages/ibm-products-web-components/src/components/side-panel/defs.ts
index bacc45894bb..4ef25596f3c 100644
--- a/packages/ibm-products-web-components/src/components/side-panel/defs.ts
+++ b/packages/ibm-products-web-components/src/components/side-panel/defs.ts
@@ -31,6 +31,11 @@ export enum SIDE_PANEL_SIZE {
*/
LARGE = 'lg',
+ /**
+ * Extra Large size.
+ */
+ EXTRA_LARGE = 'xl',
+
/**
* 2X-Large size.
*/
diff --git a/packages/ibm-products-web-components/src/components/side-panel/side-panel.scss b/packages/ibm-products-web-components/src/components/side-panel/side-panel.scss
index ecae77c6cde..80cec659564 100644
--- a/packages/ibm-products-web-components/src/components/side-panel/side-panel.scss
+++ b/packages/ibm-products-web-components/src/components/side-panel/side-panel.scss
@@ -77,7 +77,7 @@ $block-class-action-set: #{$prefix}--action-set;
@extend .#{$block-class}--right-placement;
/* remove if https://github.com/carbon-design-system/ibm-products/pull/3983 merged */
- border-inline-end: 1px solid $border-subtle-02;
+ border-inline-end: none;
inset-inline-end: 0;
}
@@ -86,7 +86,7 @@ $block-class-action-set: #{$prefix}--action-set;
@extend .#{$block-class}--left-placement;
/* remove if https://github.com/carbon-design-system/ibm-products/pull/3983 merged */
- border-inline-end: 1px solid $border-subtle-02;
+ border-inline-end: 1px solid $border-subtle-01;
inset-inline-start: 0;
}
@@ -167,9 +167,17 @@ $block-class-action-set: #{$prefix}--action-set;
content: initial; /* remove border below */
}
+ .#{$block-class}__title-text {
+ padding-inline-end: 0;
+ }
+
.#{$block-class}__subtitle-text {
+ @extend .#{$block-class}__subtitle-text;
+
&[hidden] {
@extend .#{carbon-config.$prefix}--visually-hidden !optional;
+
+ display: none;
}
}
@@ -180,6 +188,10 @@ $block-class-action-set: #{$prefix}--action-set;
&[hidden] {
@extend .#{carbon-config.$prefix}--visually-hidden !optional;
}
+
+ ::slotted(.#{$block-class}__action-toolbar-leading-button) {
+ margin-inline-end: $spacing-03;
+ }
}
.#{$block-class} .#{$block-class}__action-toolbar[hidden] {
@@ -190,6 +202,23 @@ $block-class-action-set: #{$prefix}--action-set;
@extend .#{$block-class}--has-slug;
}
+ .#{$block-class}[has-slug] [scrolls] {
+ /* override carbon ai removing background gradient */
+ background:
+ linear-gradient(
+ to top,
+ var(--cds-layer) 0%,
+ $ai-aura-start 0%,
+ 15%,
+ $ai-aura-end 50%
+ )
+ padding-box,
+ linear-gradient(to top, var(--cds-layer), var(--cds-layer)) padding-box,
+ linear-gradient(to bottom, $ai-border-start, $ai-border-end) border-box,
+ linear-gradient(to top, var(--cds-layer), var(--cds-layer)) border-box;
+ box-shadow: inset 0 -80px 70px -65px $ai-inner-shadow;
+ }
+
[scrolls] {
@extend .#{$block-class}--scrolls;
}
@@ -244,7 +273,7 @@ $block-class-action-set: #{$prefix}--action-set;
}
&[actions-multiple='triple'] {
- --flex-direction: column;
+ --flex-direction: column-reverse;
::slotted(#{carbon-config.$prefix}-button) {
flex: initial;
@@ -256,7 +285,7 @@ $block-class-action-set: #{$prefix}--action-set;
// -1 in @container query is for 1px left border
@container (width <= #{map.get(spv.$side-panel-sizes, sm)}) {
- --flex-direction: column;
+ --flex-direction: column-reverse;
::slotted(#{carbon-config.$prefix}-button) {
flex: initial;
@@ -271,6 +300,7 @@ $block-class-action-set: #{$prefix}--action-set;
@include actions-placement();
display: flex;
+ gap: 1px;
inline-size: 100%;
::slotted(#{carbon-config.$prefix}-button) {
diff --git a/packages/ibm-products-web-components/src/components/side-panel/side-panel.stories.ts b/packages/ibm-products-web-components/src/components/side-panel/side-panel.stories.ts
index 0f05daddb47..7dbce6295e0 100644
--- a/packages/ibm-products-web-components/src/components/side-panel/side-panel.stories.ts
+++ b/packages/ibm-products-web-components/src/components/side-panel/side-panel.stories.ts
@@ -50,6 +50,8 @@ const sizes = {
[`Small size (${SIDE_PANEL_SIZE.SMALL})`]: SIDE_PANEL_SIZE.SMALL,
[`Medium size (default) (${SIDE_PANEL_SIZE.MEDIUM})`]: SIDE_PANEL_SIZE.MEDIUM,
[`Large size (${SIDE_PANEL_SIZE.LARGE})`]: SIDE_PANEL_SIZE.LARGE,
+ [`Extra Large size (${SIDE_PANEL_SIZE.EXTRA_LARGE})`]:
+ SIDE_PANEL_SIZE.EXTRA_LARGE,
[`Extra Extra Large size (${SIDE_PANEL_SIZE.EXTRA_EXTRA_LARGE})`]:
SIDE_PANEL_SIZE.EXTRA_EXTRA_LARGE,
};
@@ -97,13 +99,16 @@ const actionToolbarItems = {
};
const actionItems = {
- 'No actions': 0,
'One button': 1,
- 'Two buttons with ghost': 2,
- 'Two buttons with danger': 3,
- 'Three buttons with ghost': 4,
- 'Three buttons with danger': 5,
- 'Too many buttons': 6,
+ 'One button (ghost)': 2,
+ 'One button (danger)': 3,
+ 'Two buttons': 4,
+ 'Two buttons with ghost': 5,
+ 'Two buttons with danger': 6,
+ 'Three buttons with ghost': 7,
+ 'Three buttons with danger': 8,
+ 'Three buttons': 9,
+ None: 0,
};
const slugs = {
diff --git a/packages/ibm-products-web-components/src/components/side-panel/side-panel.test.ts b/packages/ibm-products-web-components/src/components/side-panel/side-panel.test.ts
index e71569c5d45..a873c4ab544 100644
--- a/packages/ibm-products-web-components/src/components/side-panel/side-panel.test.ts
+++ b/packages/ibm-products-web-components/src/components/side-panel/side-panel.test.ts
@@ -342,7 +342,7 @@ describe('c4p-side-panel', () => {
`.${prefix}--action-set__action-button`
);
- expect(actionItems).toHaveLength(3);
+ expect(actionItems).toHaveLength(2);
});
it('should display a close button by default', async () => {
diff --git a/packages/ibm-products-web-components/src/components/side-panel/side-panel.ts b/packages/ibm-products-web-components/src/components/side-panel/side-panel.ts
index 128137d130f..953d80105fa 100644
--- a/packages/ibm-products-web-components/src/components/side-panel/side-panel.ts
+++ b/packages/ibm-products-web-components/src/components/side-panel/side-panel.ts
@@ -144,33 +144,132 @@ class CDSSidePanel extends HostListenerMixin(LitElement) {
last: HTMLElement | undefined;
all: HTMLElement[];
} {
- const elements: NodeListOf[] = [];
+ const elements: HTMLElement[] = [];
- // Add slug elements if present
+ // Add back button if present (shadow DOM)
+ if (this.currentStep > 0) {
+ const backButton = this.shadowRoot?.querySelector(
+ `.${blockClass}__navigation-back-button`
+ );
+ if (backButton) {
+ elements.push(backButton);
+ }
+ }
+
+ // Add tabbable elements from above-title slot (light DOM - breadcrumbs, etc.)
+ const aboveTitleSlot = this.shadowRoot?.querySelector(
+ 'slot[name="above-title"]'
+ );
+ if (aboveTitleSlot) {
+ const aboveTitleElements = aboveTitleSlot
+ .assignedElements({ flatten: true })
+ .flatMap((el) =>
+ Array.from(el.querySelectorAll(selectorTabbable))
+ );
+ elements.push(...aboveTitleElements);
+ }
+
+ // Add label text if present (shadow DOM)
+ const labelText = this.shadowRoot?.querySelector(
+ `.${blockClass}__label-text`
+ );
+ if (labelText) {
+ elements.push(labelText);
+ }
+
+ // Add title if present (shadow DOM)
+ const titleText = this.shadowRoot?.querySelector(
+ `.${blockClass}__title-text`
+ );
+ if (titleText) {
+ elements.push(titleText);
+ }
+
+ // Add slug elements if present (light DOM)
if (this._hasSlug) {
- elements.push(this.querySelectorAll(`${carbonPrefix}-slug`));
+ const slugElements = Array.from(
+ this.querySelectorAll(`${carbonPrefix}-slug`)
+ );
+ elements.push(...slugElements);
}
- // Add close button if not hidden
+ // Add close button if not hidden (shadow DOM)
if (!this.hideCloseButton) {
- const closeButtons = this.shadowRoot?.querySelectorAll(
- `${carbonPrefix}-icon-button`
+ const closeButton = this.shadowRoot?.querySelector(
+ `.${blockClass}__close-button`
);
- if (closeButtons) {
- elements.push(closeButtons);
+ if (closeButton) {
+ elements.push(closeButton);
}
}
- // Add tabbable elements inside light DOM
- const _tabbableItems = this.querySelectorAll(selectorTabbable);
- if (_tabbableItems) {
- elements.push(_tabbableItems);
+ // Add subtitle if present (shadow DOM)
+ const subtitleText = this.shadowRoot?.querySelector(
+ `.${blockClass}__subtitle-text`
+ );
+ if (subtitleText && !subtitleText.hidden) {
+ elements.push(subtitleText);
+ }
+
+ // Add tabbable elements from below-title slot (light DOM)
+ const belowTitleSlot = this.shadowRoot?.querySelector(
+ 'slot[name="below-title"]'
+ );
+ if (belowTitleSlot) {
+ const belowTitleElements = belowTitleSlot
+ .assignedElements({ flatten: true })
+ .flatMap((el) =>
+ Array.from(el.querySelectorAll(selectorTabbable))
+ );
+ elements.push(...belowTitleElements);
}
- // Flatten NodeList arrays and filter for focusable items
- const all = elements
- ?.flatMap((nodeList) => Array.from(nodeList))
- ?.filter((el): el is HTMLElement => typeof el?.focus === 'function');
+ // Add action toolbar elements (light DOM)
+ const actionToolbarSlot = this.shadowRoot?.querySelector(
+ 'slot[name="action-toolbar"]'
+ );
+ if (actionToolbarSlot) {
+ const actionToolbarElements = actionToolbarSlot
+ .assignedElements({ flatten: true })
+ .filter(
+ (el): el is HTMLElement =>
+ el instanceof HTMLElement &&
+ typeof (el as HTMLElement).focus === 'function'
+ );
+ elements.push(...actionToolbarElements);
+ }
+
+ // Add body content tabbable elements (light DOM - default slot)
+ const defaultSlot =
+ this.shadowRoot?.querySelector('slot:not([name])');
+ if (defaultSlot) {
+ const bodyElements = defaultSlot
+ .assignedElements({ flatten: true })
+ .flatMap((el) =>
+ Array.from(el.querySelectorAll(selectorTabbable))
+ );
+ elements.push(...bodyElements);
+ }
+
+ // Add action buttons (light DOM)
+ const actionsSlot = this.shadowRoot?.querySelector(
+ 'slot[name="actions"]'
+ );
+ if (actionsSlot) {
+ const actionElements = actionsSlot
+ .assignedElements({ flatten: true })
+ .filter(
+ (el): el is HTMLElement =>
+ el instanceof HTMLElement &&
+ typeof (el as HTMLElement).focus === 'function'
+ );
+ elements.push(...actionElements);
+ }
+
+ // Filter for focusable items
+ const all = elements.filter(
+ (el): el is HTMLElement => typeof el?.focus === 'function'
+ );
return {
first: all[0],
@@ -405,9 +504,21 @@ class CDSSidePanel extends HostListenerMixin(LitElement) {
this._hasActionToolbar = toolbarActions && toolbarActions.length > 0;
if (this._hasActionToolbar) {
- for (const toolbarAction of toolbarActions) {
+ for (let i = 0; i < toolbarActions.length; i++) {
+ const toolbarAction = toolbarActions[i];
// toolbar actions size should always be sm
toolbarAction.setAttribute('size', 'sm');
+
+ // Add leading button class to first button
+ if (i === 0) {
+ toolbarAction.classList.add(
+ `${blockClass}__action-toolbar-leading-button`
+ );
+ } else {
+ toolbarAction.classList.remove(
+ `${blockClass}__action-toolbar-leading-button`
+ );
+ }
}
}
}
@@ -709,7 +820,9 @@ class CDSSidePanel extends HostListenerMixin(LitElement) {
class=${`${blockClass}__title`}
?no-label=${!!labelText}
>
- ${title}
+
+ ${title}
+
${this._doAnimateTitle
? html`
${title?.length && labelText?.length
- ? html`
${labelText}
`
+ ? html`
+ ${labelText}
+
`
: ''}
@@ -787,6 +902,7 @@ class CDSSidePanel extends HostListenerMixin(LitElement) {
?no-title-animation=${!this._doAnimateTitle}
?no-action-toolbar=${!this._hasActionToolbar}
?no-title=${!title}
+ tabindex="0"
>