Skip to content

Commit

Permalink
Feat(web): Introduce Toast JavaScript plugin #DS-1115
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkudrna committed Mar 5, 2024
1 parent 2e394dc commit 19f440b
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 32 deletions.
34 changes: 17 additions & 17 deletions packages/web/src/js/Collapse.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import BaseComponent from './BaseComponent';
import {
ARIA_CONTROLS_ATTRIBUTE,
ARIA_EXPANDED_ATTRIBUTE,
CLASSNAME_OPEN,
CLASSNAME_TRANSITION,
NAME_DATA_TARGET,
NAME_DATA_TOGGLE,
ATTRIBUTE_ARIA_CONTROLS,
ATTRIBUTE_ARIA_EXPANDED,
ATTRIBUTE_DATA_TARGET,
ATTRIBUTE_DATA_TOGGLE,
CLASS_NAME_OPEN,
CLASS_NAME_TRANSITIONING,
} from './constants';
import EventHandler from './dom/EventHandler';
import SelectorEngine from './dom/SelectorEngine';
Expand Down Expand Up @@ -48,8 +48,8 @@ class Collapse extends BaseComponent {
};
this.state = {
open:
this.element.hasAttribute(ARIA_EXPANDED_ATTRIBUTE) &&
this.element.getAttribute(ARIA_EXPANDED_ATTRIBUTE) === 'true',
this.element.hasAttribute(ATTRIBUTE_ARIA_EXPANDED) &&
this.element.getAttribute(ATTRIBUTE_ARIA_EXPANDED) === 'true',
init: false,
};

Expand All @@ -74,7 +74,7 @@ class Collapse extends BaseComponent {
}

for (const item of children) {
const itemTrigger = SelectorEngine.findOne(`[${NAME_DATA_TOGGLE}="${NAME}"]`, item);
const itemTrigger = SelectorEngine.findOne(`[${ATTRIBUTE_DATA_TOGGLE}="${NAME}"]`, item);
const instance = Collapse.getInstance(itemTrigger);

if (instance?.state?.open && itemTrigger !== trigger) {
Expand Down Expand Up @@ -113,10 +113,10 @@ class Collapse extends BaseComponent {
}

updateTriggerElement(open: boolean = this.state.open) {
const triggers = SelectorEngine.findAll(`[${NAME_DATA_TARGET}="${this.meta.id}"]`);
const triggers = SelectorEngine.findAll(`[${ATTRIBUTE_DATA_TARGET}="${this.meta.id}"]`);
const updateElement = (element: Element | HTMLElement) => {
element.setAttribute(ARIA_CONTROLS_ATTRIBUTE, this.meta.id);
element.setAttribute(ARIA_EXPANDED_ATTRIBUTE, String(open));
element.setAttribute(ATTRIBUTE_ARIA_CONTROLS, this.meta.id);
element.setAttribute(ATTRIBUTE_ARIA_EXPANDED, String(open));
if (this.meta.hideOnCollapse && open) {
element.remove();
this.appendNodeToParent();
Expand All @@ -140,11 +140,11 @@ class Collapse extends BaseComponent {
}
this.adjustCollapsibleElementHeight(open);
if (this.state.init) {
this.target?.classList.add(CLASSNAME_TRANSITION);
this.target?.classList.add(CLASS_NAME_TRANSITIONING);
}
executeAfterTransition(this.target, () => {
this.target?.classList.remove(CLASSNAME_TRANSITION);
this.target?.classList.toggle(CLASSNAME_OPEN, open);
this.target?.classList.remove(CLASS_NAME_TRANSITIONING);
this.target?.classList.toggle(CLASS_NAME_OPEN, open);
if (open) {
this.target?.setAttribute('style', 'height: 100%');
} else {
Expand Down Expand Up @@ -188,7 +188,7 @@ class Collapse extends BaseComponent {
}

initEvents() {
const triggers = SelectorEngine.findAll(`[${NAME_DATA_TARGET}="${this.meta.id}"]`);
const triggers = SelectorEngine.findAll(`[${ATTRIBUTE_DATA_TARGET}="${this.meta.id}"]`);
if (triggers.length === 1) {
EventHandler.on(this.element, 'click', () => this.toggle());
} else {
Expand All @@ -197,7 +197,7 @@ class Collapse extends BaseComponent {
}

destroyEvents() {
const triggers = SelectorEngine.findAll(`[${NAME_DATA_TARGET}="${this.meta.id}"]`);
const triggers = SelectorEngine.findAll(`[${ATTRIBUTE_DATA_TARGET}="${this.meta.id}"]`);
if (triggers.length === 1) {
EventHandler.off(this.element, 'click', () => this.toggle());
} else {
Expand Down
85 changes: 85 additions & 0 deletions packages/web/src/js/Toast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import BaseComponent from './BaseComponent';
import { ATTRIBUTE_ARIA_EXPANDED, CLASS_NAME_HIDDEN, CLASS_NAME_OPEN, CLASS_NAME_TRANSITIONING } from './constants';
import { enableDismissTrigger, enableToggleTrigger, executeAfterTransition, SpiritConfig } from './utils';
import { EventHandler, SelectorEngine } from './dom';

const NAME = 'toast';
const DATA_KEY = 'tooltip';
const EVENT_KEY = `.${DATA_KEY}`;

const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;

const SELECTOR_DATA_DISMISS = '[data-spirit-dismiss]';

class Toast extends BaseComponent {
isShown: boolean;

static get NAME() {
return NAME;
}

constructor(element: SpiritElement, config?: SpiritConfig) {
super(element, config);

this.isShown =
this.element.classList.contains(CLASS_NAME_OPEN) || !this.element.classList.contains(CLASS_NAME_HIDDEN);
}

show() {
if (this.isShown) {
return;
}

const showEvent = EventHandler.trigger(this.element, Toast.eventName(EVENT_SHOW)) as Event;
if (showEvent.defaultPrevented) {
return;
}

const dismissEl = SelectorEngine.findOne(SELECTOR_DATA_DISMISS, this.element);
dismissEl?.setAttribute(ATTRIBUTE_ARIA_EXPANDED, 'true');

this.element.classList.remove(CLASS_NAME_HIDDEN);
this.element.classList.add(CLASS_NAME_OPEN);
this.element.classList.add(CLASS_NAME_TRANSITIONING);

executeAfterTransition(this.element, () => {
EventHandler.trigger(this.element, Toast.eventName(EVENT_SHOWN));
this.element.classList.remove(CLASS_NAME_TRANSITIONING);
});

this.isShown = true;
}

hide() {
if (!this.isShown) {
return;
}

const hideEvent = EventHandler.trigger(this.element, Toast.eventName(EVENT_HIDE)) as Event;
if (hideEvent.defaultPrevented) {
return;
}

const dismissEl = SelectorEngine.findOne(SELECTOR_DATA_DISMISS, this.element);
dismissEl?.setAttribute(ATTRIBUTE_ARIA_EXPANDED, 'false');

this.element.classList.remove(CLASS_NAME_OPEN);
this.element.classList.add(CLASS_NAME_HIDDEN);
this.element.classList.add(CLASS_NAME_TRANSITIONING);

executeAfterTransition(this.element, () => {
EventHandler.trigger(this.element, Toast.eventName(EVENT_HIDDEN));
this.element.remove();
});

this.isShown = false;
}
}

enableToggleTrigger(Toast, 'show', 'target');
enableDismissTrigger(Toast, 'hide', 'target');

export default Toast;
12 changes: 6 additions & 6 deletions packages/web/src/js/__tests__/Collapse.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { clearFixture, getFixture } from '../../../tests/helpers/fixture';
import EventHandler from '../dom/EventHandler';
import Collapse from '../Collapse';
import { CLASSNAME_OPEN, CLASSNAME_TRANSITION } from '../constants';
import { CLASS_NAME_OPEN, CLASS_NAME_TRANSITIONING } from '../constants';

describe('Collapse', () => {
let fixtureEl: Element;
Expand Down Expand Up @@ -75,10 +75,10 @@ describe('Collapse', () => {
await collapse.show();

expect(element.getAttribute('aria-expanded')).toBe('true');
expect(target).toHaveClass(CLASSNAME_TRANSITION);
expect(target).toHaveClass(CLASS_NAME_TRANSITIONING);

EventHandler.trigger(target, 'transitionend');
expect(target).toHaveClass(CLASSNAME_OPEN);
expect(target).toHaveClass(CLASS_NAME_OPEN);
});
});

Expand Down Expand Up @@ -197,16 +197,16 @@ describe('Collapse', () => {
const collapse0 = new Collapse(element0);

expect(target0).toHaveClass('Collapse');
expect(target1).toHaveClass(CLASSNAME_OPEN);
expect(target1).toHaveClass(CLASS_NAME_OPEN);

await collapse0.show();

expect(target0).toHaveClass(CLASSNAME_TRANSITION);
expect(target0).toHaveClass(CLASS_NAME_TRANSITIONING);

EventHandler.trigger(target0, 'transitionend');
EventHandler.trigger(target1, 'transitionend');

expect(target0).toHaveClass(CLASSNAME_OPEN);
expect(target0).toHaveClass(CLASS_NAME_OPEN);
expect(target1).toHaveClass('Collapse');
});
});
Expand Down
16 changes: 8 additions & 8 deletions packages/web/src/js/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const ARIA_EXPANDED_ATTRIBUTE = 'aria-expanded';
export const ARIA_CONTROLS_ATTRIBUTE = 'aria-controls';
export const ATTRIBUTE_ARIA_EXPANDED = 'aria-expanded';
export const ATTRIBUTE_ARIA_CONTROLS = 'aria-controls';
export const ATTRIBUTE_DATA_TARGET = 'data-spirit-target';
export const ATTRIBUTE_DATA_TOGGLE = 'data-spirit-toggle';

export const NAME_DATA_TOGGLE = 'data-spirit-toggle';
export const NAME_DATA_TARGET = 'data-spirit-target';

export const CLASSNAME_EXPANDED = 'is-expanded';
export const CLASSNAME_OPEN = 'is-open';
export const CLASSNAME_TRANSITION = 'is-transitioning';
export const CLASS_NAME_HIDDEN = 'is-hidden';
export const CLASS_NAME_OPEN = 'is-open';
export const CLASS_NAME_TRANSITIONING = 'is-transitioning';
export const CLASS_NAME_VISIBLE = 'is-visible';
1 change: 1 addition & 0 deletions packages/web/src/js/index.esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { default as Offcanvas } from './Offcanvas';
export { default as Password } from './Password';
export { default as ScrollView } from './ScrollView';
export { default as Tabs } from './Tabs';
export { default as Toast } from './Toast';
export { default as Tooltip } from './Tooltip';
export * from './constants';
export * from './dom';
Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/js/index.umd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Offcanvas from './Offcanvas';
import Password from './Password';
import ScrollView from './ScrollView';
import Tabs from './Tabs';
import Toast from './Toast';
import Tooltip from './Tooltip';
import * as constants from './constants';
import * as dom from './dom';
Expand All @@ -24,6 +25,7 @@ export default {
Password,
ScrollView,
Tabs,
Toast,
Tooltip,
constants,
dom,
Expand Down
18 changes: 18 additions & 0 deletions packages/web/src/scss/components/Toast/_ToastBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@
margin-inline-end: theme.$bar-action-margin-inline-end; // 4.
}

.ToastBar.is-transitioning {
@media (prefers-reduced-motion: no-preference) {
transition-property: visibility, opacity;
transition-duration: theme.$bar-transition-duration;
transition-timing-function: theme.$bar-transition-timing;
}
}

.ToastBar.is-hidden {
visibility: hidden;
opacity: 0;
}

.ToastBar.is-visible {
visibility: visible;
opacity: 1;
}

@include dictionaries.generate-colors(
$class-name: 'ToastBar',
$dictionary-values: theme.$color-dictionary,
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/scss/components/Toast/_theme.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@use 'sass:list';
@use '@tokens' as tokens;
@use '../../settings/dictionaries';
@use '../../settings/transitions';

$alignments-x: (
left: start,
Expand Down Expand Up @@ -29,6 +30,8 @@ $bar-content-gap: tokens.$space-500;
$bar-message-gap-x: tokens.$space-700;
$bar-message-gap-y: tokens.$space-500;
$bar-action-margin-inline-end: tokens.$space-400;
$bar-transition-duration: transitions.$duration-medium;
$bar-transition-timing: transitions.$timing-eased-out;

$color-dictionary: list.join('inverted', dictionaries.$emotion-colors);
$color-dictionary-config: (
Expand Down
42 changes: 41 additions & 1 deletion packages/web/src/scss/components/Toast/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,24 @@ <h2 class="docs-Heading">Alignment</h2>
</form>
<!-- Toast and visual keyboard demo: end -->

<!-- Toast show and hide demo: start -->
<fieldset style="border: 0;">
<legend class="mb-500">Show the toast prepared in the DOM:</legend>
<button
class="Button Button--medium Button--primary"
data-spirit-toggle="toast"
data-spirit-target="#my-hidden-dismissible-toast"
aria-expanded="false"
>
Show the hidden toast
</button>
</fieldset>
<!-- Toast show and hide demo: end -->

<div id="toast-example" class="Toast Toast--bottom Toast--center" role="log">
<div class="Toast__queue">

<div id="my-dismissible-toast" class="ToastBar ToastBar--success ToastBar--dismissible">
<div id="my-dismissible-toast" class="ToastBar ToastBar--success ToastBar--dismissible is-open">
<div class="ToastBar__content">
<svg width="20" height="20" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#check-plain" />
Expand All @@ -89,6 +103,7 @@ <h2 class="docs-Heading">Alignment</h2>
type="button"
class="Button Button--small Button--square Button--success"
data-spirit-dismiss="toast"
data-spirit-target="#my-dismissible-toast"
aria-controls="my-dismissible-toast"
aria-expanded="true"
>
Expand All @@ -113,6 +128,7 @@ <h2 class="docs-Heading">Alignment</h2>
type="button"
class="Button Button--small Button--square Button--informative"
data-spirit-dismiss="toast"
data-spirit-target="#my-other-dismissible-toast"
aria-controls="my-other-dismissible-toast"
aria-expanded="true"
>
Expand All @@ -123,6 +139,30 @@ <h2 class="docs-Heading">Alignment</h2>
</button>
</div>

<div id="my-hidden-dismissible-toast" class="ToastBar ToastBar--warning ToastBar--dismissible is-hidden">
<div class="ToastBar__content">
<svg width="20" height="20" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#warning" />
</svg>
<div class="ToastBar__message">
I was hidden and you exposed me!
</div>
</div>
<button
type="button"
class="Button Button--small Button--square Button--warning"
data-spirit-dismiss="toast"
data-spirit-target="#my-hidden-dismissible-toast"
aria-controls="my-hidden-dismissible-toast"
aria-expanded="false"
>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
<span class="accessibility-hidden">Close</span>
</button>
</div>

</div>
</div>

Expand Down

0 comments on commit 19f440b

Please sign in to comment.