Skip to content

Commit

Permalink
feat: add show/hide animation for dialog header
Browse files Browse the repository at this point in the history
  • Loading branch information
dauriamarco committed Nov 24, 2023
1 parent ce69767 commit 18747a1
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 24 deletions.
33 changes: 29 additions & 4 deletions src/components/dialog/dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
--sbb-dialog-backdrop-pointer-events: none;
--sbb-dialog-backdrop-color: transparent;
--sbb-dialog-header-padding-block: var(--sbb-spacing-responsive-s) 0;
--sbb-dialog-header-transition-duration: var(--sbb-animation-duration-6x);
--sbb-dialog-header-margin-block-start: 0;
--sbb-dialog-footer-border: var(--sbb-border-width-1x) solid var(--sbb-color-cloud-default);

position: fixed;
Expand Down Expand Up @@ -59,6 +61,18 @@
}
}

:host([data-hide-header]) {
--sbb-dialog-header-margin-block-start: calc(var(--sbb-dialog-header-height) * -1);

// Hide transition
--sbb-dialog-header-transition-timing: cubic-bezier(0.4, 0, 1, 1);
}

:host(:not([data-hide-header])) {
// Show transition
--sbb-dialog-header-transition-timing: cubic-bezier(0, 0, 0.2, 1);
}

:host([data-fullscreen]) {
--sbb-dialog-backdrop-color: transparent;
--sbb-dialog-max-width: 100%;
Expand Down Expand Up @@ -198,6 +212,16 @@
pointer-events: all;
}

// Apply show/hide animation unless it has a visible focus within.
&:not([data-has-visible-focus]:focus-within) {
margin-block-start: var(--sbb-dialog-header-margin-block-start);
transition: {
property: margin, box-shadow;
duration: var(--sbb-dialog-header-transition-duration);
timing-function: var(--sbb-dialog-header-transition-timing);
}
}

:host([data-fullscreen]) & {
position: fixed;
width: var(--sbb-dialog-width);
Expand All @@ -206,6 +230,11 @@
padding-block-start: var(--sbb-spacing-responsive-xs);
}

&[data-has-visible-focus]:focus-within,
:host([data-overflows]:not([data-fullscreen], [negative], [data-hide-header])) & {
@include sbb.shadow-level-9-soft;
}

@include sbb.mq($from: medium) {
border-radius: var(--sbb-dialog-border-radius) var(--sbb-dialog-border-radius) 0 0;
}
Expand Down Expand Up @@ -244,15 +273,11 @@
margin-block-start: auto;
background-color: var(--sbb-dialog-background-color);
border-block-start: var(--sbb-dialog-footer-border);
}

// stylelint-disable selector-not-notation
:is(.sbb-dialog__header, .sbb-dialog__footer) {
:host([data-overflows]:not([data-fullscreen], [negative])) & {
@include sbb.shadow-level-9-soft;
}
}
// stylelint-enable selector-not-notation

.sbb-screen-reader-only {
@include sbb.screen-reader-only;
Expand Down
112 changes: 92 additions & 20 deletions src/components/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { CSSResultGroup, html, LitElement, nothing, TemplateResult } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';

import { FocusTrap, IS_FOCUSABLE_QUERY, setModalityOnNextFocus } from '../core/a11y';
import {
FocusTrap,
IS_FOCUSABLE_QUERY,
setModalityOnNextFocus,
sbbInputModalityDetector,
} from '../core/a11y';
import {
ScrollHandler,
toggleDatasetEntry,
Expand Down Expand Up @@ -144,15 +149,17 @@ export class SbbDialog extends LitElement {
private _backClick: EventEmitter<void> = new EventEmitter(this, SbbDialog.events.backClick);

private _dialog: HTMLDivElement;
private _dialogWrapperElement: HTMLElement;
private _dialogHeaderElement: HTMLElement;
private _dialogContentElement: HTMLElement;
private _dialogCloseElement: HTMLElement;
private _dialogController: AbortController;
private _windowEventsController: AbortController;
private _openDialogController: AbortController;
private _focusTrap = new FocusTrap();
private _scrollHandler = new ScrollHandler();
private _returnValue: any;
private _isPointerDownEventOnDialog: boolean;
private _overflows: boolean;
private _lastScroll = 0;
private _dialogId = `sbb-dialog-${nextId++}`;

// Last element which had focus before the dialog was opened.
Expand Down Expand Up @@ -220,6 +227,42 @@ export class SbbDialog extends LitElement {
}
}

private _onContentScroll(): void {
const hasVisibleHeader = this.dataset.hideHeader === undefined;
const dialogHeaderHeight = this._dialogHeaderElement.clientHeight;

// Check whether hiding the header would make the scrollbar disappear
// and prevent the hiding animation if so.
if (
hasVisibleHeader &&
this._dialogContentElement.clientHeight + dialogHeaderHeight >=
this._dialogContentElement.scrollHeight
) {
return;
}

const currentScroll = this._dialogContentElement.scrollTop;
if (
Math.round(currentScroll + this._dialogContentElement.clientHeight) >=
this._dialogContentElement.scrollHeight
) {
return;
}
// Check whether is scrolling down or up.
if (currentScroll > 0 && this._lastScroll < currentScroll) {
// Scrolling down
if (hasVisibleHeader) {
this.style.setProperty('--sbb-dialog-header-height', `${dialogHeaderHeight}px`);
}
toggleDatasetEntry(this, 'hideHeader', true);
} else {
// Scrolling up
toggleDatasetEntry(this, 'hideHeader', false);
}
// `currentScroll` can be negative, e.g. on mobile; this is not allowed.
this._lastScroll = currentScroll <= 0 ? 0 : currentScroll;
}

public override connectedCallback(): void {
super.connectedCallback();
this._handlerRepository.connect();
Expand All @@ -244,7 +287,7 @@ export class SbbDialog extends LitElement {
super.disconnectedCallback();
this._handlerRepository.disconnect();
this._dialogController?.abort();
this._windowEventsController?.abort();
this._openDialogController?.abort();
this._focusTrap.disconnect();
this._dialogContentResizeObserver.disconnect();
this._removeInstanceFromGlobalCollection();
Expand All @@ -260,8 +303,8 @@ export class SbbDialog extends LitElement {
dialogRefs.splice(dialogRefs.indexOf(this as SbbDialog), 1);
}

private _attachWindowEvents(): void {
this._windowEventsController = new AbortController();
private _attachOpenDialogEvents(): void {
this._openDialogController = new AbortController();
// Remove dialog label as soon as it is not needed anymore to prevent accessing it with browse mode.
window.addEventListener(
'keydown',
Expand All @@ -270,12 +313,39 @@ export class SbbDialog extends LitElement {
await this._onKeydownEvent(event);
},
{
signal: this._windowEventsController.signal,
signal: this._openDialogController.signal,
},
);
window.addEventListener('click', () => this._removeAriaLiveRefContent(), {
signal: this._windowEventsController.signal,
signal: this._openDialogController.signal,
});
// If the content overflows, apply the header animation on scroll.
if (this._overflows) {
this._dialogContentElement?.addEventListener('scroll', () => this._onContentScroll(), {
passive: true,
signal: this._openDialogController.signal,
});
Array.from(this._dialogHeaderElement.querySelectorAll('sbb-button'))?.forEach((el) => {
el.addEventListener(
'focusin',
() => {
toggleDatasetEntry(
this._dialogHeaderElement,
'hasVisibleFocus',
sbbInputModalityDetector.mostRecentModality === 'keyboard',
);
},
{ signal: this._openDialogController.signal },
);
el.addEventListener(
'blur',
() => {
toggleDatasetEntry(this._dialogHeaderElement, 'hasVisibleFocus', false);
},
{ signal: this._openDialogController.signal },
);
});
}
}

// Check if the pointerdown event target is triggered on the dialog.
Expand Down Expand Up @@ -334,10 +404,11 @@ export class SbbDialog extends LitElement {
setTimeout(() => this._setAriaLiveRefContent());
this._focusTrap.trap(this);
this._dialogContentResizeObserver.observe(this._dialogContentElement);
this._attachWindowEvents();
this._attachOpenDialogEvents();
} else if (event.animationName === 'close' && this._state === 'closing') {
toggleDatasetEntry(this, 'hideHeader', false);
this._dialogContentElement.scrollTo(0, 0);
this._state = 'closed';
this._dialogWrapperElement.querySelector('.sbb-dialog__content').scrollTo(0, 0);
removeInertMechanism();
setModalityOnNextFocus(this._lastFocusedElement);
// Manually focus last focused element
Expand All @@ -346,7 +417,7 @@ export class SbbDialog extends LitElement {
returnValue: this._returnValue,
closeTarget: this._dialogCloseElement,
});
this._windowEventsController?.abort();
this._openDialogController?.abort();
this._focusTrap.disconnect();
this._dialogContentResizeObserver.disconnect();
this._removeInstanceFromGlobalCollection();
Expand Down Expand Up @@ -382,11 +453,12 @@ export class SbbDialog extends LitElement {
}

private _setOverflowAttribute(): void {
toggleDatasetEntry(
this,
'overflows',
this._dialogContentElement.scrollHeight > this._dialogContentElement.clientHeight,
);
this._overflows =
this._dialogContentElement.scrollHeight > this._dialogContentElement.clientHeight;
if (!this._overflows && this.dataset.hideHeader === '') {
toggleDatasetEntry(this, 'hideHeader', false);
}
toggleDatasetEntry(this, 'overflows', this._overflows);
}

protected override render(): TemplateResult {
Expand Down Expand Up @@ -420,7 +492,10 @@ export class SbbDialog extends LitElement {
`;

const dialogHeader = html`
<div class="sbb-dialog__header">
<div
class="sbb-dialog__header"
${ref((dialogHeaderrRef) => (this._dialogHeaderElement = dialogHeaderrRef as HTMLElement))}
>
${this.titleBackButton ? backButton : nothing}
${hasTitle
? html`<sbb-title
Expand Down Expand Up @@ -455,9 +530,6 @@ export class SbbDialog extends LitElement {
>
<div
@click=${(event: Event) => this._closeOnSbbDialogCloseClick(event)}
${ref(
(dialogWrapperRef) => (this._dialogWrapperElement = dialogWrapperRef as HTMLElement),
)}
class="sbb-dialog__wrapper"
>
${dialogHeader}
Expand Down

0 comments on commit 18747a1

Please sign in to comment.