├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── docs.yml ├── .gitignore ├── .npmignore ├── .tool-versions ├── LICENSE ├── README.md ├── babel.config.json ├── docs ├── api.md ├── demos │ ├── alert-container │ │ ├── code.example │ │ ├── description.md │ │ └── index.js │ ├── alert-list │ │ ├── code.example │ │ ├── description.md │ │ └── index.js │ ├── alert │ │ ├── code.example │ │ ├── description.md │ │ └── index.js │ ├── code-example.js │ ├── index.js │ └── themed │ │ ├── code.example │ │ ├── description.md │ │ └── index.js ├── header.js ├── index.html ├── index.js ├── intro.md ├── root.js └── scroll-text.js ├── package-lock.json ├── package.json ├── src ├── alert-list.js ├── alert-timer.js ├── alert-transition.js ├── alert │ ├── icon.js │ └── index.js ├── container.js ├── index.js └── transition-styles.js └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | module.exports = { 4 | env: { 5 | es6: true, 6 | node: true, 7 | browser: true 8 | }, 9 | extends: ["@runly"], 10 | rules: { 11 | "react/jsx-no-bind": "off" 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | 4 | jobs: 5 | lint: 6 | name: Lint 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12.x' 14 | 15 | - name: Cache Dependencies 16 | uses: actions/cache@v2 17 | with: 18 | path: '**/node_modules' 19 | key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }} 20 | restore-keys: | 21 | ${{ runner.os }}-deps- 22 | 23 | - name: Install dependencies 24 | run: npm i 25 | 26 | - run: npm run lint 27 | 28 | build_docs: 29 | name: Build Docs 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | - uses: actions/setup-node@v1 35 | with: 36 | node-version: '12.x' 37 | 38 | - name: Cache Dependencies 39 | uses: actions/cache@v2 40 | with: 41 | path: '**/node_modules' 42 | key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }} 43 | restore-keys: | 44 | ${{ runner.os }}-deps- 45 | 46 | - name: Install dependencies 47 | run: npm i 48 | 49 | - run: npm run build --production 50 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | docfx: 8 | name: Deploy Docs 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout Main Branch 13 | uses: actions/checkout@v2 14 | 15 | - name: Get Version 16 | id: get_version 17 | run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//} 18 | 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: '12.x' 22 | 23 | - name: Cache Dependencies 24 | uses: actions/cache@v2 25 | with: 26 | path: '**/node_modules' 27 | key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }} 28 | restore-keys: | 29 | ${{ runner.os }}-deps- 30 | 31 | - name: Install dependencies 32 | run: npm i 33 | 34 | - name: Build Documentation 35 | run: npm run build --production 36 | 37 | - name: Checkout gh-pages Branch 38 | uses: actions/checkout@v2 39 | with: 40 | ref: gh-pages 41 | path: gh-pages 42 | 43 | - name: Stage files for commit 44 | working-directory: gh-pages 45 | run: | 46 | cp ../docs/index.html . 47 | cp ../docs/build.js . 48 | cp ../docs/build.js.map . 49 | 50 | - name: Commit doc assets 51 | working-directory: gh-pages 52 | run: | 53 | git config user.name "${GITHUB_ACTOR}" 54 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 55 | git add . 56 | git diff-index --quiet HEAD || git commit -m "Documentation ${{ steps.get_version.outputs.version }}" 57 | git push 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /lib 3 | /es 4 | npm-debug.log 5 | docs/build.* 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /docs 3 | babel.config.json 4 | webpack.config.js 5 | .eslintrc.js 6 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 18.12.1 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Chad Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Bootstrap Notifier 2 | 3 | > A react component to show growl-like notifications using [bootstrap alerts](https://getbootstrap.com/docs/4.5/components/alerts/). 4 | 5 | See a [live demo](https://chadly.github.io/react-bs-notifier/). 6 | 7 | ## Usage 8 | 9 | ``` 10 | npm install react-bs-notifier --save 11 | ``` 12 | 13 | To show a list of different types of alerts in the top right corner of the page: 14 | 15 | ```js 16 | import React from "react"; 17 | import ReactDOM from "react-dom"; 18 | import { AlertList } from "react-bs-notifier"; 19 | 20 | const alerts = [{ 21 | id: 1, 22 | type: "info", 23 | message: "Hello, world" 24 | }, { 25 | id: 2, 26 | type: "success", 27 | message: "Oh, hai" 28 | }] 29 | 30 | ReactDOM.render(( 31 | 32 | ), document.getElementById("myApp")); 33 | ``` 34 | 35 | Or to show a single inline-alert: 36 | 37 | ```js 38 | import React from "react"; 39 | import ReactDOM from "react-dom"; 40 | import { Alert } from "react-bs-notifier"; 41 | 42 | ReactDOM.render(( 43 | 44 | Holy cow, man! 45 | 46 | ), document.getElementById("myApp")); 47 | ``` 48 | 49 | Or show alerts without creating an array (equivalent to first example): 50 | 51 | ```js 52 | import React from "react"; 53 | import ReactDOM from "react-dom"; 54 | import { Alert, AlertContainer } from "react-bs-notifier"; 55 | 56 | ReactDOM.render(( 57 | 58 | Hello, world 59 | Oh, hai 60 | 61 | ), document.getElementById("myApp")); 62 | ``` 63 | 64 | [Read the documentation](https://chadly.github.io/react-bs-notifier/) for more in-depth, interactive examples and live demos. 65 | 66 | ## Contributing 67 | 68 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 69 | 70 | If you have a new feature or change you'd like to submit, please submit an issue first to talk about the change you want to make. When you are finished making your change, please make sure to also update the documentation. Once you clone this repo, you can run the docs & example app locally: 71 | 72 | ``` 73 | npm install 74 | npm start 75 | ``` 76 | 77 | This will spin up a webpack dev server on port 1341. Open your browser to [localhost:1341](http://localhost:1341/) and any changes you make will build & refresh the page automatically. 78 | 79 | ### Linting 80 | 81 | To run the linter: 82 | 83 | ``` 84 | npm run lint 85 | ``` 86 | 87 | This project uses [prettier for formatting](https://github.com/prettier/prettier) and will fail linting if the code doesn't conform to prettier's output. To automatically fix any formatting issues, run: 88 | 89 | ``` 90 | npm run lint -- --fix 91 | ``` 92 | 93 | Or, if you are using an editor that supports [ESLint](http://eslint.org/), just make sure to enable automatically fixing lint errors on save. E.g., in [Visual Studio Code](https://code.visualstudio.com/) with the [ESLint plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint), make sure this is in your `settings.json`: 94 | 95 | ```json 96 | { 97 | "eslint.autoFixOnSave": true 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "browsers": "defaults" 8 | }, 9 | "modules": false 10 | } 11 | ], 12 | "@babel/preset-react" 13 | ], 14 | "plugins": ["@babel/proposal-export-default-from"], 15 | "env": { 16 | "cjs": { 17 | "presets": [ 18 | [ 19 | "@babel/preset-env", 20 | { 21 | "targets": { 22 | "node": true 23 | } 24 | } 25 | ], 26 | "@babel/preset-react" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | ### `AlertList` Props 2 | 3 | The `AlertList` component will render an array of `alerts` into one of the corners of the page. It takes the following `props`: 4 | 5 | --- 6 | 7 | #### `alerts` 8 | 9 | These are the alerts that the component should render. It expects an object with fields `id` & `message` with an optional `type` & `headline`. 10 | 11 | Hint: if you don't have anything good to make an `id` out of, try using: `new Date()).getTime()` 12 | 13 | If no `headline` is specified, only the message will show up in the alert. 14 | 15 | The supported values for `type` are one of _info_, _warning_, _danger_, or _success_. If no `type` is specified, it is assumed to be _info_. 16 | 17 | The `message` can be plain text or any arbitrary React component. Please note though that although the `message` can accept arbitrary content, the height animation on the alerts is static and based on the assumption that the content won't exceed `20em` vertically. You probably want to avoid putting anything that long in an alert which overlaps your UI anyway as opposed to linking to more detail. 18 | 19 | --- 20 | 21 | #### `position` 22 | 23 | The corner in which alerts appear. One of _top-right_ (default), _top-left_, _bottom-right_, _bottom-left_. 24 | 25 | --- 26 | 27 | #### `timeout` 28 | 29 | An optional timeout (in milliseconds) before alerts are automatically dismissed. If not specified, an alert will not be dismissed until the user dismisses it. If a timeout is specified but no `onDismiss` function, it is equivalent to specifying no timeout. There is no timeout by default. 30 | 31 | --- 32 | 33 | #### `onDismiss` 34 | 35 | When a user clicks the × icon to dismiss the alert, this function will be called. The function is optional. If no `onDismiss` function is specified, the component will not render a close button for the alert and it will be undismissable by the user. The dismiss function will receive the `alert` that was dismissed as its sole argument. 36 | 37 | --- 38 | 39 | #### `showIcon` 40 | 41 | A boolean value which indicates whether or not the alert type icon should be rendered. It is `true` by default. 42 | 43 | --- 44 | 45 | #### `dismissTitle` 46 | 47 | The title text for the dismiss-alert button. By default, it is _Dismiss_. 48 | 49 | --- 50 | 51 | ### `Alert` Props 52 | 53 | An `Alert` component by itself will render an alert inline. It takes the following `props`: 54 | 55 | #### `type` 56 | 57 | An optional type for the alert. The supported values for `type` are one of _info_, _warning_, _danger_, or _success_. If no `type` is specified, it is assumed to be _info_. 58 | 59 | --- 60 | 61 | #### `headline` 62 | 63 | An optional larger headline text to show in the alert. If no `headline` is specified, only the child content will show up in the alert. 64 | 65 | --- 66 | 67 | #### `onDismiss` 68 | 69 | When a user clicks the × icon to dismiss the alert, this function will be called. The function is optional. If no `onDismiss` function is specified, the component will not render a close button for the alert and it will be undismissable by the user. The `onDismiss` function is called with no arguments. 70 | 71 | --- 72 | 73 | #### `showIcon` 74 | 75 | A boolean value which indicates whether or not the alert type icon should be rendered. It is `true` by default. 76 | 77 | --- 78 | 79 | #### `dismissTitle` 80 | 81 | The title text for the dismiss-alert button. By default, it is _Dismiss_. 82 | 83 | --- 84 | 85 | #### `timeout` 86 | 87 | An optional timeout (in milliseconds) before this alert will be automatically dismissed. If not specified, an alert will not be dismissed until the user dismisses it. If a timeout is specified but no `onDismiss` function, it is equivalent to specifying no timeout. There is no timeout by default. 88 | 89 | --- 90 | 91 | #### `classes` 92 | 93 | JSS style overrides for the component. The following classes can be overridden: 94 | 95 | * `alert`: CSS class applied to the root `div` of the `Alert` 96 | * `info`: CSS class applied to the root `div` for `info` alerts 97 | * `success`: CSS class applied to the root `div` for `success` alerts 98 | * `warning`: CSS class applied to the root `div` for `warning` alerts 99 | * `danger`: CSS class applied to the root `div` for `danger` alerts 100 | * `dismissable`: CSS class applied to the root `div` for alerts that can be dismissed 101 | * `close`: CSS class applied to the _dismiss_ button 102 | * `icon`: CSS class applied to the alert type icon 103 | * `msgContainer`: CSS class applied to the container holding the headline and message 104 | * `headline`: CSS class applied to the headline element (if provided) 105 | * `body`: CSS class applied to the message element itself 106 | 107 | See the _Themed Alert_ example above. 108 | 109 | --- 110 | 111 | ### `AlertContainer` Props 112 | 113 | The `AlertContainer` will render it's child `Alert`s in one of the corners of the page. It takes the following `props`: 114 | 115 | --- 116 | 117 | #### `position` 118 | 119 | The corner in which alerts appear. One of _top-right_ (default), _top-left_, _bottom-right_, _bottom-left_. -------------------------------------------------------------------------------- /docs/demos/alert-container/code.example: -------------------------------------------------------------------------------- 1 | function MyComponent() { 2 | const [showing, setShowing] = React.useState({ 3 | info: false, 4 | success: false, 5 | warning: false, 6 | danger: false 7 | }); 8 | 9 | const onAlertToggle = React.useCallback( 10 | type => setShowing(s => ({ ...s, [type]: !s[type] })), 11 | [] 12 | ); 13 | 14 | return ( 15 |
16 | 17 | {showing.info ? ( 18 | 19 | Something happened of only moderate importance. 20 | 21 | ) : null} 22 | 23 | {showing.success ? ( 24 | 25 | Something amazing has happened! 26 | 27 | ) : null} 28 | 29 | {showing.warning ? ( 30 | 31 | Something bad may be about to happen. 32 | 33 | ) : null} 34 | 35 | {showing.danger ? ( 36 | 37 | Something bad has happened. Panic! 38 | 39 | ) : null} 40 | 41 | 42 |
43 | 46 | 47 | 53 | 54 | 60 | 61 | 67 |
68 |
69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /docs/demos/alert-container/description.md: -------------------------------------------------------------------------------- 1 | You can use the `AlertContainer` in combination with individual `Alert` components to recreate the functionality of the `AlertList`. If you'd rather not have a central list of alerts and you want to control which alerts appear inline, this is a nice option. -------------------------------------------------------------------------------- /docs/demos/alert-container/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CodeExample from "../code-example"; 3 | 4 | import ex from "./code.example"; 5 | import desc from "./description.md"; 6 | 7 | const AlertContainerDemo = props => ( 8 | AlertContainer} 10 | code={ex} 11 | description={desc} 12 | {...props} 13 | /> 14 | ); 15 | 16 | export default AlertContainerDemo; 17 | -------------------------------------------------------------------------------- /docs/demos/alert-list/code.example: -------------------------------------------------------------------------------- 1 | function NotifierGenerator() { 2 | const [position, setPosition] = React.useState("top-right"); 3 | const [alerts, setAlerts] = React.useState([]); 4 | const [alertTimeout, setAlertTimeout] = React.useState(0); 5 | const [newMessage, setNewMessage] = React.useState( 6 | "This is a test of the Emergency Broadcast System. This is only a test." 7 | ); 8 | 9 | const generate = React.useCallback( 10 | type => { 11 | setAlerts(alerts => [ 12 | ...alerts, 13 | { 14 | id: new Date().getTime(), 15 | type: type, 16 | headline: `Whoa, ${type}!`, 17 | message: newMessage 18 | } 19 | ]); 20 | }, 21 | [newMessage] 22 | ); 23 | 24 | const onDismissed = React.useCallback(alert => { 25 | setAlerts(alerts => { 26 | const idx = alerts.indexOf(alert); 27 | if (idx < 0) return alerts; 28 | return [...alerts.slice(0, idx), ...alerts.slice(idx + 1)]; 29 | }); 30 | }, []); 31 | 32 | const clearAlerts = React.useCallback(() => setAlerts([]), []); 33 | 34 | const onTimeoutChange = React.useCallback( 35 | ({ target: { value } }) => setAlertTimeout(+value * 1000), 36 | [] 37 | ); 38 | 39 | const onNewMessageChange = React.useCallback( 40 | ({ target: { value } }) => setNewMessage(value), 41 | [] 42 | ); 43 | 44 | const onPositionChange = React.useCallback( 45 | ({ target: { value } }) => setPosition(value), 46 | [] 47 | ); 48 | 49 | const clearAllButton = alerts.length ? ( 50 | 53 | ) : null; 54 | 55 | return ( 56 | <> 57 | 64 | 65 |
66 | 67 |