commit
1cb67ddba8
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
@ -0,0 +1,18 @@
|
||||
import commonjs from '@rollup/plugin-commonjs'; // Convert CommonJS modules to ES6
|
||||
import vue from 'rollup-plugin-vue'; // Handle .vue SFC files
|
||||
import buble from '@rollup/plugin-buble'; // Transpile/polyfill with reasonable browser support
|
||||
export default {
|
||||
input: 'src/main.js', // Path relative to package.json
|
||||
output: {
|
||||
name: 'MeshkeeToast',
|
||||
exports: 'named',
|
||||
},
|
||||
plugins: [
|
||||
commonjs(),
|
||||
vue({
|
||||
css: true, // Dynamically inject css as a <style> tag
|
||||
compileTemplate: true, // Explicitly convert template to render function
|
||||
}),
|
||||
buble(), // Transpile to ES5
|
||||
],
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "meshkeetoast",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/MeshkeeToast.umd.js",
|
||||
"style": "dist/theme-bootstrap.css",
|
||||
"module": "dist/MeshkeeToast.esm.js",
|
||||
"unpkg": "dist/MeshkeeToast.min.js",
|
||||
"browser": {
|
||||
"./sfc": "src/MeshkeeToast.vue"
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"build": "npm run build:umd & npm run build:es & npm run build:unpkg",
|
||||
"postbuild": "cp -r src/theme dist/",
|
||||
"build:umd": "rollup --config build/rollup.config.js --format umd --file dist/MeshkeeToast.umd.js",
|
||||
"build:es": "rollup --config build/rollup.config.js --format es --file dist/MeshkeeToast.esm.js",
|
||||
"build:unpkg": "rollup --config build/rollup.config.js --format iife --file dist/MeshkeeToast.min.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-buble": "^0.21.3",
|
||||
"@rollup/plugin-commonjs": "^11.1.0",
|
||||
"rollup": "^1.17.0",
|
||||
"rollup-plugin-vue": "^5.0.1",
|
||||
"vue": "^2.6.10",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"mitt": "^3.0.1",
|
||||
"sass": "^1.72.0",
|
||||
"sass-loader": "^14.1.1",
|
||||
"style-loader": "^3.3.4",
|
||||
"svg-url-loader": "^8.0.0",
|
||||
"vue-loader": "^17.4.2"
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<transition :enter-active-class="transition.enter" :leave-active-class="transition.leave">
|
||||
<div ref="root" role="alert" v-show="isActive" class="v-toast__item"
|
||||
:class="[`v-toast__item--${type}`, `v-toast__item--${position}`]" @mouseover="toggleTimer(true)"
|
||||
@mouseleave="toggleTimer(false)" @click="whenClicked">
|
||||
<div class="v-toast__icon"></div>
|
||||
<p class="v-toast__text" v-html="message"></p>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, h } from 'vue';
|
||||
import { removeElement } from './helpers.js';
|
||||
import Timer from "./timer.js";
|
||||
import Positions from './positions.js'
|
||||
import eventBus from './bus.js'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Toast',
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: Positions.BOTTOM_RIGHT,
|
||||
validator(value) {
|
||||
return Object.values(Positions).includes(value)
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 3000
|
||||
},
|
||||
dismissible: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
onDismiss: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
queue: Boolean,
|
||||
pauseOnHover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
parentTop: null,
|
||||
parentBottom: null,
|
||||
isHovered: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
correctParent() {
|
||||
switch (this.position) {
|
||||
case Positions.TOP:
|
||||
case Positions.TOP_RIGHT:
|
||||
case Positions.TOP_LEFT:
|
||||
return this.parentTop;
|
||||
|
||||
case Positions.BOTTOM:
|
||||
case Positions.BOTTOM_RIGHT:
|
||||
case Positions.BOTTOM_LEFT:
|
||||
return this.parentBottom;
|
||||
}
|
||||
},
|
||||
transition() {
|
||||
switch (this.position) {
|
||||
case Positions.TOP:
|
||||
case Positions.TOP_RIGHT:
|
||||
case Positions.TOP_LEFT:
|
||||
return {
|
||||
enter: 'v-toast--fade-in-down',
|
||||
leave: 'v-toast--fade-out'
|
||||
};
|
||||
|
||||
case Positions.BOTTOM:
|
||||
case Positions.BOTTOM_RIGHT:
|
||||
case Positions.BOTTOM_LEFT:
|
||||
return {
|
||||
enter: 'v-toast--fade-in-up',
|
||||
leave: 'v-toast--fade-out'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setupContainer() {
|
||||
this.parentTop = document.querySelector('.v-toast.v-toast--top');
|
||||
this.parentBottom = document.querySelector('.v-toast.v-toast--bottom');
|
||||
// No need to create them, they already exists
|
||||
if (this.parentTop && this.parentBottom) return;
|
||||
|
||||
if (!this.parentTop) {
|
||||
this.parentTop = document.createElement('div');
|
||||
this.parentTop.className = 'v-toast v-toast--top';
|
||||
}
|
||||
|
||||
if (!this.parentBottom) {
|
||||
this.parentBottom = document.createElement('div');
|
||||
this.parentBottom.className = 'v-toast v-toast--bottom'
|
||||
}
|
||||
|
||||
const container = document.body;
|
||||
container.appendChild(this.parentTop);
|
||||
container.appendChild(this.parentBottom);
|
||||
},
|
||||
|
||||
shouldQueue() {
|
||||
if (!this.queue) return false;
|
||||
|
||||
return (
|
||||
this.parentTop.childElementCount > 0 ||
|
||||
this.parentBottom.childElementCount > 0
|
||||
)
|
||||
},
|
||||
|
||||
dismiss() {
|
||||
if (this.timer) this.timer.stop();
|
||||
clearTimeout(this.queueTimer);
|
||||
this.isActive = false;
|
||||
|
||||
// Timeout for the animation complete before destroying
|
||||
setTimeout(() => {
|
||||
this.onDismiss.apply(null, arguments);
|
||||
|
||||
const wrapper = this.$refs.root;
|
||||
// unmount the component
|
||||
removeElement(wrapper)
|
||||
}, 150)
|
||||
},
|
||||
|
||||
showNotice() {
|
||||
if (this.shouldQueue()) {
|
||||
// Call recursively if it should queue
|
||||
this.queueTimer = setTimeout(this.showNotice, 250);
|
||||
return
|
||||
}
|
||||
console.log(this.correctParent);
|
||||
this.correctParent.insertAdjacentElement('afterbegin', this.$refs.root);
|
||||
|
||||
this.$options.elem = this.correctParent;
|
||||
|
||||
this.isActive = true;
|
||||
|
||||
if (this.duration) {
|
||||
this.timer = new Timer(this.dismiss, this.duration);
|
||||
}
|
||||
},
|
||||
|
||||
whenClicked() {
|
||||
if (!this.dismissible) return;
|
||||
this.onClick.apply(null, arguments);
|
||||
this.dismiss()
|
||||
},
|
||||
|
||||
toggleTimer(newVal) {
|
||||
if (!this.pauseOnHover || !this.timer) return;
|
||||
newVal ? this.timer.pause() : this.timer.resume();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
eventBus.off('toast-clear', this.dismiss)
|
||||
},
|
||||
beforeMount() {
|
||||
this.setupContainer()
|
||||
},
|
||||
mounted() {
|
||||
this.showNotice();
|
||||
eventBus.on('toast-clear', this.dismiss)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import './theme/bootstrap';
|
||||
</style>
|
@ -0,0 +1,55 @@
|
||||
import MeshkeeToast from './MeshkeeToast.vue'
|
||||
import { createComponent } from './helpers';
|
||||
import eventBus from './bus.js';
|
||||
|
||||
export const useToast = (globalProps = {}) => {
|
||||
return {
|
||||
open(options) {
|
||||
let message = null;
|
||||
if (typeof options === 'string') message = options;
|
||||
|
||||
const defaultProps = {
|
||||
message
|
||||
};
|
||||
|
||||
const propsData = Object.assign({}, defaultProps, globalProps, options);
|
||||
const instance = createComponent(MeshkeeToast, propsData, document.body);
|
||||
return {
|
||||
dismiss: instance.dismiss
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
eventBus.emit('toast-clear')
|
||||
},
|
||||
success(message, options = {}) {
|
||||
return this.open(Object.assign({}, {
|
||||
message,
|
||||
type: 'success'
|
||||
}, options))
|
||||
},
|
||||
error(message, options = {}) {
|
||||
return this.open(Object.assign({}, {
|
||||
message,
|
||||
type: 'error'
|
||||
}, options))
|
||||
},
|
||||
info(message, options = {}) {
|
||||
return this.open(Object.assign({}, {
|
||||
message,
|
||||
type: 'info'
|
||||
}, options))
|
||||
},
|
||||
warning(message, options = {}) {
|
||||
return this.open(Object.assign({}, {
|
||||
message,
|
||||
type: 'warning'
|
||||
}, options))
|
||||
},
|
||||
default(message, options = {}) {
|
||||
return this.open(Object.assign({}, {
|
||||
message,
|
||||
type: 'default'
|
||||
}, options))
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
import mitt from 'mitt'
|
||||
const eventBus = mitt();
|
||||
export default eventBus;
|
@ -0,0 +1,49 @@
|
||||
import Vue from 'vue';
|
||||
export const removeElement = (el) => {
|
||||
console.log(el);
|
||||
if (typeof el.remove !== 'undefined') {
|
||||
el.remove()
|
||||
} else {
|
||||
if (el.parentNode) el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
|
||||
export const createComponent = (component, props, parentContainer, slots = {}) => {
|
||||
// const vNode = h(component, props, slots)
|
||||
// const container = document.createElement('div');
|
||||
// console.log(parentContainer, container);
|
||||
// container.classList.add('v-toast--pending')
|
||||
// parentContainer.appendChild(container);
|
||||
// h(vNode, container);
|
||||
|
||||
// return vNode.component
|
||||
// return Vue.component('MeshkeeToast', {
|
||||
// render: (createElement) => {
|
||||
// return createElement(
|
||||
// 'div',
|
||||
// { class: { 'v-toast--pending': true } },
|
||||
// MeshkeeToast
|
||||
// )
|
||||
// }
|
||||
// });
|
||||
const toastTpl = Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
return h(
|
||||
component,
|
||||
{
|
||||
class: ['lx-toast', 'lx-toast-lx-word-wrap'],
|
||||
style: this.extStyle,
|
||||
props
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
let toastVM = new toastTpl();
|
||||
const tpl = toastVM.$mount().$el;
|
||||
parentContainer.appendChild(tpl);
|
||||
return toastVM.$children[0];
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
// Import vue component
|
||||
import Vue from 'vue';
|
||||
import MeshkeeToast from './MeshkeeToast.vue';
|
||||
import { useToast } from './api';
|
||||
|
||||
// Vue.prototype.$meshkeeToast = function () {
|
||||
// console.log(324);
|
||||
// return 321;
|
||||
// }
|
||||
// // Declare install function executed by Vue.use()
|
||||
// export function install(Vue) {
|
||||
// console.log(43);
|
||||
|
||||
// if (install.installed) return;
|
||||
// install.installed = true;
|
||||
// Vue.prototype.$meshkeeToast = function () {
|
||||
// console.log(324);
|
||||
// return 321;
|
||||
// }
|
||||
// Vue.component('MeshkeeToast', MeshkeeToast);
|
||||
// }
|
||||
|
||||
// // Create module definition for Vue.use()
|
||||
// const plugin = {
|
||||
// install,
|
||||
// };
|
||||
|
||||
// // Auto-install when vue is found (eg. in browser via <script> tag)
|
||||
// let GlobalVue = null;
|
||||
// if (typeof window !== 'undefined') {
|
||||
// GlobalVue = window.Vue;
|
||||
// } else if (typeof global !== 'undefined') {
|
||||
// GlobalVue = global.Vue;
|
||||
// }
|
||||
// console.log(global, GlobalVue);
|
||||
// if (GlobalVue) {
|
||||
// GlobalVue.use(plugin);
|
||||
// }
|
||||
|
||||
// // To allow use as module (npm/webpack/etc.) export component
|
||||
// export default MeshkeeToast;
|
||||
export default {
|
||||
install(Vue, options) {
|
||||
// Let's register our component globally
|
||||
// https://vuejs.org/v2/guide/components-registration.html
|
||||
Vue.prototype.$meshkeeToast = function () {
|
||||
const { open } = useToast(options);
|
||||
open('Vue');
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
export default Object.freeze({
|
||||
TOP_RIGHT: 'top-right',
|
||||
TOP: 'top',
|
||||
TOP_LEFT: 'top-left',
|
||||
BOTTOM_RIGHT: 'bottom-right',
|
||||
BOTTOM: 'bottom',
|
||||
BOTTOM_LEFT: 'bottom-left',
|
||||
})
|
@ -0,0 +1,59 @@
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.v-toast--fade-out {
|
||||
animation-name: fadeOut;
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.v-toast--fade-in-down {
|
||||
animation-name: fadeInDown;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.v-toast--fade-in-up {
|
||||
animation-name: fadeInUp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue Transitions
|
||||
*/
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 150ms ease-out;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
@ -0,0 +1 @@
|
||||
$toast-icons-path: "../sugar/icons" !default;
|
@ -0,0 +1,5 @@
|
||||
@import '../default/variables';
|
||||
@import 'variables';
|
||||
@import '../animations';
|
||||
@import '../default/main';
|
||||
@import '../sugar/icons';
|
@ -0,0 +1,78 @@
|
||||
.v-toast {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 2em;
|
||||
overflow: hidden;
|
||||
z-index: 1090;
|
||||
pointer-events: none;
|
||||
|
||||
&__item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
animation-duration: 150ms;
|
||||
margin: 0.5em 0;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
||||
border-radius: 0.25em;
|
||||
pointer-events: auto;
|
||||
opacity: 0.92;
|
||||
color: #fff;
|
||||
min-height: 3em;
|
||||
cursor: pointer;
|
||||
|
||||
// Colors
|
||||
@each $color, $value in $toast-colors {
|
||||
&--#{$color} {
|
||||
background-color: $value;
|
||||
}
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: #000
|
||||
}
|
||||
|
||||
// Individual toast position
|
||||
&.v-toast__item--top, &.v-toast__item--bottom {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
&.v-toast__item--top-right, &.v-toast__item--bottom-right {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
&.v-toast__item--top-left, &.v-toast__item--bottom-left {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
margin: 0;
|
||||
padding: 0.5em 1em;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Notice container positions
|
||||
&.v-toast--top {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.v-toast--bottom {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
&.v-toast--custom-parent {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
padding: 0;
|
||||
position: fixed !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
$toast-colors: (
|
||||
) !default;
|
||||
$toast-colors: map-merge(("success": #28a745,
|
||||
"info": #17a2b8,
|
||||
"warning": #ffc107,
|
||||
"error": #dc3545,
|
||||
"default": #343a40),
|
||||
$toast-colors
|
||||
);
|
@ -0,0 +1,3 @@
|
||||
@import 'variables';
|
||||
@import '../animations';
|
||||
@import 'main';
|
@ -0,0 +1,12 @@
|
||||
$toast-icons-path: "./icons" !default;
|
||||
$toast-colors: () !default;
|
||||
$toast-colors: map-merge(
|
||||
(
|
||||
"success": #47d78a,
|
||||
"info": #1c85d5,
|
||||
"warning": #febc22,
|
||||
"error": #f7471c,
|
||||
"default": #343a40
|
||||
),
|
||||
$toast-colors
|
||||
);
|
@ -0,0 +1,36 @@
|
||||
.v-toast {
|
||||
&__item {
|
||||
opacity: 1;
|
||||
min-height: 4em;
|
||||
|
||||
.v-toast__text {
|
||||
padding: 1.5em 1em;
|
||||
}
|
||||
|
||||
.v-toast__icon {
|
||||
display: block;
|
||||
width: 27px;
|
||||
min-width: 27px;
|
||||
height: 27px;
|
||||
margin-left: 1em;
|
||||
// background: url(#{$toast-icons-path}/info.svg) no-repeat;
|
||||
|
||||
[dir="rtl"] & {
|
||||
margin-left: unset;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&.v-toast__item--success .v-toast__icon {
|
||||
// background: url(#{$toast-icons-path}/success.svg) no-repeat;
|
||||
}
|
||||
|
||||
&.v-toast__item--error .v-toast__icon {
|
||||
// background: url(#{$toast-icons-path}/error.svg) no-repeat;
|
||||
}
|
||||
|
||||
&.v-toast__item--warning .v-toast__icon {
|
||||
// background: url(#{$toast-icons-path}/warning.svg) no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 466 B |
After Width: | Height: | Size: 453 B |
After Width: | Height: | Size: 308 B |
After Width: | Height: | Size: 589 B |
@ -0,0 +1,4 @@
|
||||
@import 'variables';
|
||||
@import '../animations';
|
||||
@import '../default/main';
|
||||
@import 'icons';
|
@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Reference in new issue