Skip to content

Commit

Permalink
fix(snackbar): allow snackbars to be stacked
Browse files Browse the repository at this point in the history
  • Loading branch information
2 people authored and adrianschmidt committed Aug 7, 2024
1 parent f48a332 commit 730c14a
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 193 deletions.
2 changes: 2 additions & 0 deletions etc/lime-elements.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ export namespace Components {
"dismissible": boolean;
"language": Languages;
"message": string;
// @deprecated
"multiline": boolean;
"show": () => Promise<void>;
"timeout"?: number;
Expand Down Expand Up @@ -1561,6 +1562,7 @@ namespace JSX_2 {
"dismissible"?: boolean;
"language"?: Languages;
"message"?: string;
// @deprecated
"multiline"?: boolean;
"onAction"?: (event: LimelSnackbarCustomEvent<void>) => void;
"onHide"?: (event: LimelSnackbarCustomEvent<void>) => void;
Expand Down
59 changes: 59 additions & 0 deletions src/components/snackbar/container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Container to keep track of all snackbar elements that gets added to the page.
* When an element gets added or removed, the container will emit a
* `changeOffset` event on all elements in the container, letting them know
* the new offset to where they should position themselves.
*/
export class SnackbarContainer {
private snackbarElements: HTMLLimelSnackbarElement[] = [];

/**
* Add a new element to the container
*
* @param snackbar - element to add
*/
public add(snackbar: HTMLLimelSnackbarElement) {
const popover = this.getPopover(snackbar);

// Stencil does not seem to recognise the existance of showPopover
// @ts-ignore
popover?.showPopover();

this.snackbarElements = [snackbar, ...this.snackbarElements];
this.emitOffsets();
}

/**
* Remove an element from the container
*
* @param snackbar - element to remove
*/
public remove(snackbar: HTMLLimelSnackbarElement): void {
const popover = this.getPopover(snackbar);

// Stencil does not seem to recognise the existance of hidePopover
// @ts-ignore
popover?.hidePopover();

this.snackbarElements = this.snackbarElements.filter(
(item) => item !== snackbar,
);
this.emitOffsets();
}

private emitOffsets() {
let offset = 0;
this.snackbarElements.forEach((snackbar) => {
snackbar.dispatchEvent(
new CustomEvent('changeOffset', {
detail: offset,
}),
);
offset += this.getPopover(snackbar).getBoundingClientRect().height;
});
}

private getPopover(snackbar: HTMLLimelSnackbarElement) {
return snackbar.shadowRoot.querySelector('[popover]');
}
}
10 changes: 5 additions & 5 deletions src/components/snackbar/examples/snackbar-dismissible.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { Component, Element, h, State } from '@stencil/core';
* By default, snackbars display a dismiss button.
* This allows users to close them at any time, before they time out.
*
* The reasons for this default behavior is that
* there could be multiple snackbars on the screen, covering each other.
* Also, snackbars could be covering other important content on the screen,
* or have unreasonably long timeout.
* The reason for this default behavior is that snackbars could be
* covering other important content on the screen,
* or simply have a timeout longer than the time it takes
* for the user to read the message.
*
* However, you can override this default interaction design by setting the
* `dismissible` property to `false`.
* `dismissible` property to `false`, which removes the close button.
*/
@Component({
tag: 'limel-example-snackbar-dismissible',
Expand Down
5 changes: 0 additions & 5 deletions src/components/snackbar/examples/snackbar-positioning.scss

This file was deleted.

56 changes: 0 additions & 56 deletions src/components/snackbar/examples/snackbar-positioning.tsx

This file was deleted.

20 changes: 9 additions & 11 deletions src/components/snackbar/snackbar.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,29 @@ import {
describe('limel-snackbar', () => {
let page: E2EPage;
let snackbar: E2EElement;
let mdcSnackbar: E2EElement;
let popover: E2EElement;
let snackbarLabel: E2EElement;

beforeEach(() => {});

describe('show', () => {
beforeEach(async () => {
page = await createPage(`
<limel-snackbar message="This is a message"></limel-snackbar>
`);
snackbar = await page.find('limel-snackbar');
mdcSnackbar = await page.find('limel-snackbar>>>.mdc-snackbar');
popover = await page.find('limel-snackbar>>>[popover]');
await page.waitForChanges();
await snackbar.callMethod('show');
await page.waitForChanges();
});

it('opens the snackbar', () => {
expect(mdcSnackbar).toHaveClass('mdc-snackbar--open');
expect(popover).toHaveClass('open');
});

it('displays the message', async () => {
// Some extra waiting is required for the content to be populated.
await page.waitForTimeout(1000);
snackbarLabel = await mdcSnackbar.find('.mdc-snackbar__label');
snackbarLabel = await popover.find('.label');
expect(snackbarLabel).toEqualText('This is a message');
});
});
Expand Down Expand Up @@ -63,21 +63,19 @@ describe('limel-snackbar', () => {
<limel-snackbar message="This is a message" action-text="Press me!"></limel-snackbar>
`);
snackbar = await page.find('limel-snackbar');
mdcSnackbar = await page.find('limel-snackbar>>>.mdc-snackbar');
popover = await page.find('limel-snackbar>>>[popover]');
button = await page.find('limel-snackbar>>>limel-button');
await page.waitForChanges();
await snackbar.callMethod('show');
await page.waitForChanges();
});

it('opens the snackbar', () => {
expect(mdcSnackbar).toHaveClass('mdc-snackbar--open');
expect(popover).toHaveClass('open');
});

it('displays the message', async () => {
// Some extra waiting is required for the content to be populated.
await page.waitForTimeout(2000);
snackbarLabel = await mdcSnackbar.find('.mdc-snackbar__label');
snackbarLabel = await popover.find('.label');
expect(snackbarLabel).toEqualText('This is a message');
});

Expand Down
110 changes: 48 additions & 62 deletions src/components/snackbar/snackbar.scss
Original file line number Diff line number Diff line change
@@ -1,73 +1,53 @@
/**
* @prop --snackbar-top: Snackbar has `position: fixed;` and uses `--snackbar-top`, which defaults to `auto` for its `top` property. See the examples in the documentations for further info.
* @prop --snackbar-right: Snackbar has `position: fixed;` and uses `--snackbar-right`, which defaults to `0` for its `right` property. See the examples in the documentations for further info.
* @prop --snackbar-bottom: Snackbar has `position: fixed;` and uses `--snackbar-bottom`, which defaults to `0` for its `bottom` property. See the examples in the documentations for further info.
* @prop --snackbar-left: Snackbar has `position: fixed;` and uses `--snackbar-left`, which defaults to `0` for its `left` property. See the examples in the documentations for further info.
*/

* {
box-sizing: border-box;
}

.mdc-snackbar {
top: var(--snackbar-top, auto);
right: var(--snackbar-right, 0);
bottom: var(--snackbar-bottom, 0);
left: var(--snackbar-left, 0);

display: none;
position: fixed;
z-index: 8;

padding: 0.75rem;

align-items: center;
justify-content: center;
}

.mdc-snackbar--opening,
.mdc-snackbar--open,
.mdc-snackbar--closing {
display: flex;
}
aside {
background: none;
border: none;
inset: unset;
overflow: visible;
padding: 0.5rem 0.5rem 0 0.5rem;
right: 0;

.mdc-snackbar--open {
.mdc-snackbar__label,
.mdc-snackbar__actions {
visibility: visible;
}
width: var(--limel-snackbar-width, 21rem);
top: calc(
var(--snackbar-distance-to-top-edge) + env(safe-area-inset-top, 0)
);

.mdc-snackbar__surface {
transform: scale(1);
opacity: 1;
transition:
opacity var(--limel-snackbar-opacity-transition-speed, 0.2s) ease,
top var(--limel-snackbar-top-transition-speed, 0.46s)
cubic-bezier(1, 0.09, 0, 0.89),
transform var(--limel-snackbar-top-transition-speed, 0.46s) ease;
transform: translate3d(0, 0, 0);
opacity: 1;

&.is-closing {
transform: translate3d(2rem, 0, 0);
opacity: 0;
--limel-snackbar-top-transition-speed: 0.2s;
--limel-snackbar-opacity-transition-speed: 0.2s;
}
}

.mdc-snackbar__surface {
transition:
opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
transform 0.2s cubic-bezier(0, 0, 0.2, 1);
.surface {
overflow: hidden;

padding: 0.5rem;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.25rem;

min-height: 3.25rem;
min-width: unset;
max-width: 42rem;

transform: scale(0.8);
opacity: 0;
border-radius: 0.75rem;

background-color: rgb(var(--contrast-1400));
box-shadow: var(--shadow-depth-8), var(--shadow-depth-16);
}

.mdc-snackbar__label {
visibility: hidden;

.label {
color: rgb(var(--contrast-100));

-webkit-font-smoothing: antialiased;
Expand All @@ -79,9 +59,7 @@
flex-grow: 1;
}

.mdc-snackbar__actions {
visibility: hidden;

.actions {
display: flex;
flex-shrink: 0;
align-items: center;
Expand All @@ -90,7 +68,7 @@
}

.dismiss,
.mdc-snackbar__actions {
.actions {
--lime-elevated-surface-background-color: rgb(
var(--contrast-1300)
); // background color of the buttons
Expand All @@ -101,32 +79,40 @@
--icon-background-color: var(--lime-elevated-surface-background-color);
--fill-color: var(--mdc-theme-primary);
--track-color: rgb(var(--contrast-800), 0.2);
transition:
opacity 0.1s ease,
transform 0.1s ease;
position: absolute;
top: -0.9rem;
left: -0.9rem;
transform: scale(0.8);
top: -0.375rem;
left: -0.375rem;
transform: scale(0.7);

display: flex;
align-items: center;
justify-content: center;

limel-icon-button.mdc-snackbar__dismiss {
transform: scale(0.8);
margin: 0;
padding: 0;
}

svg {
position: absolute;
transform: rotate(90deg);
fill: transparent;
stroke-dasharray: 100;
stroke-linecap: round;

.mdc-snackbar--open & {
aside:popover-open & {
animation: timeout var(--snackbar-timeout) linear forwards;
}
}

.is-closing {
transform: scale(0.5);
opacity: 0;
}
}

.dismiss-button {
transform: scale(0.8);
margin: 0;
padding: 0;
}

@keyframes timeout {
Expand Down
Loading

0 comments on commit 730c14a

Please sign in to comment.