├── .browserslistrc ├── .lintstagedrc ├── .commitlintrc.json ├── postcss.config.js ├── assets └── 480w │ └── breadstick-logo.png ├── docs ├── .vuepress │ ├── public │ │ └── breadstick-logo.png │ ├── config.js │ └── enhanceApp.js ├── support │ └── README.md ├── guide │ └── getting-started.md ├── README.md ├── examples │ └── README.md ├── api │ └── README.md └── advanced │ └── README.md ├── .editorconfig ├── src ├── index.js ├── plugin │ └── index.js └── components │ ├── Alert │ ├── alert.test.js │ ├── index.js │ └── styles.css │ ├── Breadstick │ └── index.js │ ├── Message │ └── index.js │ └── BreadstickManager │ └── index.js ├── .huskyrc ├── babel.config.js ├── jest.config.js ├── .gitignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md └── CODE_OF_CONDUCT.md ├── .all-contributorsrc ├── LICENSE ├── rollup.config.js ├── package.json └── README.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,vue}": "eslint --fix" 3 | } -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ] 5 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /assets/480w/breadstick-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebender828/breadstick/HEAD/assets/480w/breadstick-logo.png -------------------------------------------------------------------------------- /docs/.vuepress/public/breadstick-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebender828/breadstick/HEAD/docs/.vuepress/public/breadstick-logo.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Breadstick from './components/Breadstick' 2 | export default Breadstick 3 | export { default as BreadstickBakery } from './plugin' 4 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged", 4 | "pre-push": "yarn test", 5 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env' 4 | ], 5 | plugins: [ 6 | '@babel/plugin-proposal-class-properties' 7 | ], 8 | exclude: ['node_modules'] 9 | } 10 | -------------------------------------------------------------------------------- /src/plugin/index.js: -------------------------------------------------------------------------------- 1 | import Breadstick from '../components/Breadstick' 2 | 3 | /** 4 | * Breadstick plugin 5 | */ 6 | export default { 7 | install (Vue, options = {}) { 8 | Vue.prototype.$breadstick = new Breadstick() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 4 | '^.+\\.(js|jsx)?$': 'babel-jest' 5 | }, 6 | testMatch: [ 7 | '**/src/**/*.test.(js|jsx|ts|tsx)' 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/components/Alert/alert.test.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import { Alert } from '.' 3 | 4 | describe('===== Alert Component Tests =====', () => { 5 | let alert 6 | beforeEach(() => { 7 | alert = shallowMount(Alert, { 8 | propsData: { 9 | id: 123, 10 | title: 'Test notification', 11 | close: jest.fn(), 12 | clear: jest.fn(), 13 | reset: jest.fn() 14 | } 15 | }) 16 | }) 17 | describe('Instance tests', () => { 18 | it('should be a Vue instance', () => { 19 | expect(alert.isVueInstance()).toBeTruthy() 20 | }) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | jest: true 6 | }, 7 | 'extends': [ 8 | 'plugin:vue/essential', 9 | '@vue/standard' 10 | ], 11 | rules: { 12 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 14 | }, 15 | parserOptions: { 16 | parser: 'babel-eslint' 17 | }, 18 | overrides: [ 19 | { 20 | files: [ 21 | '**/__tests__/*.{j,t}s?(x)', 22 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 23 | ], 24 | env: { 25 | jest: true 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /docs/support/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Support 3 | --- 4 | 5 | ## ❤️ Support this project 6 | If you like this project, please consider supporting it by buying my a coffee! 7 | 8 | 9 | Buy me a coffee 10 | Buy me a coffee 11 | 12 | 13 |
14 |
15 | Made with ❤️ by Jonathan Bakebwa 🇺🇬 16 |
-------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "codebender828", 10 | "name": "Jonathan Bakebwa", 11 | "avatar_url": "https://avatars2.githubusercontent.com/u/21237954?v=4", 12 | "profile": "https://jbakebwa.dev", 13 | "contributions": [ 14 | "code", 15 | "doc", 16 | "test" 17 | ] 18 | }, 19 | { 20 | "login": "DominusKelvin", 21 | "name": "Omereshone Kelvin Oghenerhoro", 22 | "avatar_url": "https://avatars0.githubusercontent.com/u/24433274?v=4", 23 | "profile": "http://bit.ly/becomeworldclass", 24 | "contributions": [ 25 | "doc" 26 | ] 27 | } 28 | ], 29 | "contributorsPerLine": 7, 30 | "projectName": "breadstick", 31 | "projectOwner": "codebender828", 32 | "repoType": "github", 33 | "repoHost": "https://github.com", 34 | "skipCi": true 35 | } 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 11 | 2. Update the README.md with details of changes to the interface, this includes new environment 12 | variables, exposed ports, useful file locations and container parameters. 13 | 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 14 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 15 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | --- 4 | 5 | ## :speaking_head: Introduction 6 | Breadstick is a simple and flexible implementation of toast style notifications for Vue.js. 7 | 8 | _🌟 Inspired by [toasted-notes](https://github.com/bmcmahen/toasted-notes) for React - which I think is really cool. ❤️_ 9 | 10 | ## Demo 11 | See a live [demo](https://breadstick.now.sh/) of breadstick 12 | 13 | ## ⚡️ Installation 14 | Install `breadstick` and its peer dependency, `animejs`, using yarn or npm. 15 | 16 | ```bash 17 | npm install breadstick animejs --save 18 | ``` 19 | 20 | You can then register `BreadstickBakery` as a Vue plugin. 21 | ```js 22 | import Vue from 'vue' 23 | import { BreadstickBakery } from 'breadstick' 24 | 25 | // This exposes `this.$breadstick` in your Vue template. 26 | Vue.use(BreadstickBakery) 27 | ``` 28 | 29 | #### Installing with Nuxt 30 | After installing Breadstick, we import it and register it as a **client-side plugin**. This is because Breadstick it makes use of some DOM APIs. Code is similiar to the Vue plugin shown above. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jonathan Bakebwa 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 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | actionText: Get Started → 4 | actionLink: /guide/getting-started 5 | heroImage: /breadstick-logo.png 6 | features: 7 | - title: An imperative API 8 | details: This means that you don't need to set component state or render elements to trigger notifications. Instead, just call a function. This makes it very user friendly for component library authors. 9 | - title: Render whatever you want 10 | details: utilize the render callback to create entirely custom notifications. 11 | - title: Functional default styles 12 | details: Import the provided css for some nice styling defaults or write your own styles. 13 | - title: JS agnostic notifications 14 | details: Breadstick can be incrementally adopted to your application since it uses the already progressive Vue.js under the hood. 15 | footer: Made with 💚 by Jonathan Bakebwa 16 | --- 17 | 18 | ## 🥳 Getting Started 19 | Here are a few Codesandbox starters you can use to get started with Breadstick in your Vue or Nuxt App. 20 | 21 | * Vue Starter: [http://bit.ly/breadstick-vue](http://bit.ly/breadstick-vue) 22 | * Nuxt.js Starter: [http://bit.ly/breadstick-nuxt](http://bit.ly/breadstick-nuxt) -------------------------------------------------------------------------------- /src/components/Alert/index.js: -------------------------------------------------------------------------------- 1 | import './styles.css' 2 | 3 | export const Alert = { 4 | name: 'Alert', 5 | props: { 6 | id: [String, Number], 7 | title: [String, Object], 8 | close: Function, 9 | clear: Function, 10 | reset: Function 11 | }, 12 | render (h) { 13 | return h('span', { 14 | class: ['Breadstick__alert'], 15 | attrs: { 16 | id: this.id 17 | }, 18 | on: { 19 | mouseenter: this.clear, 20 | mouseleave: this.reset 21 | } 22 | }, [ 23 | typeof title === 'string' 24 | ? h('div', { 25 | class: ['Breadstick__alert_text'] 26 | }, this.title) : this.title, 27 | !this.title && this.$slots.default, 28 | this.close && h(Close, { 29 | props: { 30 | close: this.close 31 | } 32 | }) 33 | ]) 34 | } 35 | 36 | } 37 | 38 | export const Close = { 39 | name: 'Close', 40 | props: { 41 | close: { 42 | type: Function, 43 | default: () => null 44 | } 45 | }, 46 | render (h) { 47 | return h('button', { 48 | class: ['Breadstick__alert_close'], 49 | attrs: { 50 | type: 'button', 51 | 'aria-label': this.close 52 | }, 53 | on: { 54 | click: this.close 55 | } 56 | }, [h('span', { attrs: { 'aria-hidden': true } }, '×')]) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/Alert/styles.css: -------------------------------------------------------------------------------- 1 | .Breadstick__alert { 2 | margin: 2.5px; 3 | background-color: white; 4 | overflow: hidden; 5 | max-width: 650px; 6 | position: relative; 7 | border-radius: 0.4rem; 8 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 9 | display: flex; 10 | padding: 1rem; 11 | padding-right: 48px; 12 | box-shadow: rgba(52, 58, 64, 0.15) 0px 1px 10px 0px, 13 | rgba(52, 58, 64, 0.1) 0px 6px 12px 0px, 14 | rgba(52, 58, 64, 0.12) 0px 6px 15px -2px; 15 | } 16 | 17 | .Breadstick__alert_text { 18 | box-sizing: border-box; 19 | -webkit-font-smoothing: antialiased; 20 | font-weight: 500; 21 | line-height: 1.5; 22 | font-size: 1rem; 23 | margin: 0px; 24 | } 25 | 26 | .Breadstick__alert_close { 27 | padding: 12px; 28 | outline: none; 29 | cursor: pointer; 30 | background-color: transparent; 31 | position: absolute; 32 | top: 7px; 33 | right: 4px; 34 | border-radius: 0.4rem; 35 | border: 0; 36 | -webkit-appearance: none; 37 | font-size: 1rem; 38 | font-weight: 700; 39 | line-height: 1; 40 | text-shadow: 0 1px 0 #fff; 41 | opacity: 0.5; 42 | } 43 | 44 | .Breadstick__alert_close:focus { 45 | box-shadow: rgba(52, 58, 64, 0.15) 0px 0px 0px 3px; 46 | } 47 | 48 | .Breadstick__message-wrapper { 49 | padding: 8px; 50 | } 51 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Screenshots (if appropriate): 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 22 | 23 | ## Checklist: 24 | 25 | 26 | - [ ] My code follows the code style of this project. 27 | - [ ] My change requires a change to the documentation. 28 | - [ ] I have updated the documentation accordingly. 29 | - [ ] I have added tests to cover my changes. 30 | - [ ] All new and existing tests passed. 31 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Breadstick', 3 | description: "A simple but flexible implementation of toast style notifications for Vue.js.", 4 | themeConfig: { 5 | nav: [ 6 | { 7 | text: 'Guide', 8 | link: '/guide/getting-started.html' 9 | }, 10 | { 11 | text: 'API', 12 | link: '/api/' 13 | }, 14 | { 15 | text: 'Examples', 16 | link: '/examples/' 17 | }, 18 | { 19 | text: 'Github', 20 | link: 'https://github.com/codebender828/breadstick' 21 | }, 22 | ], 23 | repo: 'codebender828/breadstick', 24 | docsDir: 'docs', 25 | docsBranch: 'dev', 26 | sidebar: [ 27 | { 28 | title: 'Guide', 29 | collapsable: false, 30 | children: [ 31 | '/guide/getting-started', 32 | ] 33 | }, 34 | { 35 | title: 'Examples', 36 | collapsable: false, 37 | children: [ 38 | '/examples/' 39 | ] 40 | }, 41 | { 42 | title: 'Advanced Usage', 43 | collapsable: false, 44 | children: [ 45 | '/advanced/' 46 | ] 47 | }, 48 | { 49 | title: 'API', 50 | collapsable: false, 51 | children: [ 52 | '/api/' 53 | ] 54 | }, 55 | { 56 | title: '❤️ Support', 57 | collapsable: false, 58 | children: [ 59 | '/support/' 60 | ] 61 | } 62 | ], 63 | editLinks: true, 64 | editLinkText: 'Help us improve this page!' 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using Breadstick 3 | name: Using Breadstick 4 | --- 5 | 6 | ## 🥳 Starters 7 | Here are a few Codesandbox starters you can use to get started with Breadstick in your Vue or Nuxt App. 8 | - **[Vue.js Starter](http://bit.ly/breadstick-vue)** 9 | - **[Nuxt.js Starter](http://bit.ly/breadstick-nuxt)** 10 | 11 | Breadstick can be used to render different types of notifications out of the box. You can render simple string notifications as well as custom styled notifications. This makes it really convenient. 12 | 13 | ## 🍊 Basic usage 14 | Invoke a notification using the `notify` method to display a message to the user. 15 | Breadstick defaults the notification duration to 5 seconds in the `top` position. 16 | 17 | ```js 18 | this.$breadstick.notify('🥞 Show me the pancakes') 19 | ``` 20 | 21 | ## 📚 Using different positions 22 | You can display notifications in different positions, including top-left, top, top-right, bottom-left, bottom, and bottom-right. 23 | 24 | ```js 25 | [ 26 | 'top-left', 27 | 'top', 28 | 'top-right', 29 | 'bottom-left', 30 | 'bottom', 31 | 'bottom-right' 32 | ].forEach(position => { 33 | this.$breadstick.notify("Using position " + position, { 34 | position 35 | }) 36 | }) 37 | ``` 38 | 39 | ## 🏠 Using custom element 40 | With JSX or Vue's render function, breadstick is able to render a custom element or Vue component 41 | 42 | ```jsx 43 | this.$breadstick.notify( 44 |
I am a custom HTML element
45 | ) 46 | ``` 47 | 48 | ## 📭 Close all notifications 49 | You can clear all notifications by calling breadstick's `closeAll` method 50 | 51 | ```jsx 52 | this.$breadstick.closeAll() 53 | ``` -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 3 | --- 4 | 5 | ## `notify(String|VNode|Function, options)` 6 | - **Arguments** 7 | - `{ String | VNode | Function }` Message 8 | - `{ Object }` options 9 | 10 | Breadstick's `notify` method accepts two parameters. The first parameter can be a `String`, `VNode` (Object), or `Function` and the second is the options object. 11 | 12 | If a string is passed in the first argument, breadstick will render a notificiation with the string in the top center position with it's default internal component. 13 | ```js 14 | this.$breadstick.notify('Simple notification.') 15 | ``` 16 | 17 | If a `VNode` is passed, Breadstick treats it like a rendered component and renders it instead. 18 | ```jsx 19 | this.$breadstick.notify( 20 |
I am a custom HTML element
21 | ) 22 | ``` 23 | 24 | If a callback `Function` is passed in the first argument, it will expose an object with two parameters: `h` and the `onClose` which are both functions. Using a render callback allows you to tap into the close function. It's your best option if you want to completely re-style your toast notification 25 | 26 | ```js 27 | this.$breadstick.notify(({ h, onClose }) => { 28 | return h('div', 'My custom notification') 29 | }) 30 | ``` 31 | 32 | ## Options 33 | Option | Type | Default | Values 34 | --- | --- | --- | -- 35 | `position` | `String` | `top` | `top`, `right`, `bottom`, `left`, `top-left`, `top-right`, `bottom-right`, `bottom-left` 36 | `duration` | `Number` | 5000 | Any number in milliseconds 37 | 38 | 39 | ## `closeAll()` 40 | - Type: `Function` 41 | The `closeAll` method closes all toast notifications that are visible in the UI at the time of invocation. Nice a succinct way to dismiss all notifications 42 | -------------------------------------------------------------------------------- /docs/advanced/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | Advanced Usage 3 | --- 4 | 5 | # Advanced Usage 6 | 7 | Whereas breadstick shines in making simple notifications for your Vue app, it's real strength is shown in allowing you to create custom notifications through it's render function callback. 8 | 9 | This is particularly useful if you want use custom themed elements or Vue components inside of your toast notification. In the following snippet, we render a custom `Alert` component to display a toast notification. 10 | 11 | This is particularly useful for building your own themed notification component library. 12 | 13 | [Here are some examples](#) of how to use breadstick to render you own custom element. 14 | 15 | ## 🌮 With Vue's `render` function callback 16 | Breadstick exposes Vue's `createElement` function in the render callback that you can use to render your own components in a toast notification. This can be useful in a context where Vue's `this` context may not be available. 17 | 18 | In a Vue component, you can even use that component's `this.$createElement` to render your own element/component and return it in the render function callback so breadstick can display it. 19 | 20 | ```js 21 | // Import your custom `Alert` component and render it in breadstick 22 | import Alert from './components/Alert' 23 | 24 | export default { 25 | mounted () { 26 | this.$breadstick.notify(({ h, onClose }) => { 27 | return h(Alert, { 28 | on: { 29 | click: onClose 30 | } 31 | }, 'A render function Alert notification') 32 | }) 33 | } 34 | } 35 | ``` 36 | 37 | ## 🚚 With JSX 38 | You can also use JSX if you like :). 39 | ```jsx 40 | // Import your custom `Alert` component and render it in breadstick 41 | import Alert from './components/Alert' 42 | 43 | export default { 44 | mounted () { 45 | breadstick.notify(({ onClose }) => { 46 | return ( 47 | 48 | An JSX Alert notification 49 | 50 | ) 51 | } 52 | } 53 | } 54 | ``` -------------------------------------------------------------------------------- /src/components/Breadstick/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import BreadstickManager from '../BreadstickManager' 3 | 4 | const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined' 5 | const PORTAL_ID = 'breadstick-kitchen' 6 | const ROOT_ID = 'breadstick-chef' 7 | const VM = typeof window !== 'undefined' && window.Vue ? window.Vue : Vue 8 | /** 9 | * @description Create Toast Portal 10 | * @returns {HTMLElement} 11 | */ 12 | function createPortal () { 13 | if (!isBrowser) { 14 | return 15 | } 16 | 17 | let portalElement 18 | const existingPortalElement = document.getElementById(PORTAL_ID) 19 | 20 | if (existingPortalElement) { 21 | portalElement = existingPortalElement 22 | } else { 23 | const el = document.createElement('div') 24 | el.id = PORTAL_ID 25 | el.className = 'Breadstick' 26 | if (document.body != null) { 27 | document.body.appendChild(el) 28 | } 29 | portalElement = el 30 | } 31 | 32 | // Create toaster-kitchen root instance 33 | const root = document.createElement('div') 34 | root.id = ROOT_ID 35 | portalElement.appendChild(root) 36 | return portalElement 37 | } 38 | 39 | function createBreadstick (boundNotify) { 40 | new VM({ 41 | el: `#${ROOT_ID}`, 42 | render (h) { 43 | return h(BreadstickManager, { 44 | props: { 45 | notify: boundNotify 46 | } 47 | }, this.$slots.default) 48 | } 49 | }).$mount() 50 | } 51 | 52 | /** 53 | * Toaster returns breadstick instance 54 | */ 55 | class Breadstick { 56 | constructor () { 57 | createPortal() 58 | createBreadstick(this.bindNotify) 59 | } 60 | 61 | closeAll = () => { 62 | if (this.removeAll) { 63 | this.removeAll() 64 | } 65 | }; 66 | 67 | bindNotify = (fn, removeAll) => { 68 | this.createNotification = fn 69 | this.removeAll = removeAll 70 | }; 71 | 72 | /** 73 | * @description Calls notification method 74 | * @param {Function|String} Message object 75 | * @param {Object} options 76 | */ 77 | notify = (message, options = {}) => { 78 | if (this.createNotification) { 79 | this.createNotification(message, options) 80 | } 81 | }; 82 | } 83 | 84 | export default Breadstick 85 | -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | const MIXPANEL_TOKEN = 'dbca4e68375c43b7ed8b199eeffb1cdf' 2 | 3 | let mixpanelConfig = { 4 | token: MIXPANEL_TOKEN, 5 | debug: true 6 | } 7 | 8 | export default ({ router }) => { 9 | (function(c,a){if(!a.__SV){var b=window;try{var d,m,j,k=b.location,f=k.hash;d=function(a,b){return(m=a.match(RegExp(b+'=([^&]*)')))?m[1]:null};f&&d(f,'state')&&(j=JSON.parse(decodeURIComponent(d(f,'state'))),'mpeditor'===j.action&&(b.sessionStorage.setItem('_mpcehash',f),history.replaceState(j.desiredHash||'',c.title,k.pathname+k.search)))}catch(n){}var l,h;window.mixpanel=a;a._i=[];a.init=function(b,d,g){function c(b,i){var a=i.split('.');2==a.length&&(b=b[a[0]],i=a[1]);b[i]=function(){b.push([i].concat(Array.prototype.slice.call(arguments, 10 | 0)))}}var e=a;'undefined'!==typeof g?e=a[g]=[]:g='mixpanel';e.people=e.people||[];e.toString=function(b){var a='mixpanel';'mixpanel'!==g&&(a+='.'+g);b||(a+=' (stub)');return a};e.people.toString=function(){return e.toString(1)+'.people (stub)'};l='disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove'.split(' '); 11 | for(h=0;h { 17 | (process.env.NODE_ENV !== 'production') && console.log('Route changed to', to) 18 | (process.env.NODE_ENV === 'production') && mixpanel.track('Route Viewed', { 'page': to.path }) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import cjs from 'rollup-plugin-commonjs' 3 | import babel from 'rollup-plugin-babel' 4 | import scss from 'rollup-plugin-scss' 5 | import { terser } from 'rollup-plugin-terser' 6 | import pkg from './package.json' 7 | 8 | const name = 'Breadstick' 9 | const banner = `/*! 10 | * ${pkg.name} v${pkg.version} 11 | * (c) ${new Date().getFullYear()} Jonathan Bakebwa Mugaju 12 | * @license MIT 13 | */` 14 | 15 | const production = !process.env.ROLLUP_WATCH 16 | 17 | // Externals 18 | const externals = [ 19 | ...Object.keys(pkg.peerDependencies || {}) 20 | ] 21 | 22 | const commons = { 23 | external: externals, 24 | plugins: [ 25 | resolve(), 26 | babel({ 27 | exclude: 'node_modules/**', 28 | runtimeHelpers: true 29 | }), 30 | cjs({ 31 | include: /node_modules/ 32 | }), 33 | scss({ 34 | output: 'dist/breadstick.css' 35 | }), 36 | production && terser() 37 | ] 38 | } 39 | 40 | /** 41 | * Configurations 42 | */ 43 | export default [ 44 | { 45 | input: 'src/index.js', 46 | output: [ 47 | { 48 | banner, 49 | name, 50 | file: `dist/esm/index.js`, 51 | format: 'esm', 52 | exports: 'named', 53 | globals: { 54 | 'vue': 'Vue', 55 | 'animejs': 'anime' 56 | } 57 | } 58 | ], 59 | ...commons 60 | }, 61 | { 62 | input: 'src/index.js', 63 | output: [ 64 | { 65 | banner, 66 | name, 67 | file: `dist/es/index.js`, 68 | format: 'es', 69 | exports: 'named', 70 | globals: { 71 | 'vue': 'Vue', 72 | 'animejs': 'anime' 73 | } 74 | } 75 | ], 76 | ...commons 77 | }, 78 | { 79 | input: 'src/index.js', 80 | output: [ 81 | { 82 | banner, 83 | name, 84 | file: `dist/umd/index.js`, 85 | format: 'umd', 86 | exports: 'named', 87 | globals: { 88 | 'vue': 'Vue', 89 | 'animejs': 'anime' 90 | } 91 | } 92 | ], 93 | ...commons 94 | }, 95 | { 96 | input: 'src/index.js', 97 | output: [ 98 | { 99 | banner, 100 | name, 101 | file: `dist/cjs/index.js`, 102 | format: 'cjs', 103 | exports: 'named', 104 | globals: { 105 | 'vue': 'Vue', 106 | 'animejs': 'anime' 107 | } 108 | } 109 | ], 110 | ...commons 111 | } 112 | ] 113 | -------------------------------------------------------------------------------- /src/components/Message/index.js: -------------------------------------------------------------------------------- 1 | import { Alert } from '../Alert/' 2 | 3 | /** 4 | * Message component 5 | */ 6 | const Message = { 7 | name: 'Message', 8 | props: { 9 | id: { 10 | type: Number, 11 | default: null 12 | }, 13 | message: { 14 | type: [String, Function, Object], 15 | default: null 16 | }, 17 | position: { 18 | type: String 19 | }, 20 | requestClose: { 21 | type: Boolean, 22 | default: false 23 | }, 24 | duration: { 25 | type: Number, 26 | default: 5000 27 | } 28 | }, 29 | data () { 30 | return { 31 | timeout: undefined 32 | } 33 | }, 34 | mounted () { 35 | this.createTimeout() 36 | }, 37 | methods: { 38 | close () { 39 | this.$emit('remove', { id: this.id, position: this.position }) 40 | clearTimeout(this.timeout) 41 | }, 42 | onMouseEnter () { 43 | if (this.timeout) { 44 | clearTimeout(this.timeout) 45 | } 46 | }, 47 | onMouseLeave () { 48 | this.createTimeout() 49 | }, 50 | createTimeout () { 51 | this.timeout = setTimeout(this.close, this.duration) 52 | return this.timeout 53 | }, 54 | renderMessage (h) { 55 | // The returned message is a string 56 | if (typeof this.message === 'string') { 57 | return h(Alert, { 58 | attrs: { 59 | id: this.id 60 | }, 61 | props: { 62 | clear: this.onMouseEnter, 63 | reset: this.onMouseLeave, 64 | title: this.message, 65 | close: this.close 66 | } 67 | }, this.message) 68 | } 69 | 70 | // The returned message is a function with Vue's render function callback 71 | if (typeof this.message === 'function') { 72 | const message = this.message({ 73 | h, 74 | id: this.id, 75 | onClose: this.close 76 | }) 77 | return h('span', { 78 | on: { 79 | mouseenter: this.onMouseEnter, 80 | mouseleave: this.onMouseLeave 81 | } 82 | }, [message]) 83 | } 84 | 85 | // The returned message is a component VNode 86 | if (this.message.constructor && this.message.constructor.name === 'VNode') { 87 | return h('span', { 88 | on: { 89 | 'mouseenter': this.onMouseEnter, 90 | 'mouseleave': this.onMouseLeave 91 | } 92 | }, [this.message]) 93 | } 94 | return null 95 | } 96 | }, 97 | render (h) { 98 | return h('span', { 99 | }, [this.renderMessage(h)]) 100 | } 101 | } 102 | 103 | export default Message 104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "breadstick", 3 | "version": "0.2.15", 4 | "private": false, 5 | "sideEffects": false, 6 | "main": "dist/index.js", 7 | "module": "dist/es/index.js", 8 | "unpkg": "dist/umd/index.js", 9 | "jsdelivr": "dist/umd/index.js", 10 | "author": { 11 | "name": "Jonathan Bakebwa", 12 | "url": "https://github.com/codebender828" 13 | }, 14 | "license": "MIT", 15 | "keywords": [ 16 | "front-end", 17 | "vue", 18 | "notifications", 19 | "toast", 20 | "snackbar", 21 | "breadstick", 22 | "stacking", 23 | "position", 24 | "dismissible", 25 | "functional" 26 | ], 27 | "files": [ 28 | "dist", 29 | "src" 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/codebender828/breadstick" 34 | }, 35 | "scripts": { 36 | "dev": "watch 'yarn build:es' src", 37 | "build": "yarn build:cjs && yarn build:es", 38 | "build:cjs": "cross-env NODE_ENV=production BABEL_ENV=cjs babel --root-mode upward src -d dist --copy-files", 39 | "build:es": "cross-env NODE_ENV=production BABEL_ENV=es babel --root-mode upward src -d dist/es --copy-files", 40 | "docs:dev": "vuepress dev docs", 41 | "docs:build": "vuepress build docs", 42 | "test": "jest --passWithNoTests", 43 | "test:watch": "jest --passWithNoTests --watchAll", 44 | "lint": "vue-cli-service lint" 45 | }, 46 | "peerDependencies": { 47 | "animejs": "^3.1.0", 48 | "vue": "^2.6.10" 49 | }, 50 | "devDependencies": { 51 | "@babel/cli": "^7.8.3", 52 | "@babel/core": "^7.8.3", 53 | "@babel/plugin-proposal-class-properties": "^7.8.3", 54 | "@babel/plugin-syntax-jsx": "^7.8.3", 55 | "@babel/preset-env": "^7.8.3", 56 | "@commitlint/cli": "^8.2.0", 57 | "@commitlint/config-conventional": "^8.2.0", 58 | "@vue/cli-plugin-babel": "^4.0.0", 59 | "@vue/cli-plugin-eslint": "^4.0.0", 60 | "@vue/cli-plugin-unit-jest": "^4.0.0", 61 | "@vue/cli-service": "^4.0.0", 62 | "@vue/eslint-config-standard": "^4.0.0", 63 | "@vue/test-utils": "1.0.0-beta.29", 64 | "babel-eslint": "^10.0.3", 65 | "babel-jest": "^25.1.0", 66 | "babel-plugin-transform-class-properties": "^6.24.1", 67 | "breadstick": "^0.1.21", 68 | "core-js": "^3.3.2", 69 | "cross-env": "^6.0.3", 70 | "eslint": "^5.16.0", 71 | "eslint-plugin-vue": "^5.0.0", 72 | "husky": "^3.0.9", 73 | "jest": "^25.1.0", 74 | "lint-staged": "^9.4.2", 75 | "rollup": "^1.27.12", 76 | "rollup-plugin-babel": "^4.3.3", 77 | "rollup-plugin-commonjs": "^10.1.0", 78 | "rollup-plugin-node-resolve": "^5.2.0", 79 | "rollup-plugin-scss": "^1.0.2", 80 | "rollup-plugin-terser": "^5.1.3", 81 | "rollup-plugin-uglify": "^6.0.4", 82 | "vue": "^2.6.11", 83 | "vue-template-compiler": "^2.6.11", 84 | "vuepress": "^1.3.0", 85 | "watch": "^1.0.2" 86 | }, 87 | "publishConfig": { 88 | "registry": "https://registry.npmjs.org/" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at excellence@jbakebwa.dev. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/components/BreadstickManager/index.js: -------------------------------------------------------------------------------- 1 | import anime from 'animejs' 2 | import Message from '../Message' 3 | 4 | const positions = { 5 | top: [], 6 | 'top-left': [], 7 | 'top-right': [], 8 | 'bottom-left': [], 9 | bottom: [], 10 | 'bottom-right': [] 11 | } 12 | 13 | /** 14 | * @description Compute styles for specific position 15 | * @param {String} position 16 | * @returns {Object} Styles object 17 | */ 18 | const computeBreadstickStyle = (position) => { 19 | let style = { 20 | display: 'flex', 21 | flexDirection: 'column', 22 | alignItems: 'center' 23 | } 24 | 25 | if (position.includes('right')) { 26 | style.alignItems = 'flex-end' 27 | } else if (position.includes('left')) { 28 | style.alignItems = 'flex-start' 29 | } 30 | 31 | return style 32 | } 33 | 34 | /** 35 | * Breadstick Animations 36 | */ 37 | const animations = { 38 | enter: (el) => { 39 | var height = el.clientHeight 40 | return { 41 | height: [0, height], 42 | opacity: [0, 1], 43 | scale: [0.9, 1] 44 | } 45 | }, 46 | leave: { 47 | height: 0, 48 | opacity: [1, 0], 49 | scale: [1, 0.9] 50 | } 51 | } 52 | 53 | /** 54 | * Breadstick component 55 | */ 56 | const BreadstickManager = { 57 | name: 'BreadstickManager', 58 | data () { 59 | return { 60 | idCounter: 0, 61 | positions 62 | } 63 | }, 64 | props: { 65 | notify: Function, 66 | default: () => null 67 | }, 68 | created () { 69 | this.notify(this._notify, this.closeAll) 70 | }, 71 | methods: { 72 | /** 73 | * @description Creates toast state for single toast notification 74 | * @param {Object|Vue.Component|String} message 75 | * @param {Object} options 76 | * @returns {Object} Toast state object 77 | */ 78 | createToastState (message, options) { 79 | const id = ++this.idCounter 80 | // a bit messy, but object.position returns a number because 81 | // it's a method argument. 82 | const position = 83 | options.hasOwnProperty('position') && typeof options.position === 'string' 84 | ? options.position 85 | : 'top' 86 | 87 | return { 88 | id, 89 | message, 90 | position, 91 | showing: true, 92 | duration: 93 | typeof options.duration === 'undefined' ? 5000 : options.duration, 94 | onRequestRemove: () => this.removeToast(String(id), position), 95 | type: options.type 96 | } 97 | }, 98 | 99 | /** 100 | * @description Shows notification 101 | * @param {Object|Vue.Component|String} message 102 | * @param {Object} options 103 | */ 104 | _notify (message, options) { 105 | const toast = this.createToastState(message, options) 106 | const { position } = toast 107 | 108 | // prepend the toast for toasts positioned at the top of 109 | // the screen, otherwise append it. 110 | const isTop = position.includes('top') 111 | isTop ? this.positions[position].unshift(toast) 112 | : this.positions[position].push(toast) 113 | }, 114 | 115 | /** 116 | * @description Close all toast components 117 | */ 118 | closeAll () { 119 | Object.keys(this.positions).forEach((pos) => { 120 | const position = this.positions[pos] 121 | position.forEach((toast) => { 122 | this.removeToast(toast.id, pos) 123 | }) 124 | }) 125 | }, 126 | 127 | /** 128 | * @description Remove toast from position list 129 | * @param {String} id 130 | * @param {String} position 131 | */ 132 | removeToast (id, position) { 133 | this.positions[position] = this.positions[position].filter(toast => toast.id !== id) 134 | return this.positions[position] 135 | }, 136 | 137 | /** 138 | * @description Compute styles for toast component 139 | * @param {String} position 140 | */ 141 | getStyle (position) { 142 | let style = { 143 | width: 'fit-content', 144 | position: 'fixed', 145 | zIndex: 5500 146 | } 147 | 148 | if (position === 'top' || position === 'bottom') { 149 | style.margin = '0 auto' 150 | style.textAlign = 'center' 151 | } 152 | 153 | if (position.includes('top')) { 154 | style.top = 0 155 | } 156 | 157 | if (position.includes('bottom')) { 158 | style.bottom = 0 159 | } 160 | 161 | if (!position.includes('left')) { 162 | style.right = 0 163 | } 164 | 165 | if (!position.includes('right')) { 166 | style.left = 0 167 | } 168 | return style 169 | }, 170 | /** 171 | * @description Get animation for transition 172 | * @param {String} key Type of animation phase 173 | * @param {HTMLElement} el Element 174 | */ 175 | getAnimation (key, el) { 176 | const animation = animations[key] 177 | return typeof animation === 'function' 178 | ? animation.call(this, el) 179 | : animation 180 | }, 181 | /** 182 | * @description Calls enter animation 183 | * @param {{el: HTMLElement, complete: Function}} el 184 | */ 185 | enter (el, complete) { 186 | const animation = this.getAnimation('enter', el) 187 | anime({ 188 | targets: el, 189 | ...animation, 190 | complete, 191 | easing: 'spring(1, 100, 50, 0)' 192 | }) 193 | }, 194 | /** 195 | * @description Calls leave animation 196 | * @param {{el: HTMLElement, complete: Function}} el 197 | */ 198 | leave (el, complete) { 199 | let animation = this.getAnimation('leave', el) 200 | anime({ 201 | targets: el, 202 | ...animation, 203 | complete, 204 | easing: 'spring(1, 100, 70, 0)' 205 | }) 206 | } 207 | }, 208 | render (h) { 209 | return h('span', [ 210 | Object.keys(this.positions).map((position) => { 211 | const pos = position 212 | const toasts = this.positions[pos] 213 | return h('TransitionGroup', { 214 | style: this.getStyle(pos), 215 | props: { 216 | css: false 217 | }, 218 | on: { 219 | enter: this.enter, 220 | leave: this.leave 221 | }, 222 | key: position, 223 | class: ['Breadstick__manager-' + pos], 224 | ref: `BreadstickManager_${pos}` 225 | }, [ 226 | toasts.map((toast) => { 227 | return h(Message, { 228 | props: { 229 | position: pos, 230 | key: toast.id, 231 | message: toast.message, 232 | ...toast 233 | }, 234 | style: { 235 | ...computeBreadstickStyle(pos) 236 | }, 237 | on: { 238 | remove: ({ id, position }) => { 239 | this.removeToast(id, position) 240 | } 241 | }, 242 | key: toast.id 243 | }, this.$slots.default) 244 | }) 245 | ]) 246 | }) 247 | ]) 248 | } 249 | } 250 | 251 | export default BreadstickManager 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 🚀 A simple and flexible implementation of toast style notifications for Vue.js. 2 | 3 | 4 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 5 | 6 | 7 | 8 |
9 |

10 | 11 |

12 |

Breadstick

13 |

A simple and flexible implementation of toast style notifications for Vue.js.

14 |

🌟 Inspired by toasted-notes for React - which I think is really cool. ❤️

15 | 16 |

Demo

17 | 18 | ### 🌟 Features 19 | - **Functional Stacking, Positioning, Timing & Dismissal API.** Breadstick exposes a function that allows you to to control the stacking, positioning and dismissal of authored toast style notifications. 20 | - **Render whatever you want.** Utilize the render callback to create beautiful custom notifications. 21 | - **Functional default styles.** Import the provided css for some nice styling defaults or write your own styles. 22 | - **Vue framework-agnostic notification.** Breadstick is un-opininated about the styling of your Vue toast notifications. It's main strengths are in handling the business logic of stacking, positioning, timing & dismissal of your toast notifications. 23 | 24 | ## 📚 Table of Contents 25 | - [🥳 Breadstick Starters](#starters) 26 | - [⚡️ Installation](#installation) 27 | - [Installing with Nuxt](#installing-with-nuxt) 28 | - [🤖 Examples](#examples) 29 | - [🍊 Basic usage](#basic-usage) 30 | - [📚 Using different positions](#different-positions) 31 | - [🏠 Using custom element](#custom-notifications) 32 | - [📭 Close all notifications](#close-all) 33 | - [🏗 Advanced usage](#advanced) 34 | - [🌮 With Vue's `render` function callback](#render-callback) 35 | - [🚚 With JSX](#with-jsx) 36 | - [💼 API](#api) 37 | - [`notify(String|VNode|Function, options)`](#notify) 38 | - [Options](#options) 39 | - [`closeAll()`](#close-all) 40 | - [🔖 TODO:](#todo) 41 | - [🤝 Contributing](#contributing) 42 | - [❤️ Support this project](#support) 43 | - [Contributors ✨](#contributors) 44 | 45 | 46 | ### 🥳 Breadstick Starters 47 | Here are a few Codesandbox starters you can use to get started with Breadstick in your Vue or Nuxt App. 48 | - **Vue.js Starter**: http://bit.ly/breadstick-vue 49 | - **Nuxt.js Starter**: http://bit.ly/breadstick-nuxt 50 | 51 | 52 | ### ⚡️ Installation 53 | Install `breadstick` and its peer dependency, `animejs`, using yarn or npm. 54 | 55 | ```bash 56 | npm install breadstick animejs --save 57 | ``` 58 | 59 | You can then register `BreadstickBakery` as a Vue plugin. 60 | ```js 61 | import Vue from 'vue' 62 | import { BreadstickBakery } from 'breadstick' 63 | 64 | // This exposes `this.$breadstick` in your Vue template. 65 | Vue.use(BreadstickBakery) 66 | ``` 67 | 68 | #### Installing with Nuxt 69 | After installing Breadstick, we import it and register it as a **client-side plugin**. This is because Breadstick it makes use of some DOM APIs. Code is similiar to the Vue plugin shown above. 70 | 71 | 72 | 73 | 74 | ### 🤖 Examples 75 | Breadstick can be used to render different types of notifications out of the box. You can render simple string notifications as well as custom styled notifications. This makes it really convenient. 76 | 77 | 78 | 79 | #### 🍊 Basic usage 80 | Invoke a notification using the `notify` method to display a message to the user. 81 | Breadstick defaults the notification duration to 5 seconds in the `top` position. 82 | 83 | ```js 84 | this.$breadstick.notify('🥞 Show me the pancakes') 85 | ``` 86 | 87 | 88 | #### 📚 Using different positions 89 | You can display notifications in different positions, including top-left, top, top-right, bottom-left, bottom, and bottom-right. 90 | 91 | ```js 92 | [ 93 | 'top-left', 94 | 'top', 95 | 'top-right', 96 | 'bottom-left', 97 | 'bottom', 98 | 'bottom-right' 99 | ].forEach(position => { 100 | this.$breadstick.notify("Using position " + position, { 101 | position 102 | }) 103 | }) 104 | ``` 105 | 106 | 107 | 108 | #### 🏠 Using custom element 109 | With JSX or Vue's render function, breadstick is able to render a custom element or Vue component 110 | 111 | ```jsx 112 | this.$breadstick.notify( 113 |
I am a custom HTML element
114 | ) 115 | ``` 116 | 117 | 118 | 119 | #### 📭 Close all notifications 120 | You can clear all notifications by calling breadstick's `closeAll` method 121 | 122 | ```jsx 123 | this.$breadstick.closeAll() 124 | ``` 125 | 126 | 127 | 128 | ### 🏗 Advanced usage 129 | Whereas breadstick shines in making simple notifications for your Vue app, it's real strength is shown in allowing you to create custom notifications through it's render function callback. 130 | 131 | This is particularly useful if you want use custom themed elements or Vue components inside of your toast notification. In the following snippet, we render a custom `Alert` component to display a toast notification. 132 | 133 | This is particularly useful for building your own themed notification component library. 134 | 135 | [Here is an advanced examples](https://github.com/codebender828/breadstick-examples) of how to use breadstick to render you own authored toast notifications. 136 | 137 | 138 | 139 | #### 🌮 With Vue's `render` function callback 140 | Breadstick exposes Vue's `createElement` function in the render callback that you can use to render your own components in a toast notification. This can be useful in a context where Vue's `this` context may not be available. 141 | 142 | In a Vue component, you can even use that component's `this.$createElement` to render your own element/component and return it in the render function callback so breadstick can display it. 143 | 144 | ```js 145 | // Import your custom `Alert` component and render it in breadstick 146 | import Alert from './components/Alert' 147 | 148 | export default { 149 | mounted () { 150 | this.$breadstick.notify(({ h, onClose }) => { 151 | return h(Alert, { 152 | on: { 153 | click: onClose 154 | } 155 | }, 'A render function Alert notification') 156 | }) 157 | } 158 | } 159 | ``` 160 | 161 | 162 | 163 | #### 🚚 With JSX 164 | You can also use JSX if you like :). 165 | ```jsx 166 | // Import your custom `Alert` component and render it in breadstick 167 | import Alert from './components/Alert' 168 | 169 | export default { 170 | mounted () { 171 | breadstick.notify(({ onClose }) => { 172 | return ( 173 | 174 | An JSX Alert notification 175 | 176 | ) 177 | } 178 | } 179 | } 180 | ``` 181 | 182 | 183 | 184 | ## 💼 API 185 | 186 | 187 | 188 | ### `notify(String|VNode|Function, options)` 189 | - **Arguments** 190 | - `{ String | VNode | Function }` Message 191 | - `{ Object }` options 192 | 193 | Breadstick's `notify` method accepts two parameters. The first parameter can be a `String`, `VNode` (Object), or `Function` and the second is the options object. 194 | 195 | If a string is passed in the first argument, breadstick will render a notificiation with the string in the top center position with it's default internal component. 196 | ```js 197 | this.$breadstick.notify('Simple notification.') 198 | ``` 199 | 200 | If a `VNode` is passed, Breadstick treats it like a rendered component and renders it instead. 201 | ```jsx 202 | this.$breadstick.notify( 203 |
I am a custom HTML element
204 | ) 205 | ``` 206 | 207 | If a callback `Function` is passed in the first argument, it will expose an object with two parameters: `h` and the `onClose` which are both functions. Using a render callback allows you to tap into the close function. It's your best option if you want to completely re-style your toast notification 208 | 209 | ```js 210 | this.$breadstick.notify(({ h, onClose }) => { 211 | return h('div', 'My custom notification') 212 | }) 213 | ``` 214 | 215 | 216 | #### Options 217 | Option | Type | Default | Values 218 | --- | --- | --- | -- 219 | `position` | `String` | `top` | `top`, `right`, `bottom`, `left`, `top-left`, `top-right`, `bottom-right`, `bottom-left` 220 | `duration` | `Number` | 5000 | Any number in milliseconds 221 | 222 | 223 | 224 | ### `closeAll()` 225 | - Type: `Function` 226 | The `closeAll` method closes all toast notifications that are visible in the UI at the time of invocation. Nice a succinct way to dismiss all notifications 227 | 228 | 229 | 230 | ## 🔖 TODO: 231 | Breadstick still has a few more features coming up. These include: 232 | - [ ] Indefinitely display toast notification 233 | - [ ] Allow sharing of same application Vue instance. 234 | 235 | 236 | 237 | ## 🤝 Contributing 238 | Here's our contribution [guide.](./.github/CONTRIBUTING.md) 239 | 240 | 241 | 242 | ## ❤️ Support this project 243 | If you like this project, please consider supporting it by buying my a coffee! 244 | 245 | 246 | Buy me a coffee 247 | 248 | 249 |
250 |
251 | Made with ❤️ by Jonathan Bakebwa 🇺🇬 252 |
253 | 254 | 255 | ## Contributors ✨ 256 | 257 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 |

Jonathan Bakebwa

💻 📖

Omereshone Kelvin Oghenerhoro

📖
268 | 269 | 270 | 271 | 272 | 273 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 274 | --------------------------------------------------------------------------------