diff --git a/docs/lib/Components/ToastsPage.js b/docs/lib/Components/ToastsPage.js new file mode 100644 index 000000000..0cccf9061 --- /dev/null +++ b/docs/lib/Components/ToastsPage.js @@ -0,0 +1,88 @@ +/* eslint react/no-multi-comp: 0, react/prop-types: 0 */ +import React from 'react'; +import { PrismCode } from 'react-prism'; +import PageTitle from '../UI/PageTitle'; +import SectionTitle from '../UI/SectionTitle'; + +import ToastExample from '../examples/Toast'; +const ToastExampleSource = require('!!raw-loader!../examples/Toast'); + +import ToastBodyExample from '../examples/ToastBody'; +const ToastBodyExampleSource = require('!!raw-loader!../examples/ToastBody'); + +import ToastHeaderIconExample from '../examples/ToastHeaderIcon'; +const ToastHeaderIconExampleSource = require('!!raw-loader!../examples/ToastHeaderIcon'); + +import ToastDismissExample from '../examples/ToastDismiss'; +const ToastDismissExampleSource = require('!!raw-loader!../examples/ToastDismiss'); + +import AlertUncontrolledDismissExample from '../examples/AlertUncontrolledDismiss'; +const AlertUncontrolledDismissExampleSource = require('!!raw-loader!../examples/AlertUncontrolledDismiss'); + +import {AlertFadelessExample, UncontrolledAlertFadelessExample} from '../examples/AlertFadeless'; +const AlertFadelessExampleSource = require('!!raw-loader!../examples/AlertFadeless'); + +export default class ToastsPage extends React.Component { + render() { + return ( +
+ +
+ +
+
+          
+            {ToastExampleSource}
+          
+        
+ + Properties +
+          
+{`Toast.propTypes = {
+  className: PropTypes.string,
+  closeClassName: PropTypes.string,
+  color: PropTypes.string, // default: 'success'
+  isOpen: PropTypes.bool,  // default: true
+  toggle: PropTypes.func,
+  tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+  // Controls the transition of the toast fading in and out
+  // See [Fade](/components/fade/) for more details
+  transition: PropTypes.shape(Fade.propTypes),
+}`}
+          
+        
+ + Toast body +
+ +
+
+          
+            {ToastBodyExampleSource}
+          
+        
+ + Header icons +
+ +
+
+          
+            {ToastHeaderIconExampleSource}
+          
+        
+ + Dismissing +
+ +
+
+          
+            {ToastDismissExampleSource}
+          
+        
+
+ ); + } +} diff --git a/docs/lib/Components/index.js b/docs/lib/Components/index.js index 3d9055218..525279579 100644 --- a/docs/lib/Components/index.js +++ b/docs/lib/Components/index.js @@ -86,6 +86,10 @@ const items = [ name: 'Spinners', to: '/components/spinners/' }, + { + name: 'Toasts', + to: '/components/toasts/' + }, { name: 'Pagination', to: '/components/pagination/' diff --git a/docs/lib/examples/Toast.js b/docs/lib/examples/Toast.js new file mode 100644 index 000000000..c2792041e --- /dev/null +++ b/docs/lib/examples/Toast.js @@ -0,0 +1,111 @@ +import React from 'react'; +import { Toast, ToastBody, ToastHeader } from 'reactstrap'; + +const Example = (props) => { + return ( +
+
+ + + Reactstrap + + + This is a toast on a white background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a gridded background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a primary background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a secondary background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a success background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a danger background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a warning background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on an info background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a dark background — check it out! + + +
+
+ + + Reactstrap + + + This is a toast on a black background — check it out! + + +
+
+ ); +}; + +export default Example; diff --git a/docs/lib/examples/ToastBody.js b/docs/lib/examples/ToastBody.js new file mode 100644 index 000000000..34746ceb4 --- /dev/null +++ b/docs/lib/examples/ToastBody.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { Toast } from 'reactstrap'; + +const Example = (props) => { + return ( +
+ + This is a toast with a body — check it out! + +
+ ); +}; + +export default Example; diff --git a/docs/lib/examples/ToastDismiss.js b/docs/lib/examples/ToastDismiss.js new file mode 100644 index 000000000..a7c16ce84 --- /dev/null +++ b/docs/lib/examples/ToastDismiss.js @@ -0,0 +1,39 @@ +/* eslint react/no-multi-comp: 0, react/prop-types: 0 */ + +import React from 'react'; +import { Button, Toast, ToastBody, ToastHeader } from 'reactstrap'; + +class ToastDismissExample extends React.Component { + constructor(props) { + super(props); + this.state = { + show: false + }; + + this.toggle = this.toggle.bind(this); + } + + toggle() { + this.setState({ + show: !this.state.show + }); + } + + render() { + return ( +
+ +
+
+ + Toast title + + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + + +
+ ); + } +} + +export default ToastDismissExample; diff --git a/docs/lib/examples/ToastHeaderIcon.js b/docs/lib/examples/ToastHeaderIcon.js new file mode 100644 index 000000000..1c646538d --- /dev/null +++ b/docs/lib/examples/ToastHeaderIcon.js @@ -0,0 +1,75 @@ +import React from 'react'; +import { Toast, ToastBody, ToastHeader } from 'reactstrap'; + +const Example = (props) => { + return ( +
+ + + Reactstrap + + + This is a toast with a primary icon — check it out! + + + + + Reactstrap + + + This is a toast with a secondary icon — check it out! + + + + + Reactstrap + + + This is a toast with a success icon — check it out! + + + + + Reactstrap + + + This is a toast with a danger icon — check it out! + + + + + Reactstrap + + + This is a toast with a warning icon — check it out! + + + + + Reactstrap + + + This is a toast with an info icon — check it out! + + + + + Reactstrap + + + This is a toast with a light icon — check it out! + + + + + Reactstrap + + + This is a toast with a dark icon — check it out! + + +
+ ); +}; + +export default Example; diff --git a/docs/lib/routes.js b/docs/lib/routes.js index f96004943..b3ea7c0fb 100644 --- a/docs/lib/routes.js +++ b/docs/lib/routes.js @@ -24,6 +24,7 @@ import PaginationPage from './Components/PaginationPage'; import TabsPage from './Components/TabsPage'; import JumbotronPage from './Components/JumbotronPage'; import AlertsPage from './Components/AlertsPage'; +import ToastsPage from './Components/ToastsPage'; import CollapsePage from './Components/CollapsePage'; import CarouselPage from './Components/CarouselPage'; import ListGroupPage from './Components/ListGroupPage'; @@ -62,6 +63,7 @@ const routes = ( + diff --git a/docs/static/docs.css b/docs/static/docs.css index 2287fe999..82737168b 100644 --- a/docs/static/docs.css +++ b/docs/static/docs.css @@ -224,51 +224,51 @@ pre, code { word-break: normal; word-wrap: normal; line-height: 1.5; - + -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; - + -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } - + /* Code blocks */ pre[class*="language-"] { padding: 1em; margin: 1.5em 0; overflow: auto; } - + :not(pre) > code[class*="language-"], pre[class*="language-"] { background: #272822; } - + /* Inline code */ :not(pre) > code[class*="language-"] { padding: .1em; border-radius: .3em; white-space: normal; } - + .token.comment, .token.prolog, .token.doctype, .token.cdata { color: slategray; } - + .token.punctuation { color: #f8f8f2; } - + .namespace { opacity: .7; } - + .token.property, .token.tag, .token.constant, @@ -276,12 +276,12 @@ pre, code { .token.deleted { color: #f92672; } - + .token.boolean, .token.number { color: #ae81ff; } - + .token.selector, .token.attr-name, .token.string, @@ -290,7 +290,7 @@ pre, code { .token.inserted { color: #a6e22e; } - + .token.operator, .token.entity, .token.url, @@ -299,22 +299,22 @@ pre, code { .token.variable { color: #f8f8f2; } - + .token.atrule, .token.attr-value, .token.function { color: #e6db74; } - + .token.keyword { color: #66d9ef; } - + .token.regex, .token.important { color: #fd971f; } - + .token.important, .token.bold { font-weight: bold; @@ -322,11 +322,11 @@ pre, code { .token.italic { font-style: italic; } - + .token.entity { cursor: help; } - + .token a { color: inherit; } @@ -336,3 +336,7 @@ code .tag { padding: 0; display: inherit; } + +.bg-docs-transparent-grid { + background: url(./transparent.svg); +} \ No newline at end of file diff --git a/docs/static/transparent.svg b/docs/static/transparent.svg new file mode 100644 index 000000000..103178de2 --- /dev/null +++ b/docs/static/transparent.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/Toast.js b/src/Toast.js new file mode 100644 index 000000000..196a77fa9 --- /dev/null +++ b/src/Toast.js @@ -0,0 +1,82 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { mapToCssModules, tagPropType } from './utils'; +import Fade from './Fade'; + +const propTypes = { + children: PropTypes.node, + className: PropTypes.string, + closeClassName: PropTypes.string, + closeAriaLabel: PropTypes.string, + cssModule: PropTypes.object, + color: PropTypes.string, + body: PropTypes.bool, + fade: PropTypes.bool, + isOpen: PropTypes.bool, + toggle: PropTypes.func, + tag: tagPropType, + transition: PropTypes.shape(Fade.propTypes), + innerRef: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string, + PropTypes.func, + ]), +}; + +const defaultProps = { + color: 'success', + isOpen: true, + tag: 'div', + closeAriaLabel: 'Close', + fade: true, + transition: { + ...Fade.defaultProps, + unmountOnExit: true, + }, +}; + +function Alert(props) { + const { + className, + closeClassName, + closeAriaLabel, + cssModule, + tag: Tag, + color, + isOpen, + toggle, + children, + transition, + fade, + innerRef, + body, + ...attributes + } = props; + + const classes = mapToCssModules(classNames( + className, + 'toast', + body ? 'toast-body' : false, + ), cssModule); + + const closeClasses = mapToCssModules(classNames('close', closeClassName), cssModule); + + const alertTransition = { + ...Fade.defaultProps, + ...transition, + baseClass: fade ? transition.baseClass : '', + timeout: fade ? transition.timeout : 0, + }; + + return ( + + {children} + + ); +} + +Alert.propTypes = propTypes; +Alert.defaultProps = defaultProps; + +export default Alert; diff --git a/src/ToastBody.js b/src/ToastBody.js new file mode 100644 index 000000000..b8660b5bc --- /dev/null +++ b/src/ToastBody.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { mapToCssModules, tagPropType } from './utils'; + +const propTypes = { + tag: tagPropType, + className: PropTypes.string, + cssModule: PropTypes.object, + innerRef: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string, + PropTypes.func, + ]), +}; + +const defaultProps = { + tag: 'div' +}; + +const ToastBody = (props) => { + const { + className, + cssModule, + innerRef, + tag: Tag, + ...attributes + } = props; + const classes = mapToCssModules(classNames( + className, + 'toast-body' + ), cssModule); + + return ( + + ); +}; + +ToastBody.propTypes = propTypes; +ToastBody.defaultProps = defaultProps; + +export default ToastBody; diff --git a/src/ToastHeader.js b/src/ToastHeader.js new file mode 100644 index 000000000..721cb1b35 --- /dev/null +++ b/src/ToastHeader.js @@ -0,0 +1,82 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { mapToCssModules, tagPropType } from './utils'; + +const propTypes = { + tag: tagPropType, + icon: PropTypes.string, + wrapTag: tagPropType, + toggle: PropTypes.func, + className: PropTypes.string, + cssModule: PropTypes.object, + children: PropTypes.node, + closeAriaLabel: PropTypes.string, + charCode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + close: PropTypes.object, +}; + +const defaultProps = { + tag: 'strong', + wrapTag: 'div', + tagClassName: 'mr-auto', + closeAriaLabel: 'Close', + charCode: 215, +}; + +const ToastHeader = (props) => { + let closeButton; + const { + className, + cssModule, + children, + toggle, + tag: Tag, + wrapTag: WrapTag, + closeAriaLabel, + charCode, + close, + tagClassName, + icon, + ...attributes } = props; + + const classes = mapToCssModules(classNames( + className, + 'toast-header' + ), cssModule); + + if (!close && toggle) { + const closeIcon = typeof charCode === 'number' ? String.fromCharCode(charCode) : charCode; + closeButton = ( + + ); + } + + return ( + + {icon && ( + + )} + + {children} + + {close || closeButton} + + ); +}; + +ToastHeader.propTypes = propTypes; +ToastHeader.defaultProps = defaultProps; + +export default ToastHeader; diff --git a/src/index.js b/src/index.js index a841338bf..148ae0a96 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ +import ToastHeader from "./ToastHeader"; + export Container from './Container'; export Row from './Row'; export Col from './Col'; @@ -76,6 +78,9 @@ export TabContent from './TabContent'; export TabPane from './TabPane'; export Jumbotron from './Jumbotron'; export Alert from './Alert'; +export Toast from './Toast'; +export ToastBody from './ToastBody'; +export ToastHeader from './ToastHeader'; export Collapse from './Collapse'; export ListGroupItem from './ListGroupItem'; export ListGroupItemHeading from './ListGroupItemHeading'; diff --git a/webpack.docs.config.js b/webpack.docs.config.js index 0df6b1f9b..341c75152 100644 --- a/webpack.docs.config.js +++ b/webpack.docs.config.js @@ -34,6 +34,7 @@ const paths = [ '/components/tabs/', '/components/jumbotron/', '/components/alerts/', + '/components/toasts/', '/components/collapse/', '/components/carousel/', '/components/listgroup/',