Skip to content

Commit

Permalink
Convert EuiToast to TypeScript (#2032)
Browse files Browse the repository at this point in the history
Convert toast components to TypeScript.
  • Loading branch information
pugnascotia authored Jun 13, 2019
1 parent 6f896e2 commit 1bede52
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 150 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
- Converted observer utility components to TypeScript ([#2009](https://github.com/elastic/eui/pull/2009))
- Converted tool tip components to TypeScript ([#2013](https://github.com/elastic/eui/pull/2013))
- Converted `EuiCopy` to TypeScript ([#2016](https://github.com/elastic/eui/pull/2016))
- Convert badge and token components to TypeScript ([#2026](https://github.com/elastic/eui/pull/2026))
- Converted badge and token components to TypeScript ([#2026](https://github.com/elastic/eui/pull/2026))
- Added `magnet` glyph to `EuiIcon` ([2010](https://github.com/elastic/eui/pull/2010))
- Changed `logoAWS` SVG in `EuiIcon` to work better in dark mode ([#2036](https://github.com/elastic/eui/pull/2036))
- Converted toast components to TypeScript ([#2032](https://github.com/elastic/eui/pull/2032))

**Bug fixes**

Expand Down
1 change: 0 additions & 1 deletion src/components/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
/// <reference path="./steps/index.d.ts" />
/// <reference path="./table/index.d.ts" />
/// <reference path="./tabs/index.d.ts" />
/// <reference path="./toast/index.d.ts" />

declare module '@elastic/eui' {
// @ts-ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`EuiToast Props color danger is rendered 1`] = `
<EuiToast
color="danger"
title="test title"
>
<div
aria-live="polite"
Expand Down Expand Up @@ -31,7 +32,9 @@ exports[`EuiToast Props color danger is rendered 1`] = `
>
<span
className="euiToastHeader__title"
/>
>
test title
</span>
</div>
</EuiI18n>
</div>
Expand All @@ -41,6 +44,7 @@ exports[`EuiToast Props color danger is rendered 1`] = `
exports[`EuiToast Props color primary is rendered 1`] = `
<EuiToast
color="primary"
title="test title"
>
<div
aria-live="polite"
Expand Down Expand Up @@ -69,7 +73,9 @@ exports[`EuiToast Props color primary is rendered 1`] = `
>
<span
className="euiToastHeader__title"
/>
>
test title
</span>
</div>
</EuiI18n>
</div>
Expand All @@ -79,6 +85,7 @@ exports[`EuiToast Props color primary is rendered 1`] = `
exports[`EuiToast Props color success is rendered 1`] = `
<EuiToast
color="success"
title="test title"
>
<div
aria-live="polite"
Expand Down Expand Up @@ -107,7 +114,9 @@ exports[`EuiToast Props color success is rendered 1`] = `
>
<span
className="euiToastHeader__title"
/>
>
test title
</span>
</div>
</EuiI18n>
</div>
Expand All @@ -117,6 +126,7 @@ exports[`EuiToast Props color success is rendered 1`] = `
exports[`EuiToast Props color warning is rendered 1`] = `
<EuiToast
color="warning"
title="test title"
>
<div
aria-live="polite"
Expand Down Expand Up @@ -145,7 +155,9 @@ exports[`EuiToast Props color warning is rendered 1`] = `
>
<span
className="euiToastHeader__title"
/>
>
test title
</span>
</div>
</EuiI18n>
</div>
Expand All @@ -155,6 +167,7 @@ exports[`EuiToast Props color warning is rendered 1`] = `
exports[`EuiToast Props iconType is rendered 1`] = `
<EuiToast
iconType="user"
title="test title"
>
<div
aria-live="polite"
Expand Down Expand Up @@ -207,7 +220,9 @@ exports[`EuiToast Props iconType is rendered 1`] = `
</EuiIcon>
<span
className="euiToastHeader__title"
/>
>
test title
</span>
</div>
</EuiI18n>
</div>
Expand Down Expand Up @@ -273,7 +288,9 @@ exports[`EuiToast is rendered 1`] = `
>
<span
class="euiToastHeader__title"
/>
>
test title
</span>
</div>
<div
class="euiText euiText--small euiToastBody"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';
import { render, mount } from 'enzyme';
import sinon from 'sinon';
import { requiredProps, findTestSubject } from '../../test';

import { EuiGlobalToastList, TOAST_FADE_OUT_MS } from './global_toast_list';
import {
EuiGlobalToastList,
Toast,
TOAST_FADE_OUT_MS,
} from './global_toast_list';

describe('EuiGlobalToastList', () => {
test('is rendered', () => {
Expand All @@ -21,7 +24,7 @@ describe('EuiGlobalToastList', () => {
describe('props', () => {
describe('toasts', () => {
test('is rendered', () => {
const toasts = [
const toasts: Toast[] = [
{
title: 'A',
text: 'a',
Expand Down Expand Up @@ -54,7 +57,7 @@ describe('EuiGlobalToastList', () => {

describe('dismissToast', () => {
test('is called when a toast is clicked', done => {
const dismissToastSpy = sinon.spy();
const dismissToastSpy = jest.fn();
const component = mount(
<EuiGlobalToastList
toasts={[
Expand All @@ -74,14 +77,14 @@ describe('EuiGlobalToastList', () => {

// The callback is invoked once the toast fades from view.
setTimeout(() => {
expect(dismissToastSpy.called).toBe(true);
expect(dismissToastSpy).toBeCalled();
done();
}, TOAST_FADE_OUT_MS + 1);
});

test('is called when the toast lifetime elapses', done => {
const TOAST_LIFE_TIME_MS = 5;
const dismissToastSpy = sinon.spy();
const dismissToastSpy = jest.fn();
mount(
<EuiGlobalToastList
toasts={[
Expand All @@ -97,15 +100,15 @@ describe('EuiGlobalToastList', () => {

// The callback is invoked once the toast fades from view.
setTimeout(() => {
expect(dismissToastSpy.called).toBe(true);
expect(dismissToastSpy).toBeCalled();
done();
}, TOAST_LIFE_TIME_MS + TOAST_FADE_OUT_MS + 10);
});

test('toastLifeTimeMs is overrideable by individidual toasts', done => {
const TOAST_LIFE_TIME_MS = 10;
const TOAST_LIFE_TIME_MS_OVERRIDE = 100;
const dismissToastSpy = sinon.spy();
const dismissToastSpy = jest.fn();
mount(
<EuiGlobalToastList
toasts={[
Expand All @@ -122,10 +125,10 @@ describe('EuiGlobalToastList', () => {

// The callback is invoked once the toast fades from view.
setTimeout(() => {
expect(dismissToastSpy.called).toBe(false);
expect(dismissToastSpy).not.toBeCalled();
}, TOAST_LIFE_TIME_MS + TOAST_FADE_OUT_MS + 10);
setTimeout(() => {
expect(dismissToastSpy.called).toBe(true);
expect(dismissToastSpy).toBeCalled();
done();
}, TOAST_LIFE_TIME_MS_OVERRIDE + TOAST_FADE_OUT_MS + 10);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React, { Component, ReactChild } from 'react';
import classNames from 'classnames';

import { CommonProps } from '../common';
import { Timer } from '../../services/time';
import { IconPropType } from '../icon';
import { EuiGlobalToastListItem } from './global_toast_list_item';
import { EuiToast } from './toast';
import { EuiToast, EuiToastProps } from './toast';

export const TOAST_FADE_OUT_MS = 250;

export class EuiGlobalToastList extends Component {
constructor(props) {
super(props);
export interface Toast extends EuiToastProps {
id: string;
text?: ReactChild;
toastLifeTimeMs?: number;
}

this.state = {
toastIdToDismissedMap: {},
};
export interface EuiGlobalToastListProps extends CommonProps {
toasts: Toast[];
dismissToast: (this: EuiGlobalToastList, toast: Toast) => void;
toastLifeTimeMs: number;
}

this.dismissTimeoutIds = [];
this.toastIdToTimerMap = {};
interface State {
toastIdToDismissedMap: {
[toastId: string]: boolean;
};
}

this.isScrollingToBottom = false;
this.isScrolledToBottom = true;
export class EuiGlobalToastList extends Component<
EuiGlobalToastListProps,
State
> {
state: State = {
toastIdToDismissedMap: {},
};

// See [Return Value](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#Return_value)
// for information on initial value of 0
this.isScrollingAnimationFrame = 0;
this.startScrollingAnimationFrame = 0;
}
dismissTimeoutIds: number[] = [];
toastIdToTimerMap: { [toastId: string]: Timer } = {};

static propTypes = {
className: PropTypes.string,
toasts: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
title: PropTypes.node,
text: PropTypes.node,
color: PropTypes.string,
iconType: IconPropType,
toastLifeTimeMs: PropTypes.number,
}).isRequired
),
dismissToast: PropTypes.func.isRequired,
toastLifeTimeMs: PropTypes.number.isRequired,
};
isScrollingToBottom = false;
isScrolledToBottom = true;
isUserInteracting = false;

// See [Return Value](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#Return_value)
// for information on initial value of 0
isScrollingAnimationFrame = 0;
startScrollingAnimationFrame = 0;

listElement: Element | null = null;

static defaultProps = {
toasts: [],
Expand All @@ -56,7 +58,9 @@ export class EuiGlobalToastList extends Component {
const scrollToBottom = () => {
// Although we cancel the requestAnimationFrame in componentWillUnmount,
// it's possible for this.listElement to become null in the meantime
if (!this.listElement) return;
if (!this.listElement) {
return;
}

const position = this.listElement.scrollTop;
const destination =
Expand Down Expand Up @@ -110,9 +114,11 @@ export class EuiGlobalToastList extends Component {
};

onScroll = () => {
this.isScrolledToBottom =
this.listElement.scrollHeight - this.listElement.scrollTop ===
this.listElement.clientHeight;
if (this.listElement) {
this.isScrolledToBottom =
this.listElement.scrollHeight - this.listElement.scrollTop ===
this.listElement.clientHeight;
}
};

scheduleAllToastsForDismissal = () => {
Expand All @@ -123,7 +129,7 @@ export class EuiGlobalToastList extends Component {
});
};

scheduleToastForDismissal = toast => {
scheduleToastForDismissal = (toast: Toast) => {
// Start fading the toast out once its lifetime elapses.
this.toastIdToTimerMap[toast.id] = new Timer(
this.dismissToast.bind(this, toast),
Expand All @@ -133,16 +139,16 @@ export class EuiGlobalToastList extends Component {
);
};

dismissToast = toast => {
dismissToast = (toast: Toast) => {
// Remove the toast after it's done fading out.
this.dismissTimeoutIds.push(
setTimeout(() => {
window.setTimeout(() => {
// Because this is wrapped in a setTimeout, and because React does not guarantee when
// state updates happen, it is possible to double-dismiss a toast
// including by double-clicking the "x" button on the toast
// so, first check to make sure we haven't already dismissed this toast
if (this.toastIdToTimerMap.hasOwnProperty(toast.id)) {
this.props.dismissToast(toast);
this.props.dismissToast.apply(this, [toast]);
this.toastIdToTimerMap[toast.id].clear();
delete this.toastIdToTimerMap[toast.id];

Expand Down Expand Up @@ -173,13 +179,15 @@ export class EuiGlobalToastList extends Component {
};

componentDidMount() {
this.listElement.addEventListener('scroll', this.onScroll);
this.listElement.addEventListener('mouseenter', this.onMouseEnter);
this.listElement.addEventListener('mouseleave', this.onMouseLeave);
if (this.listElement) {
this.listElement.addEventListener('scroll', this.onScroll);
this.listElement.addEventListener('mouseenter', this.onMouseEnter);
this.listElement.addEventListener('mouseleave', this.onMouseLeave);
}
this.scheduleAllToastsForDismissal();
}

componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: EuiGlobalToastListProps) {
this.scheduleAllToastsForDismissal();

if (!this.isUserInteracting) {
Expand All @@ -200,9 +208,11 @@ export class EuiGlobalToastList extends Component {
if (this.startScrollingAnimationFrame !== 0) {
window.cancelAnimationFrame(this.startScrollingAnimationFrame);
}
this.listElement.removeEventListener('scroll', this.onScroll);
this.listElement.removeEventListener('mouseenter', this.onMouseEnter);
this.listElement.removeEventListener('mouseleave', this.onMouseLeave);
if (this.listElement) {
this.listElement.removeEventListener('scroll', this.onScroll);
this.listElement.removeEventListener('mouseenter', this.onMouseEnter);
this.listElement.removeEventListener('mouseleave', this.onMouseLeave);
}
this.dismissTimeoutIds.forEach(clearTimeout);
for (const toastId in this.toastIdToTimerMap) {
if (this.toastIdToTimerMap.hasOwnProperty(toastId)) {
Expand Down
Loading

0 comments on commit 1bede52

Please sign in to comment.