diff --git a/.yarn/cache/@carbon-ibm-products-styles-npm-2.27.1-936e33a14a-42ef53c93c.zip b/.yarn/cache/@carbon-ibm-products-styles-npm-2.27.1-936e33a14a-42ef53c93c.zip deleted file mode 100644 index 805eabc9acf..00000000000 Binary files a/.yarn/cache/@carbon-ibm-products-styles-npm-2.27.1-936e33a14a-42ef53c93c.zip and /dev/null differ diff --git a/.yarn/cache/@carbon-ibm-products-styles-npm-2.28.0-809aeccc71-e895f56284.zip b/.yarn/cache/@carbon-ibm-products-styles-npm-2.28.0-809aeccc71-e895f56284.zip new file mode 100644 index 00000000000..fded0d19726 Binary files /dev/null and b/.yarn/cache/@carbon-ibm-products-styles-npm-2.28.0-809aeccc71-e895f56284.zip differ diff --git a/packages/carbon-web-components/package.json b/packages/carbon-web-components/package.json index 8324dfda981..07d373090ed 100644 --- a/packages/carbon-web-components/package.json +++ b/packages/carbon-web-components/package.json @@ -70,7 +70,7 @@ ], "dependencies": { "@babel/runtime": "^7.16.3", - "@carbon/ibm-products-styles": "^2.27.1", + "@carbon/ibm-products-styles": "^2.28.0", "@carbon/styles": "1.51.1", "@floating-ui/dom": "^1.6.3", "@ibm/telemetry-js": "^1.2.1", diff --git a/packages/carbon-web-components/src/components/tearsheet/story-styles.scss b/packages/carbon-web-components/src/components/tearsheet/story-styles.scss index e96ea32a531..0384a186098 100644 --- a/packages/carbon-web-components/src/components/tearsheet/story-styles.scss +++ b/packages/carbon-web-components/src/components/tearsheet/story-styles.scss @@ -11,6 +11,13 @@ $story-prefix: 'tearsheet-stories'; @use '@carbon/styles/scss/spacing' as *; +#page-content-selector { + position: absolute; + z-index: 9999; + inset-block-start: 0; + inset-inline-start: 0; +} + .#{$story-prefix}__tabs .#{$prefix}--tab-content { display: none; } diff --git a/packages/carbon-web-components/src/components/tearsheet/tearsheet-story.ts b/packages/carbon-web-components/src/components/tearsheet/tearsheet-story.ts index 0e3f1ce4142..b4d244994f2 100644 --- a/packages/carbon-web-components/src/components/tearsheet/tearsheet-story.ts +++ b/packages/carbon-web-components/src/components/tearsheet/tearsheet-story.ts @@ -315,7 +315,7 @@ const slugs = { const getSlug = (index) => { switch (index) { case 1: - return html` + return html`

AI Explained

84%

@@ -503,3 +503,200 @@ NarrowWithAllHeaderItems.parameters = { }), }, }; + +const StackingTemplate = (argsIn) => { + const args = { + actionItems: getActionItems(select('Slot (actions)', actionItems, 4)), + headerActions: getActionToolbarItems( + select('Slot (header-toolbar)', headerActions, 0) + ), + content: getContent(select('Slot (default), panel contents', contents, 2)), + label: getLabel(select('label', labels, 1)), + open: boolean('open', false), + influencerWidth: select( + 'influencer-width', + influencerWidths, + TEARSHEET_INFLUENCER_WIDTH.NARROW + ), + influencerPlacement: select( + 'influencer-placement', + influencerPlacements, + TEARSHEET_INFLUENCER_PLACEMENT.LEFT + ), + + influencer: getInfluencer(select('influencer (slot)', influencers, 0)), + + preventCloseOnClickOutside: boolean( + 'prevent-close-on-click-outside', + false + ), + selectorInitialFocus: text('selector-initial-focus', ''), + width: select('width', widths, TEARSHEET_WIDTH.WIDE), + slug: getSlug(select('slug (AI slug)', slugs, 0)), + description: text( + 'description', + 'Description used to describe the flow if need be.' + ), + title: text( + 'title', + 'Title used to designate the overarching flow of the tearsheet.' + ), + headerNavigation: getNavigation(select('header-navigation', navigation, 0)), + + ...(argsIn?.['cds-tearsheet'] ?? {}), + }; + + const toggleButton = (index) => { + const tearsheet = document.querySelector(`[data-index="${index}"]`); + tearsheet?.toggleAttribute('open'); + }; + + return html` +
+
+
+ + Toggle tearsheet one + Toggle tearsheet two + Toggle tearsheet three + +
+
+ + + Toggle tearsheet two + ${args.content} + + + ${args.label} + + + ${args.title ? html`One ${args.title}` : ''} + + + ${args.description + ? html`${args.description}` + : ''} + + + ${args.headerActions} + + + ${args.actionItems} + + + ${args.slug} + + + ${args.headerNavigation} + + + ${args.influencer} + + + + Toggle tearsheet three + ${args.content} + + + ${args.label} + + + ${args.title ? html`Two ${args.title}` : ''} + + + ${args.description + ? html`${args.description}` + : ''} + + + ${args.headerActions} + + + ${args.actionItems} + + + ${args.slug} + + + ${args.headerNavigation} + + + ${args.influencer} + + + + ${args.content} + + + ${args.label} + + + ${args.title ? html`Three ${args.title}` : ''} + + + ${args.description + ? html`${args.description}` + : ''} + + + ${args.headerActions} + + + ${args.actionItems} + + + ${args.slug} + + + ${args.headerNavigation} + + + ${args.influencer} + + `; +}; + +export const Stacking = StackingTemplate.bind({}) as TemplateType; +Stacking.parameters = { + ...storyDocs.parameters, + knobs: { + 'cds-tearsheet': () => ({}), + }, +}; diff --git a/packages/carbon-web-components/src/components/tearsheet/tearsheet.scss b/packages/carbon-web-components/src/components/tearsheet/tearsheet.scss index 2678430d85a..0ace579470e 100644 --- a/packages/carbon-web-components/src/components/tearsheet/tearsheet.scss +++ b/packages/carbon-web-components/src/components/tearsheet/tearsheet.scss @@ -40,13 +40,16 @@ $motion-duration: $duration-moderate-02; @extend .#{$prefix}--tearsheet; &[open] { + --overlay-color: #{$overlay}; + --overlay-opacity: 1; + z-index: utilities.z('modal'); align-items: flex-end; + background: initial; opacity: 1; + // stylelint-disable-next-line carbon/motion-duration-use, carbon/motion-easing-use - transition: visibility 0s linear, - background-color $motion-duration motion(entrance, expressive), - opacity $motion-duration motion(entrance, expressive); + transition: visibility 0s linear; visibility: inherit; .#{$prefix}--tearsheet__container { @@ -57,6 +60,39 @@ $motion-duration: $duration-moderate-02; @media (prefers-reduced-motion: reduce) { transition: none; } + + &::before { + position: absolute; + display: block; + background: var(--overlay-color); + content: ''; + inset: 0; + opacity: var(--overlay-opacity); + + transition: background-color $motion-duration motion(exit, expressive), + opacity $motion-duration motion(exit, expressive); + + @media (prefers-reduced-motion: reduce) { + transition: none; + } + + &[stack-position='1'][stack-depth='2'] { + --overlay-opacity: 0.67; + } + + &[stack-position='1'][stack-depth='3'] { + --overlay-opacity: 0.22; + } + + &[stack-position='2'][stack-depth='3'] { + --overlay-opacity: 0.5; + } + + &[stack-position='2'][stack-depth='2'], + &[stack-position='3'][stack-depth='3'] { + --overlay-opacity: 0.5; + } + } } [hidden] { @@ -70,7 +106,24 @@ $motion-duration: $duration-moderate-02; } &[slug] { - background-color: $ai-overlay; + --overlay-color: #{$ai-overlay}; + + .#{$block-class}__container { + border: 1px solid transparent; + + /* override carbon ai removing background gradient */ + background: linear-gradient(to top, var(--cds-layer), var(--cds-layer)) + padding-box, + linear-gradient( + to bottom, + var(--cds-ai-border-start, #78a9ff), + var(--cds-ai-border-end, #d0e2ff) + ) + border-box, + linear-gradient(to top, var(--cds-layer), var(--cds-layer)) border-box; + border-block-end: 0; + box-shadow: 0 4px 10px 2px $ai-drop-shadow; + } .#{$block-class}__content { @include utilities.callout-gradient('default', 0); @@ -84,6 +137,35 @@ $motion-duration: $duration-moderate-02; } } + .#{$block-class}__container { + /* lower prop is deprecated but the default in ibm products */ + @extend .#{$block-class}__container--lower; + + &[stack-position='1'][stack-depth='2'], + &[stack-position='2'][stack-depth='3'] { + max-block-size: calc( + 100% - (#{$spacing-09} + #{$spacing-08}) + #{$spacing-05} + ); + transform: scale(var(--#{$block-class}--stacking-scale-factor-single)); + } + + &[stack-position='1'][stack-depth='3'] { + max-block-size: calc( + 100% - (#{$spacing-09} + #{$spacing-08}) + (2 * #{$spacing-05}) + ); + transform: scale(var(--#{$block-class}--stacking-scale-factor-double)); + } + } + + &[stack-position='1'][stack-depth='2'], + &[stack-position='2'][stack-depth='3'] { + z-index: utilities.z('modal') - 1; + } + + &[stack-position='1'][stack-depth='3'] { + z-index: utilities.z('modal') - 2; + } + &[width='narrow'] { .#{$block-class}__header { margin: 0; diff --git a/packages/carbon-web-components/src/components/tearsheet/tearsheet.ts b/packages/carbon-web-components/src/components/tearsheet/tearsheet.ts index 0f68c80c98a..6837ba5441d 100644 --- a/packages/carbon-web-components/src/components/tearsheet/tearsheet.ts +++ b/packages/carbon-web-components/src/components/tearsheet/tearsheet.ts @@ -35,6 +35,13 @@ export { TEARSHEET_WIDTH, }; +const maxStackDepth = 3; +type StackHandler = (newDepth: number, newPosition: number) => void; +interface StackState { + open: StackHandler[]; + all: StackHandler[]; +} + // eslint-disable-next-line no-bitwise const PRECEDING = Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS; @@ -168,6 +175,10 @@ class CDSTearsheet extends HostListenerMixin(LitElement) { @HostListener('shadowRoot:focusout') // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to private _handleBlur = async ({ target, relatedTarget }: FocusEvent) => { + if (!this._topOfStack()) { + return; + } + const { // condensedActions, open, @@ -228,7 +239,7 @@ class CDSTearsheet extends HostListenerMixin(LitElement) { @HostListener('document:keydown') // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to private _handleKeydown = ({ key, target }: KeyboardEvent) => { - if (key === 'Esc' || key === 'Escape') { + if ((key === 'Esc' || key === 'Escape') && this._topOfStack()) { this._handleUserInitiatedClose(target); } }; @@ -409,8 +420,81 @@ class CDSTearsheet extends HostListenerMixin(LitElement) { this._checkUpdateActionSizes(); } + // Data structure to communicate the state of tearsheet stacking + // (i.e. when more than one tearsheet is open). Each tearsheet supplies a + // handler to be called whenever the stacking of the tearsheets changes, which + // happens when a tearsheet opens or closes. The 'open' array contains one + // handler per OPEN tearsheet ordered from lowest to highest in visual z-order. + // The 'all' array contains all the handlers for open and closed tearsheets. + + @state() + _stackDepth = -1; + + @state() + _stackPosition = -1; + + private _topOfStack = () => { + return this._stackDepth === this._stackPosition; + }; + + private static _stack: StackState = { + open: [], + all: [], + }; + private _notifyStack = () => { + CDSTearsheet._stack.all.forEach( + (handler: (stackSize: number, position: number) => void) => { + handler( + Math.min(CDSTearsheet._stack.open.length, maxStackDepth), + CDSTearsheet._stack.open.indexOf(handler) + 1 + ); + } + ); + }; + + private _handleStackChange: StackHandler = (newDepth, newPosition) => { + this._stackDepth = newDepth; + this._stackPosition = newPosition; + if (this._stackDepth > 1 && this._stackPosition > 0) { + this.setAttribute('stack-position', `${newPosition}`); + this.setAttribute('stack-depth', `${this._stackDepth}`); + } else { + this.removeAttribute('stack-position'); + this.removeAttribute('stack-depth'); + } + }; + + private _updateStack = () => { + if (this.open) { + CDSTearsheet._stack.open.push(this._handleStackChange); + } else { + const indexOpen = CDSTearsheet._stack.open.indexOf( + this._handleStackChange + ); + if (indexOpen >= 0) { + CDSTearsheet._stack.open.splice(indexOpen, 1); + } + } + this._notifyStack(); + }; + actionsMultiple = ['', 'single', 'double', 'triple'][this._actionsCount]; + connectedCallback() { + super.connectedCallback(); + + CDSTearsheet._stack.all.push(this._handleStackChange); + } + + disconnectedCallback() { + super.disconnectedCallback(); + + const indexAll = CDSTearsheet._stack.all.indexOf(this._handleStackChange); + CDSTearsheet._stack.all.splice(indexAll, 1); + const indexOpen = CDSTearsheet._stack.all.indexOf(this._handleStackChange); + CDSTearsheet._stack.open.splice(indexOpen, 1); + } + render() { const { closeIconDescription, @@ -491,6 +575,8 @@ class CDSTearsheet extends HostListenerMixin(LitElement) { ?opening=${open && !this._isOpen} ?closing=${!open && this._isOpen} width=${width} + stack-position=${this._stackPosition} + stack-depth=${this._stackDepth} @click=${this._handleClickContainer}> ${headerTemplate} @@ -600,6 +686,8 @@ class CDSTearsheet extends HostListenerMixin(LitElement) { } if (changedProperties.has('open')) { + this._updateStack(); + this._checkSetOpen(); if (this.open) { this._launcher = this.ownerDocument!.activeElement; diff --git a/yarn.lock b/yarn.lock index 0bbc7288b4d..beb9947998a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3112,16 +3112,16 @@ __metadata: languageName: node linkType: hard -"@carbon/ibm-products-styles@npm:^2.27.1": - version: 2.27.1 - resolution: "@carbon/ibm-products-styles@npm:2.27.1" +"@carbon/ibm-products-styles@npm:^2.28.0": + version: 2.28.0 + resolution: "@carbon/ibm-products-styles@npm:2.28.0" peerDependencies: "@carbon/grid": ^11.21.1 "@carbon/layout": ^11.20.1 "@carbon/motion": ^11.16.1 "@carbon/themes": ^11.32.0 "@carbon/type": ^11.25.1 - checksum: 10/42ef53c93cd2049fa385203c2ddb157fcf65f693b03ee562b3611705ed8a35748a3386d51094373fb3aacec3e5dbba5e5aa166e77ba617a4106fc2a9a1e05845 + checksum: 10/e895f5628488dc9b66f4f669186b9ce69b990c3fb5259387a80980b8a185eb0df34abf46cf91d34fbf5556dfc8596c92347dbbdf3fde9a80ed387868b8c1df54 languageName: node linkType: hard @@ -3662,7 +3662,7 @@ __metadata: "@babel/runtime": "npm:^7.16.3" "@babel/template": "npm:~7.12.0" "@babel/traverse": "npm:~7.23.7" - "@carbon/ibm-products-styles": "npm:^2.27.1" + "@carbon/ibm-products-styles": "npm:^2.28.0" "@carbon/icon-helpers": "npm:10.46.0" "@carbon/icons": "npm:11.36.0" "@carbon/styles": "npm:1.51.1"