Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert EuiToast to TypeScript #2032

Merged
merged 5 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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