From 2ca4bb33b9c510f5978c69974c5ce68e32f656ca Mon Sep 17 00:00:00 2001 From: Kyle Alwyn Date: Sat, 10 Nov 2018 12:08:02 -0800 Subject: [PATCH] feat: Add Toast component --- package-lock.json | 88 ++++++++++++++----- package.json | 13 +-- src/Dropdown/Dropdown.js | 26 ++++-- src/Dropdown/Dropdown.mdx | 4 +- src/Modal/Modal.js | 2 +- src/Toast/Toast.mdx | 61 +++++++++++++ src/Toast/ToastContainer.js | 170 ++++++++++++++++++++++++++++++++++++ src/Toast/config.js | 81 +++++++++++++++++ src/Toast/index.js | 2 + src/Toast/toast.js | 30 +++++++ src/theme.js | 2 + src/utils.js | 2 +- 12 files changed, 439 insertions(+), 42 deletions(-) create mode 100644 src/Toast/Toast.mdx create mode 100644 src/Toast/ToastContainer.js create mode 100644 src/Toast/config.js create mode 100644 src/Toast/index.js create mode 100644 src/Toast/toast.js diff --git a/package-lock.json b/package-lock.json index 0f67a12..fb6ab7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -960,6 +961,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz", "integrity": "sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==", + "dev": true, "requires": { "esutils": "^2.0.2", "lodash": "^4.17.10", @@ -998,6 +1000,7 @@ "version": "0.6.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.6.8.tgz", "integrity": "sha512-IMSL7ekYhmFlILXcouA6ket3vV7u9BqStlXzbKOF9HBtpUPMMlHU+bBxrLOa2NvleVwNIxeq/zL8LafLbeUXcA==", + "dev": true, "requires": { "@emotion/memoize": "^0.6.6" } @@ -1005,7 +1008,8 @@ "@emotion/memoize": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz", - "integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==" + "integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==", + "dev": true }, "@emotion/serialize": { "version": "0.9.1", @@ -2209,7 +2213,8 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true }, "asn1": { "version": "0.2.4", @@ -2647,6 +2652,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.8.0.tgz", "integrity": "sha512-PcrdbXFO/9Plo9JURIj8G0Dsz+Ct8r+NvjoLh6qPt8Y/3EIAj1gHGW1ocPY1IkQbXZLBEZZSRBAxJem1KFdBXg==", + "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", "lodash": "^4.17.10" @@ -4955,7 +4961,8 @@ "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", + "dev": true }, "css-select": { "version": "2.0.0", @@ -4979,6 +4986,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.2.2.tgz", "integrity": "sha512-w99Fzop1FO8XKm0VpbQp3y5mnTnaS+rtCvS+ylSEOK76YXO5zoHQx/QMB1N54Cp+Ya9jB9922EHrh14ld4xmmw==", + "dev": true, "requires": { "css-color-keywords": "^1.0.0", "fbjs": "^0.8.5", @@ -5981,6 +5989,7 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, "requires": { "iconv-lite": "~0.4.13" } @@ -6714,7 +6723,8 @@ "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true }, "event-stream": { "version": "3.3.6", @@ -7295,6 +7305,7 @@ "version": "0.8.17", "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "dev": true, "requires": { "core-js": "^1.0.0", "isomorphic-fetch": "^2.1.1", @@ -7308,7 +7319,8 @@ "core-js": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "dev": true } } }, @@ -9280,6 +9292,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -9777,7 +9790,8 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-string": { "version": "1.0.4", @@ -9885,6 +9899,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "dev": true, "requires": { "node-fetch": "^1.0.1", "whatwg-fetch": ">=0.10.0" @@ -11709,9 +11724,10 @@ } }, "memoize-one": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.2.tgz", - "integrity": "sha512-ucx2DmXTeZTsS4GPPUZCbULAN7kdPT1G+H49Y34JjbQ5ESc6OGhVxKvb1iKhr9v19ZB9OtnHwNnhUnNR/7Wteg==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", + "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==", + "dev": true }, "memory-fs": { "version": "0.4.1", @@ -11950,6 +11966,11 @@ "through2": "^2.0.0" } }, + "mitt": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.1.3.tgz", + "integrity": "sha512-mUDCnVNsAi+eD6qA0HkRkwYczbLHJ49z17BGe2PYRhZL4wpZUFZGJHU7/5tmvohoma+Hdn0Vh/oJTiPEmgSruA==" + }, "mixin-deep": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", @@ -12215,6 +12236,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, "requires": { "encoding": "^0.1.11", "is-stream": "^1.0.1" @@ -13215,7 +13237,8 @@ "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true }, "prebuild-install": { "version": "5.2.0", @@ -13372,6 +13395,7 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, "requires": { "asap": "~2.0.3" } @@ -13852,7 +13876,8 @@ "react-is": { "version": "16.5.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.5.2.tgz", - "integrity": "sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==" + "integrity": "sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==", + "dev": true }, "react-lifecycles-compat": { "version": "3.0.4", @@ -15120,7 +15145,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sane": { "version": "2.5.2", @@ -15572,7 +15598,8 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true }, "setprototypeof": { "version": "1.1.0", @@ -16562,18 +16589,28 @@ "dev": true }, "styled-components": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.0.0.tgz", - "integrity": "sha512-+SXoKjaXmApQHjQXNY5pxuRpnabR7vnL6J59Gtcj118ll8gsg9MM8gkjWu6Ahc0NB9kLjh3O9Ho2iun6uLunVA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.0.3.tgz", + "integrity": "sha512-oEZovK4xMGAMhOA9h74dCYJsp3IwUFhEvtYe4gwTy0cBZ3a17YMxBfM2oXsEoED9f+HCM5UQZW2h297n4u8hUw==", + "dev": true, "requires": { "@emotion/is-prop-valid": "^0.6.8", "babel-plugin-styled-components": ">= 1", "css-to-react-native": "^2.2.2", "memoize-one": "^4.0.0", "prop-types": "^15.5.4", - "react-is": "^16.3.1", + "react-is": "^16.6.0", "stylis": "^3.5.0", - "stylis-rule-sheet": "^0.0.10" + "stylis-rule-sheet": "^0.0.10", + "supports-color": "^5.5.0" + }, + "dependencies": { + "react-is": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.1.tgz", + "integrity": "sha512-wOKsGtvTMYs7WAscmwwdM8sfRRvE17Ym30zFj3n37Qx5tHRfhenPKEPILHaHob6WoLFADmQm1ZNrE5xMCM6sCw==", + "dev": true + } } }, "styled-system": { @@ -16603,12 +16640,14 @@ "stylis": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.3.tgz", - "integrity": "sha512-TxU0aAscJghF9I3V9q601xcK3Uw1JbXvpsBGj/HULqexKOKlOEzzlIpLFRbKkCK990ccuxfXUqmPbIIo7Fq/cQ==" + "integrity": "sha512-TxU0aAscJghF9I3V9q601xcK3Uw1JbXvpsBGj/HULqexKOKlOEzzlIpLFRbKkCK990ccuxfXUqmPbIIo7Fq/cQ==", + "dev": true }, "stylis-rule-sheet": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", - "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", + "dev": true }, "supports-color": { "version": "5.5.0", @@ -17036,7 +17075,8 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true }, "to-object-path": { "version": "0.3.0", @@ -17264,7 +17304,8 @@ "ua-parser-js": { "version": "0.7.18", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", - "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" + "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==", + "dev": true }, "uglify-js": { "version": "3.4.9", @@ -18758,7 +18799,8 @@ "whatwg-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==", + "dev": true }, "whatwg-mimetype": { "version": "2.2.0", diff --git a/package.json b/package.json index 881ec91..129a86b 100644 --- a/package.json +++ b/package.json @@ -29,18 +29,19 @@ }, "dependencies": { "lodash": "^4.17.11", - "popper.js": "^1.14.4", - "react-portal": "^4.1.5", - "styled-system": "^3.1.11", + "mitt": "^1.1.3", "polished": "^2.0.0", + "popper.js": "^1.14.4", "prop-types": "^15.6.1", "react-animations": "^1.0.0", + "react-portal": "^4.1.5", "react-transition-group": "^2.5.0", - "styled-components": "^4.0.0" + "styled-system": "^3.1.11" }, "peerDependencies": { "react": "^16.5.0", - "react-dom": "^16.5.0" + "react-dom": "^16.5.0", + "styled-components": "^4.0.0" }, "devDependencies": { "@babel/core": "^7.1.2", @@ -72,7 +73,7 @@ "rollup-plugin-commonjs": "^9.2.0", "rollup-plugin-filesize": "^5.0.1", "standard-version": "^4.4.0", - "styled-components": "^4.0.0" + "styled-components": "^4.0.3" }, "resolutions": { "babel-core": "^7.0.0-bridge.0" diff --git a/src/Dropdown/Dropdown.js b/src/Dropdown/Dropdown.js index 84e0179..90a5902 100644 --- a/src/Dropdown/Dropdown.js +++ b/src/Dropdown/Dropdown.js @@ -4,7 +4,7 @@ import { css } from 'styled-components'; import Popper from 'popper.js'; import Box from '../Box'; import Portal from '../Portal'; -import { createComponent } from '../utils'; +import { createComponent, themeGet } from '../utils'; const DropdownTrigger = createComponent({ name: 'DropdownTrigger', @@ -162,9 +162,15 @@ export default class Dropdown extends React.Component { const DropdownHeader = createComponent({ name: 'DropdownHeader', + style: css` + padding: 0.75rem 1rem 0; + `, +}); + +const DropdownHeaderInner = createComponent({ + name: 'DropdownHeaderInner', style: css` padding: 0 0 0.25rem; - margin-bottom: 0.5rem; border-bottom: 2px solid ${p => p.theme.colors.grayLight}; `, }); @@ -182,8 +188,10 @@ Dropdown.Title = createComponent({ Dropdown.Header = ({ title, children }) => ( - {title && {title}} - {children} + + {title && {title}} + {children} + ); @@ -191,7 +199,7 @@ Dropdown.Body = createComponent({ name: 'DropdownBody', as: Box, style: css` - padding: 12px; + padding: 1rem; `, }); @@ -216,8 +224,8 @@ Dropdown.Item = createComponent({ text-decoration: none; color: inherit; cursor: pointer; - margin: 0 -12px; - padding: 4px 12px; + margin: 0 -1rem; + padding: 0.25rem 1rem; transition: 125ms background; & + ${Dropdown.SectionTitle} { @@ -239,7 +247,7 @@ Dropdown.Footer = createComponent({ }, style: ({ theme }) => css` background: ${theme.colors.grayLightest}; - padding: 8px 12px; - border-radius: 0 0 4px 4px; + padding: 0.75rem 1rem; + border-radius: 0 0 ${themeGet('radius')}px ${themeGet('radius')}px; `, }); diff --git a/src/Dropdown/Dropdown.mdx b/src/Dropdown/Dropdown.mdx index 9a7a0d5..f4a6198 100644 --- a/src/Dropdown/Dropdown.mdx +++ b/src/Dropdown/Dropdown.mdx @@ -22,9 +22,9 @@ Easily display contextual overlays using custom trigger elements. Dropdown's pos > {({ close }) => ( <> - - + + Section One Item One Item Two diff --git a/src/Modal/Modal.js b/src/Modal/Modal.js index be80eb2..2104737 100644 --- a/src/Modal/Modal.js +++ b/src/Modal/Modal.js @@ -15,7 +15,7 @@ const Backdrop = createComponent({ right: 0; bottom: 0; z-index: 1000; - padding: 12px; + padding: 1rem; display: flex; align-items: center; position: fixed; diff --git a/src/Toast/Toast.mdx b/src/Toast/Toast.mdx new file mode 100644 index 0000000..1239eed --- /dev/null +++ b/src/Toast/Toast.mdx @@ -0,0 +1,61 @@ +--- +menu: Core +name: Toast +--- + +import { Playground, PropsTable } from 'docz' +import toast from './toast' +import ToastContainer from './ToastContainer' +import Button from '../Button'; + +# Toast + +## Props + + + +## Positioning + +Toast positions will default to `top-center`. To change the positioning, you can either pass the `position` prop to the `` to be used as the default. You can also pass the position to each individual toast you're rendering, which will override the default.rendering. + +## Examples + + + <> +

Variants

+ + + + + + + + + + +

Positioning

+ + + + + + + + +
diff --git a/src/Toast/ToastContainer.js b/src/Toast/ToastContainer.js new file mode 100644 index 0000000..70be98f --- /dev/null +++ b/src/Toast/ToastContainer.js @@ -0,0 +1,170 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Transition, TransitionGroup } from 'react-transition-group'; +import * as animations from 'react-animations'; +import { css, keyframes } from 'styled-components'; +import Portal from '../Portal'; +import Flex from '../Flex'; +import Box from '../Box'; +import Icon from '../Icon'; +import { emitter } from './toast'; +import { createComponent, themeGet } from '../utils'; +import { Types, Events, Positions, PositionConfigs } from './config'; + +const VariantToColorMap = { + success: 'green', + error: 'red', + warn: 'orange', + info: 'blue', +}; + +const getTransitionStyle = (state, position, duration) => { + const { animationIn, animationOut } = PositionConfigs[position]; + + switch (state) { + case 'entering': + return css` + animation: ${duration}ms ${keyframes`${animations[animationIn]}`}; + `; + + case 'exiting': + return css` + animation: ${duration}ms ${keyframes`${animations[animationOut]}`}; + `; + + default: + return css``; + } +}; + +const ToastPortal = createComponent({ + name: 'ToastPortal', + style: ({ position }) => PositionConfigs[position].wrapperStyle, +}); + +const Toast = createComponent({ + name: 'Toast', + style: ({ state, type, position, animationDuration, theme }) => css` + padding: 0.75rem 1rem; + min-width: 250px; + max-width: 400px; + cursor: pointer; + color: white; + font-weight: 600; + border-radius: ${themeGet('radius')}px; + box-shadow: ${themeGet('shadow')}; + background: ${theme.colors[VariantToColorMap[type]]}; + transition: 175ms; + + ${getTransitionStyle(state, position, animationDuration)}; + + & + & { + margin-top: 0.5rem; + } + + &:hover { + box-shadow: ${themeGet('shadowHover')}; + } + `, +}); + +export default class ToastContainer extends Component { + counter = 0; + state = { + toasts: [], + }; + + static propTypes = { + type: PropTypes.string, + position: PropTypes.string, + timeout: PropTypes.number, + animationDuration: PropTypes.number, + autoClose: PropTypes.bool, + closeOnClick: PropTypes.bool, + showClose: PropTypes.bool, + }; + + static defaultProps = { + type: Types.INFO, + position: Positions.TOP_CENTER, + timeout: 5000, + animationDuration: 250, + autoClose: true, + closeOnClick: true, + showClose: true, + }; + + componentDidMount() { + emitter.on(Events.ADD, this.add); + } + + componentWillUnmount() { + emitter.off(Events.ADD, this.add); + } + + add = (options = {}) => { + const id = ++this.counter; // eslint-disable-line + + const toast = { + ...this.props, + ...options, + id, + }; + + this.setState( + state => ({ + ...state, + toasts: [...state.toasts, toast], + }), + () => { + if (toast.autoClose) { + setTimeout(() => { + this.remove(id); + }, toast.timeout + toast.animationDuration); + } + } + ); + }; + + remove = id => { + this.setState(state => ({ + toasts: state.toasts.filter(t => t.id !== id), + })); + }; + + handleToastClick = toast => { + if (toast.closeOnClick) { + this.remove(toast.id); + } + }; + + render() { + const { toasts } = this.state; + + return Object.keys(Positions).map(key => { + const p = Positions[key]; + return ( + + + + {toasts + .filter(t => t.position === p) + .map(toast => ( + + {state => ( + this.handleToastClick(toast)}> + + {toast.message} + {toast.showClose && this.remove(toast.id)} />} + + + )} + + ))} + + + + ); + }); + } +} diff --git a/src/Toast/config.js b/src/Toast/config.js new file mode 100644 index 0000000..cf1ee1e --- /dev/null +++ b/src/Toast/config.js @@ -0,0 +1,81 @@ +import { css } from 'styled-components'; + +export const Events = { + ADD: 'add', + REMOVE: 'remove', +}; + +export const Types = { + SUCCESS: 'success', + WARN: 'warn', + INFO: 'info', + ERROR: 'error', +}; + +export const Positions = { + TOP_LEFT: 'top-left', + TOP_CENTER: 'top-center', + TOP_RIGHT: 'top-right', + BOTTOM_LEFT: 'bottom-left', + BOTTOM_CENTER: 'bottom-center', + BOTTOM_RIGHT: 'bottom-right', +}; + +export const PositionConfigs = { + [Positions.TOP_LEFT]: { + animationIn: 'slideInLeft', + animationOut: 'fadeOutUp', + wrapperStyle: css` + position: fixed; + left: 1rem; + top: 1rem; + `, + }, + [Positions.TOP_CENTER]: { + animationIn: 'slideInDown', + animationOut: 'fadeOutUp', + wrapperStyle: css` + position: fixed; + left: 50%; + transform: translateX(-50%); + top: 1rem; + `, + }, + [Positions.TOP_RIGHT]: { + animationIn: 'slideInRight', + animationOut: 'fadeOutUp', + wrapperStyle: css` + position: fixed; + right: 1rem; + top: 1rem; + `, + }, + [Positions.BOTTOM_LEFT]: { + animationIn: 'slideInLeft', + animationOut: 'fadeOutDown', + wrapperStyle: css` + position: fixed; + left: 1rem; + bottom: 1rem; + `, + }, + [Positions.BOTTOM_CENTER]: { + animationIn: 'slideInUp', + animationOut: 'fadeOutDown', + wrapperStyle: css` + position: fixed; + left: 50%; + transform: translateX(-50%); + border-bottom-color: 1rem; + `, + }, + [Positions.BOTTOM_RIGHT]: { + animationIn: 'slideInLeft', + animationOut: 'fadeOutDown', + wrapperStyle: css` + position: fixed; + right: 1rem; + bottom: 1rem; + `, + }, +}; diff --git a/src/Toast/index.js b/src/Toast/index.js new file mode 100644 index 0000000..a9faf5a --- /dev/null +++ b/src/Toast/index.js @@ -0,0 +1,2 @@ +export { default as toast } from './toast'; +export { default as ToastContainer } from './ToastContainer'; diff --git a/src/Toast/toast.js b/src/Toast/toast.js new file mode 100644 index 0000000..47f63a1 --- /dev/null +++ b/src/Toast/toast.js @@ -0,0 +1,30 @@ +import EventEmitter from 'mitt'; +import { Events } from './config'; + +export const emitter = new EventEmitter(); + +const toast = (options = {}) => { + if (!options.message) { + throw new Error('Molekule: Toast requires a message'); + } + + emitter.emit(Events.ADD, options); +}; + +toast.success = (message, options = {}) => { + toast({ message, type: 'success', ...options }); +}; + +toast.error = (message, options = {}) => { + toast({ message, type: 'error', ...options }); +}; + +toast.warn = (message, options = {}) => { + toast({ message, type: 'warn', ...options }); +}; + +toast.info = (message, options = {}) => { + toast({ message, type: 'info', ...options }); +}; + +export default toast; diff --git a/src/theme.js b/src/theme.js index 9d41fd5..9531615 100644 --- a/src/theme.js +++ b/src/theme.js @@ -2,6 +2,7 @@ import { merge } from 'lodash'; export default (overrides = {}) => { const shadow = '0 3px 6px hsla(0,0%,60%,.1), 0 3px 6px hsla(0,0%,60%,.15), 0 -1px 2px hsla(0,0%,60%,.02)'; + const shadowHover = '0 6px 9px hsla(0,0%,60%,.2), 0 6px 9px hsla(0,0%,60%,.2), 0 -1px 2px hsla(0,0%,60%,.08)'; const colors = merge( { @@ -91,6 +92,7 @@ export default (overrides = {}) => { radii, radius: 4, shadow, + shadowHover, typography, variants, }; diff --git a/src/utils.js b/src/utils.js index 6580bcf..2eadfa7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,6 @@ import { get, kebabCase } from 'lodash'; -import { themeGet as styledThemeGet } from 'styled-system'; import styled from 'styled-components'; +import { themeGet as styledThemeGet } from 'styled-system'; export const themeGet = styledThemeGet;