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
+}