├── .babelrc
├── .eslintrc
├── .gitignore
├── .nvmrc
├── .pnp.cjs
├── .yarnrc.yml
├── LICENSE
├── README.md
├── build
└── index.js
├── jest.config.js
├── package.json
├── src
├── __snapshots__
│ └── index.spec.jsx.snap
├── index.jsx
└── index.spec.jsx
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true,
5 | "jest": true
6 | },
7 | "plugins": ["testing-library"]
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /coverage
3 |
4 | .DS_Store
5 | .vscode
6 | .idea
7 | .yarn
8 |
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v14.7.0
2 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | plugins:
2 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
3 | spec: "@yarnpkg/plugin-interactive-tools"
4 |
5 | yarnPath: .yarn/releases/yarn-3.0.1.cjs
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Daniel Sneijers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Flash Message ✨
2 |
3 | Simple component that unmounts a component after a given delay. It adds no styling or animations, you can use other components like [react-transition-group](https://github.com/reactjs/react-transition-group) for that.
4 |
5 | ## Basic Example
6 |
7 | ```jsx
8 | import React from 'react';
9 | import { render } from 'react-dom';
10 | import FlashMessage from 'react-flash-message'
11 |
12 | const Message = () => (
13 |
14 | I will disapper in 5 seconds!
15 |
16 | )
17 |
18 | render(Message, document.body);
19 | ```
20 |
21 | ## API
22 |
23 | ### Component
24 |
25 | ```jsx
26 | import FlashMessage from 'react-flash-message';
27 |
28 | // inside render
29 |
30 | Message
31 | ;
32 | ```
33 |
34 | ### Props
35 |
36 | | Prop | Type | Default | Description |
37 | | ---------------- | ------ | ------- | ---------------------------------------------------------------- |
38 | | `duration` | number | 5000 | Number of milliseconds the component will show |
39 | | `persistOnHover` | bool | true | Will not remove the component when the user hovers on it if true |
40 |
41 | ## Issues
42 |
43 | Feel free to contribute. Submit a Pull Request or open an issue for further discussion.
44 |
45 | ## License
46 |
47 | MIT
48 |
--------------------------------------------------------------------------------
/build/index.js:
--------------------------------------------------------------------------------
1 | (()=>{var e={159:(e,t,r)=>{"use strict";var n=r(929);function o(){}function i(){}i.resetWarningCache=o,e.exports=function(){function e(e,t,r,o,i,u){if(u!==n){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var r={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:o};return r.PropTypes=r,r}},15:(e,t,r)=>{e.exports=r(159)()},929:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};(()=>{"use strict";r.r(n),r.d(n,{default:()=>l});const e=require("react");var t=r.n(e),o=r(15);function i(e){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function u(e,t){for(var r=0;r/node_modules/',
6 | ],
7 | testEnvironment: "jsdom",
8 | };
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-flash-message",
3 | "version": "1.0.8",
4 | "description": "Simple component that unmounts a component after a given delay.",
5 | "main": "build/index.js",
6 | "author": "Daniel Sneijers (https://github.com/danielsneijers)",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/danielsneijers/react-flash-message.git"
10 | },
11 | "license": "MIT",
12 | "scripts": {
13 | "start": "webpack --mode development --watch",
14 | "build": "webpack --mode production",
15 | "test": "jest",
16 | "lint": "eslint --ext .js --ext .jsx src --fix",
17 | "prettier": "prettier --trailing-comma es5 --single-quote --write 'src/**/*.{js,jsx}'",
18 | "precommit": "lint-staged"
19 | },
20 | "lint-staged": {
21 | "linters": {
22 | "*.{js,jsx}": [
23 | "yarn prettier",
24 | "yarn lint",
25 | "git add"
26 | ]
27 | }
28 | },
29 | "dependencies": {
30 | "prop-types": "^15.7.2"
31 | },
32 | "devDependencies": {
33 | "@babel/core": "^7.15.0",
34 | "@babel/plugin-transform-react-jsx": "^7.14.9",
35 | "@babel/preset-env": "^7.15.0",
36 | "@babel/preset-react": "^7.14.5",
37 | "@testing-library/jest-dom": "^5.14.1",
38 | "@testing-library/react": "^12.0.0",
39 | "babel-jest": "^27.0.6",
40 | "babel-loader": "^8.2.2",
41 | "eslint": "^7.32.0",
42 | "eslint-config-airbnb": "^18.2.1",
43 | "eslint-plugin-import": "^2.24.0",
44 | "eslint-plugin-jsx-a11y": "^6.4.1",
45 | "eslint-plugin-react": "^7.24.0",
46 | "eslint-plugin-testing-library": "^4.11.0",
47 | "extract-text-webpack-plugin": "^3.0.2",
48 | "html-webpack-plugin": "^5.3.2",
49 | "husky": "^7.0.1",
50 | "jest": "^27.0.6",
51 | "lint-staged": "^11.1.2",
52 | "prettier": "^2.3.2",
53 | "react": "^17.0.2",
54 | "react-dom": "^17.0.2",
55 | "regenerator-runtime": "^0.13.9",
56 | "webpack": "^5.50.0",
57 | "webpack-cli": "^4.8.0",
58 | "webpack-dev-server": "^4.0.0"
59 | },
60 | "peerDependencies": {
61 | "react": ">=15 <= 17",
62 | "react-dom": ">=15 <= 17"
63 | },
64 | "packageManager": "yarn@3.0.1"
65 | }
66 |
--------------------------------------------------------------------------------
/src/__snapshots__/index.spec.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` mounts and unmounts children 1`] = `
4 | Array [
5 | Array [
6 | "render",
7 | ],
8 | Array [
9 | "componentDidMount",
10 | ],
11 | ]
12 | `;
13 |
14 | exports[` mounts and unmounts children 2`] = `
15 | Array [
16 | Array [
17 | "render",
18 | ],
19 | Array [
20 | "componentDidMount",
21 | ],
22 | Array [
23 | "componentWillUnmount",
24 | ],
25 | ]
26 | `;
27 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { node, number, bool } from 'prop-types';
3 |
4 | class FlashMessage extends Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | this.state = { isVisible: true };
9 |
10 | this.hide = this.hide.bind(this);
11 | this.resumeTimer = this.resumeTimer.bind(this);
12 | this.pauseTimer = this.pauseTimer.bind(this);
13 | }
14 |
15 | componentDidMount() {
16 | const { duration } = this.props;
17 | this.remaining = duration;
18 | this.resumeTimer();
19 | }
20 |
21 | componentWillUnmount() {
22 | clearTimeout(this.timer);
23 | }
24 |
25 | hide() {
26 | this.setState({ isVisible: false });
27 | }
28 |
29 | resumeTimer() {
30 | window.clearTimeout(this.timer);
31 |
32 | this.start = new Date();
33 | this.timer = setTimeout(this.hide, this.remaining);
34 | }
35 |
36 | pauseTimer() {
37 | const { persistOnHover } = this.props;
38 | if (persistOnHover) {
39 | clearTimeout(this.timer);
40 |
41 | this.remaining -= new Date() - this.start;
42 | }
43 | }
44 |
45 | render() {
46 | const { isVisible } = this.state;
47 | const { children } = this.props;
48 |
49 | return isVisible ? (
50 |
51 | {children}
52 |
53 | ) : null;
54 | }
55 | }
56 |
57 | FlashMessage.defaultProps = {
58 | duration: 5000,
59 | children: null,
60 | persistOnHover: true,
61 | };
62 |
63 | FlashMessage.propTypes = {
64 | children: node,
65 | duration: number,
66 | persistOnHover: bool,
67 | };
68 |
69 | export default FlashMessage;
70 |
--------------------------------------------------------------------------------
/src/index.spec.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-classes-per-file, react/prop-types, react/no-unescaped-entities */
2 | import '@testing-library/jest-dom';
3 | import React, { PureComponent } from 'react';
4 | import { render, fireEvent } from '@testing-library/react';
5 | import FlashMessage from './index';
6 |
7 | class TestComponent extends PureComponent {
8 | componentDidMount() {
9 | const { track } = this.props;
10 | track('componentDidMount');
11 | }
12 |
13 | componentWillUnmount() {
14 | const { track } = this.props;
15 | track('componentWillUnmount');
16 | }
17 |
18 | render() {
19 | const { track } = this.props;
20 | track('render');
21 | return Hej, I'm a message!
;
22 | }
23 | }
24 |
25 | describe('', () => {
26 | beforeAll(() => {
27 | const constantDate = new Date('2018-02-13T04:41:20');
28 |
29 | /* eslint-disable-next-line no-global-assign */
30 | Date = class extends Date {
31 | constructor() {
32 | return constantDate;
33 | }
34 | };
35 | });
36 |
37 | beforeEach(() => {
38 | jest.useFakeTimers();
39 | });
40 |
41 | it('renders and sets a timer', () => {
42 | const message = 'test';
43 | const { queryByText } = render(
44 |
45 | {message}
46 | ,
47 | );
48 |
49 | expect(queryByText(message)).toBeInTheDocument();
50 | });
51 |
52 | it('disappears after x seconds', () => {
53 | const message = 'test';
54 | const { queryByText } = render(
55 |
56 | {message}
57 | ,
58 | );
59 |
60 | expect(queryByText(message)).toBeInTheDocument();
61 |
62 | jest.advanceTimersByTime(2000);
63 |
64 | expect(queryByText(message)).not.toBeInTheDocument();
65 | });
66 |
67 | /* Disabled because clearTimeout isn't mocked properly in new jest
68 | it('removes existing timer before umounting', () => {
69 | const { unmount } = render(
70 |
71 |
72 | ,
73 | );
74 |
75 | expect(clearTimeout).toHaveBeenCalledTimes(1);
76 |
77 | unmount();
78 |
79 | expect(clearTimeout).toHaveBeenCalledTimes(2);
80 | })*/
81 |
82 | it('mounts and unmounts children', () => {
83 | const tracker = jest.fn();
84 | render(
85 |
86 |
87 | ,
88 | );
89 |
90 | expect(tracker.mock.calls).toMatchSnapshot();
91 |
92 | jest.advanceTimersByTime(5000);
93 |
94 | expect(tracker.mock.calls).toMatchSnapshot();
95 | });
96 |
97 | it("doesn't unmount on hover when flag is set", () => {
98 | const message = 'test';
99 | const { queryByText } = render({message});
100 |
101 | fireEvent.mouseEnter(queryByText(message));
102 | jest.advanceTimersByTime(2000);
103 |
104 | expect(queryByText(message)).toBeInTheDocument();
105 |
106 | fireEvent.mouseLeave(queryByText(message));
107 | jest.advanceTimersByTime(1000);
108 |
109 | expect(queryByText(message)).not.toBeInTheDocument();
110 | });
111 |
112 | it('does unmount on hover when flag is set', () => {
113 | const message = 'test';
114 | const { queryByText } = render(
115 |
116 | {message}
117 | ,
118 | );
119 |
120 | fireEvent.mouseEnter(queryByText(message));
121 | jest.advanceTimersByTime(2000);
122 |
123 | expect(queryByText(message)).not.toBeInTheDocument();
124 | });
125 | });
126 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path');
2 | const webpack = require('webpack');
3 | // const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | const baseConfig = {
6 | entry: './src/index.jsx',
7 | // devtool: 'inline-source-map',
8 | // devServer: {
9 | // hot: true,
10 | // contentBase: 'build',
11 | // publicPath: '/',
12 | // stats: 'errors-only',
13 | // },
14 | output: {
15 | path: resolve('./build'),
16 | filename: 'index.js',
17 | libraryTarget: 'commonjs2',
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.jsx?$/,
23 | loader: 'babel-loader',
24 | exclude: /node-modules/,
25 | },
26 | ],
27 | },
28 | // plugins: [
29 | // new HtmlWebpackPlugin({
30 | // title: 'Title',
31 | // template: './src/index.html',
32 | // minify: { useShortDoctype: true },
33 | // hash: false,
34 | // }),
35 | // ],
36 | externals: {
37 | react: 'react', // this line is just to use the React dependency of our parent-testing-project instead of using our own React.
38 | },
39 | resolve: {
40 | extensions: ['.js', '.jsx'],
41 | modules: ['node_modules'],
42 | },
43 | };
44 |
45 | module.exports = baseConfig;
46 |
--------------------------------------------------------------------------------