From 403f8cda76ed98c469fe52fceb7843fd44501e78 Mon Sep 17 00:00:00 2001 From: JP Rodrigues <70jprodrigues@gmail.com> Date: Tue, 20 Oct 2020 13:35:47 -0300 Subject: [PATCH] Add first version --- .gitignore | 59 ++++++++ README.md | 97 +++++++++++++ package.json | 29 ++++ src/Toaster.vue | 180 ++++++++++++++++++++++++ src/api.js | 36 +++++ src/defaults/positions.js | 27 ++++ src/helpers/event-bus.js | 31 ++++ src/helpers/mount-component.js | 27 ++++ src/helpers/remove-element.js | 9 ++ src/helpers/timer.js | 25 ++++ src/index.js | 14 ++ src/themes/default/animations.styl | 44 ++++++ src/themes/default/colors.styl | 3 + src/themes/default/index.styl | 5 + src/themes/default/toast-container.styl | 20 +++ src/themes/default/toast.styl | 22 +++ src/themes/default/variables.styl | 7 + 17 files changed, 635 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package.json create mode 100644 src/Toaster.vue create mode 100644 src/api.js create mode 100644 src/defaults/positions.js create mode 100644 src/helpers/event-bus.js create mode 100644 src/helpers/mount-component.js create mode 100644 src/helpers/remove-element.js create mode 100644 src/helpers/timer.js create mode 100644 src/index.js create mode 100644 src/themes/default/animations.styl create mode 100644 src/themes/default/colors.styl create mode 100644 src/themes/default/index.styl create mode 100644 src/themes/default/toast-container.styl create mode 100644 src/themes/default/toast.styl create mode 100644 src/themes/default/variables.styl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec7a786 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# http://git-scm.com/docs/gitignore + +# different IDEs +.idea +.project +*.sublime-* +.brackets.json +.vscode + +# logs and cache +/logs/ +*.log +npm-debug.log* +.sass-cache +.cache +.php_cs.cache + +# OS generated files +[Tt]humbs.db +ehthumbs.db +*~ +.*~ +._* +*.bak +*.save +*.swp + +# Recycle bin folder used by different os +.Trash-* +$RECYCLE.BIN/ + +# Windows shortcuts +*.lnk + +# Folder config file +[Dd]esktop.ini +*.DS_Store +.DS_Store? + +# node packages +node_modules/ + +# Composer, exclude on root vendor folder +/vendor/ +composer.phar + +# Security tokens +*.pem +*.pub +*.crt +*.key + +# Project related +*.tgz +/dist/ +/demo/ +/docs/ +/tmp/ +/coverage/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb658a8 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# Vue Toaster + +Vue.js toast notification plugin for vue 3 + +## Installation +```bash +# yarn +yarn add @meforma/vue-toaster + +# npm +npm install @meforma/vue-toaster +``` + +## Import +```js +// In you main.js +// ... considering that your app creation is here +import Toaster from '@meforma/vue-toaster'; + +createApp(App).use(Toaster).mount('#app') +``` + +## Usage +```js +// this.$toast.show(message, {/* options */}); +this.$toast.show('You did it!'); +this.$toast.success('You did it!'); +this.$toast.error('You did it!'); +this.$toast.warning('You did it!'); +this.$toast.info('You did it!'); + +// Close all opened toast after 3000ms +setTimeout(this.$toast.clear, 3000) + +``` + +## Available options +The API methods accepts these options: + +| Attribute | Type | Default | Description | +| :--- | :---: | :---: | :--- | +| message | String | -- | Message text/html (required) | +| type | String | `default` | One of `success`, `info`, `warning`, `error`, `default` | +| position | String | `bottom-right` | One of `top`, `bottom`, `top-right`, `bottom-right`,`top-left`, `bottom-left` | +| duration | Number | `4000` | Visibility duration in milliseconds | +| dismissible | Boolean | `true` | Allow user close by clicking | +| onClick | Function | -- | Do something when user clicks | +| onClose | Function | -- | Do something after toast gets dismissed | +| queue | Boolean | `false` | Wait for existing to close before showing new | +| pauseOnHover | Boolean | `true` | Pause the timer when mouse on over a toast | +| useDefaultCss | Boolean | `true` | User default css styles | + +## API methods +### `show(message, ?options)` +This is generic method, you can use this method to make any kind of toast. +```js +// Can accept a message as string and apply rest of options from defaults +this.$toast.show('Howdy!'); + +// Can accept an Object of options. +// If yout don't pass options, the default toast will be showed +this.$toast.show('Something went wrong!', { + type: 'error', + // all of other options may go here +}); +``` + +### `success(message,?options)` +There are some proxy methods to make it more readable. The same rule for `error`, `info` and `warning` methods +```js +this.$toast.success('Profile saved.', { + // optional options Object +}) +``` + +## Global options +You can set options for all the instances during plugin initialization +```js +app.use(Toaster, { + // One of the options + position: 'top' +}) +``` + +Further you can override option when creating new instances +```js +this.$toast.success('Order placed.', { + // override the global option + position: 'bottom' +}) +``` + +## Based on +* [vue-toast-notification](https://github.com/ankurk91/vue-toast-notification) plugin + +## License +[MIT](LICENSE.txt) License diff --git a/package.json b/package.json new file mode 100644 index 0000000..37e2f97 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "@meforma/vue-toaster", + "version": "1.0.0", + "description": "Vue.js toaster notification", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/MeForma/vue-toaster.git" + }, + "keywords": [ + "toast", + "toaster", + "notification", + "vue", + "vue", + "3", + "toastify", + "toaster" + ], + "author": "@jprodrigues70", + "license": "MIT", + "bugs": { + "url": "https://github.com/MeForma/vue-toaster/issues" + }, + "homepage": "https://github.com/MeForma/vue-toaster#readme" +} diff --git a/src/Toaster.vue b/src/Toaster.vue new file mode 100644 index 0000000..c54c317 --- /dev/null +++ b/src/Toaster.vue @@ -0,0 +1,180 @@ + + + diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..ca7ea2e --- /dev/null +++ b/src/api.js @@ -0,0 +1,36 @@ +import Toaster from './Toaster.vue' +import eventBus from './helpers/event-bus.js' +import mount from './helpers/mount-component' + +const Api = (globalOptions = {}) => { + return { + show(message, options = {}) { + let localOptions = { message, ...options } + const c = mount(Toaster, { + props: { ...globalOptions, ...localOptions } + }) + return c + }, + clear() { + eventBus.$emit('toast-clear') + }, + success(message, options = {}) { + options.type = 'success' + return this.show(message, options) + }, + error(message, options = {}) { + options.type = 'error' + return this.show(message, options) + }, + info(message, options = {}) { + options.type = 'info' + return this.show(message, options) + }, + warning(message, options = {}) { + options.type = 'warning' + return this.show(message, options) + } + } +} + +export default Api diff --git a/src/defaults/positions.js b/src/defaults/positions.js new file mode 100644 index 0000000..a898bef --- /dev/null +++ b/src/defaults/positions.js @@ -0,0 +1,27 @@ +const POSITIONS = { + TOP_RIGHT: 'top-right', + TOP: 'top', + TOP_LEFT: 'top-left', + BOTTOM_RIGHT: 'bottom-right', + BOTTOM: 'bottom', + BOTTOM_LEFT: 'bottom-left' +} +export default Object.freeze(POSITIONS) + +export function definePosition(position, top, bottom) { + let result = null + switch (position) { + case POSITIONS.TOP: + case POSITIONS.TOP_RIGHT: + case POSITIONS.TOP_LEFT: + result = top + break + + case POSITIONS.BOTTOM: + case POSITIONS.BOTTOM_RIGHT: + case POSITIONS.BOTTOM_LEFT: + result = bottom + break + } + return result +} diff --git a/src/helpers/event-bus.js b/src/helpers/event-bus.js new file mode 100644 index 0000000..c8d911a --- /dev/null +++ b/src/helpers/event-bus.js @@ -0,0 +1,31 @@ +class Event { + constructor() { + this.queue = {} + } + + $on(name, callback) { + this.queue[name] = this.queue[name] || [] + this.queue[name].push(callback) + } + + $off(name, callback) { + if (this.queue[name]) { + for (var i = 0; i < this.queue[name].length; i++) { + if (this.queue[name][i] === callback) { + this.queue[name].splice(i, 1) + break + } + } + } + } + + $emit(name, data) { + if (this.queue[name]) { + this.queue[name].forEach(function (callback) { + callback(data) + }) + } + } +} + +export default new Event() diff --git a/src/helpers/mount-component.js b/src/helpers/mount-component.js new file mode 100644 index 0000000..18711e6 --- /dev/null +++ b/src/helpers/mount-component.js @@ -0,0 +1,27 @@ +import { render, h } from 'vue' + +const createElement = () => + typeof document !== 'undefined' && document.createElement('div') + +const mount = (component, { props, children, element, app } = {}) => { + let el = element ? element : createElement() + + let vNode = h(component, props, children) + if (app?._context) { + vNode.appContext = app._context + } + + render(vNode, el) + + const destroy = () => { + if (el) { + render(null, el) + } + el = null + vNode = null + } + + return { vNode, destroy, el } +} + +export default mount diff --git a/src/helpers/remove-element.js b/src/helpers/remove-element.js new file mode 100644 index 0000000..d9a3264 --- /dev/null +++ b/src/helpers/remove-element.js @@ -0,0 +1,9 @@ +const removeElement = (el) => { + if (typeof el.remove !== 'undefined') { + el.remove() + } else { + el.parentNode.removeChild(el) + } +} + +export { removeElement } diff --git a/src/helpers/timer.js b/src/helpers/timer.js new file mode 100644 index 0000000..89296a6 --- /dev/null +++ b/src/helpers/timer.js @@ -0,0 +1,25 @@ +// https://stackoverflow.com/a/3969760 +export default class Timer { + constructor(callback, delay) { + this.startedAt = Date.now() + this.callback = callback + this.delay = delay + + this.timer = setTimeout(callback, delay) + } + + pause() { + this.stop() + this.delay -= Date.now() - this.startedAt + } + + resume() { + this.stop() + this.startedAt = Date.now() + this.timer = setTimeout(this.callback, this.delay) + } + + stop() { + clearTimeout(this.timer) + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..5793e12 --- /dev/null +++ b/src/index.js @@ -0,0 +1,14 @@ +import Toaster from './Toaster.vue' +import Api from './api.js' +import Positions from './defaults/positions.js' + +const Plugin = (app, options = {}) => { + let methods = Api(options) + app.$toast = methods + app.config.globalProperties.$toast = methods +} + +Toaster.install = Plugin + +export default Toaster +export { Toaster, Positions } diff --git a/src/themes/default/animations.styl b/src/themes/default/animations.styl new file mode 100644 index 0000000..ea751bb --- /dev/null +++ b/src/themes/default/animations.styl @@ -0,0 +1,44 @@ +// Animations are taken from animate.css +// https://daneden.github.io/animate.css + +@keyframes fadeOut + from + opacity 1 + to + opacity 0 + +.fadeOut + animation-name fadeOut + +@keyframes fadeInDown + from + opacity .5 + transform translate3d(0, -100%, 0) + to + opacity 1 + transform none + +.fadeInDown + animation-name fadeInDown + +@keyframes fadeInUp + from + opacity .5 + transform translate3d(0, 100%, 0) + to + opacity 1 + transform none + +.fadeInUp + animation-name fadeInUp + +// VUE + +.fade-enter-active + transition opacity 300ms ease-in +.fade-leave-active + transition opacity 150ms ease-out + +.fade-enter, +.fade-leave-to + opacity 0 diff --git a/src/themes/default/colors.styl b/src/themes/default/colors.styl new file mode 100644 index 0000000..5030da2 --- /dev/null +++ b/src/themes/default/colors.styl @@ -0,0 +1,3 @@ +for key, value in $toast-colors + .c-toast--{key} + background-color value diff --git a/src/themes/default/index.styl b/src/themes/default/index.styl new file mode 100644 index 0000000..5a6515c --- /dev/null +++ b/src/themes/default/index.styl @@ -0,0 +1,5 @@ +@import './variables'; +@import './colors'; +@import './animations'; +@import './toast-container'; +@import './toast'; diff --git a/src/themes/default/toast-container.styl b/src/themes/default/toast-container.styl new file mode 100644 index 0000000..08c140f --- /dev/null +++ b/src/themes/default/toast-container.styl @@ -0,0 +1,20 @@ +.c-toast-container + position fixed + display flex + top 0 + bottom 0 + left 0 + right 0 + padding 2em + overflow hidden + z-index 9999 + pointer-events none + + &--top + flex-direction column + &--bottom + flex-direction column-reverse + + @media screen and (max-width 768px) + padding 0 + position fixed !important diff --git a/src/themes/default/toast.styl b/src/themes/default/toast.styl new file mode 100644 index 0000000..846ce70 --- /dev/null +++ b/src/themes/default/toast.styl @@ -0,0 +1,22 @@ +.c-toast + display grid + align-items center + animation-duration 150ms + margin 0.5em 0 + border-radius 0.5em + pointer-events auto + color #fff + min-height 3em + cursor pointer + font-family: Avenir, Helvetica, Arial, sans-serif; + padding 0.5em 2em + word-break break-word + + &--top, &--bottom + align-self center + + &--top-right, &--bottom-right + align-self flex-end + + &--top-left, &--bottom-left + align-self flex-start diff --git a/src/themes/default/variables.styl b/src/themes/default/variables.styl new file mode 100644 index 0000000..19740c6 --- /dev/null +++ b/src/themes/default/variables.styl @@ -0,0 +1,7 @@ +$toast-colors = { + "success": #28a745, + "info": #17a2b8, + "warning": #ffc107, + "error": #dc3545, + "default": #343a40 +}