This commit is contained in:
2025-07-02 21:55:07 +09:00
commit fa63330e69
855 changed files with 432271 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
const libName = "awn"
const prefix = {
popup: `${libName}-popup`,
toast: `${libName}-toast`,
btn: `${libName}-btn`,
confirm: `${libName}-confirm`
}
// Constants for toasts
export const tConsts = {
prefix: prefix.toast,
klass: {
label: `${prefix.toast}-label`,
content: `${prefix.toast}-content`,
icon: `${prefix.toast}-icon`,
progressBar: `${prefix.toast}-progress-bar`,
progressBarPause: `${prefix.toast}-progress-bar-paused`
},
ids: {
container: `${prefix.toast}-container`
}
}
// Constants for popups
export const mConsts = {
prefix: prefix.popup,
klass: {
buttons: `${libName}-buttons`,
button: prefix.btn,
successBtn: `${prefix.btn}-success`,
cancelBtn: `${prefix.btn}-cancel`,
title: `${prefix.popup}-title`,
body: `${prefix.popup}-body`,
content: `${prefix.popup}-content`,
dotAnimation: `${prefix.popup}-loading-dots`
},
ids: {
wrapper: `${prefix.popup}-wrapper`,
confirmOk: `${prefix.confirm}-ok`,
confirmCancel: `${prefix.confirm}-cancel`
}
}
export const eConsts = {
klass: {
hiding: `${libName}-hiding`
},
lib: libName
}

View File

@@ -0,0 +1,75 @@
import {
eConsts
} from "./constants.js"
import {global} from '../../js/module/variable.js'
export default class {
constructor(parent, id, klass, style, options) {
this.newNode = document.createElement('div')
if (id) this.newNode.id = id
if (klass) this.newNode.className = klass
if (style) this.newNode.style.cssText = style
this.parent = parent
this.options = options
}
beforeInsert() {}
afterInsert() {}
insert() {
this.beforeInsert()
this.el = this.parent.appendChild(this.newNode)
this.afterInsert()
return this
}
replace(el) {
if (!this.getElement()) return
return this.beforeDelete().then(() => {
this.updateType(el.type)
this.parent.replaceChild(el.newNode, this.el)
this.el = this.getElement(el.newNode)
this.afterInsert()
return this
})
}
beforeDelete(el = this.el) {
let timeLeft = 0
if (this.start) {
timeLeft = this.options.minDurations[this.type] + this.start - Date.now()
if (timeLeft < 0) timeLeft = 0
}
return new Promise(resolve => {
setTimeout(() => {
el.classList.add(eConsts.klass.hiding)
setTimeout(resolve, this.options.animationDuration)
}, timeLeft)
})
}
delete(el = this.el) {
if (!this.getElement(el)) return null
return this.beforeDelete(el).then(() => {
el.remove()
this.afterDelete()
})
}
afterDelete() {}
getElement(el = this.el) {
if (!el) return null
return global.shadowRoot.getElementById(el.id)
}
addEvent(name, func) {
this.el.addEventListener(name, func)
}
toggleClass(klass) {
this.el.classList.toggle(klass)
}
updateType(type) {
this.type = type
this.duration = this.options.duration(this.type)
}
}

View File

@@ -0,0 +1,107 @@
import Options from "./options.js"
import Toast from "./toast.js"
import Popup from "./popup.js"
import Elem from "./elem.js"
import {global} from '../../js/module/variable.js'
import {
tConsts
} from "./constants.js"
export default class Notifier {
constructor(options = {}) {
this.options = new Options(options)
}
tip(msg, options) {
return this._addToast(msg, "tip", options).el
}
info(msg, options) {
return this._addToast(msg, "info", options).el
}
success(msg, options) {
return this._addToast(msg, "success", options).el
}
warning(msg, options) {
return this._addToast(msg, "warning", options).el
}
alert(msg, options) {
return this._addToast(msg, "alert", options).el
}
async (promise, onResolve, onReject, msg, options) {
let asyncToast = this._addToast(msg, "async", options)
return this._afterAsync(promise, onResolve, onReject, options, asyncToast)
}
confirm(msg, onOk, onCancel, options) {
return this._addPopup(msg, "confirm", options, onOk, onCancel)
}
asyncBlock(promise, onResolve, onReject, msg, options) {
let asyncBlock = this._addPopup(msg, "async-block", options)
return this._afterAsync(promise, onResolve, onReject, options, asyncBlock)
}
modal(msg, className, options) {
return this._addPopup(msg, className, options)
}
closeToasts() {
let c = this.container
while (c.firstChild) {
c.removeChild(c.firstChild)
}
}
// Tools
_addPopup(msg, className, options, onOk, onCancel) {
return new Popup(msg, className, this.options.override(options), onOk, onCancel)
}
_addToast(msg, type, options, old) {
options = this.options.override(options)
let newToast = new Toast(msg, type, options, this.container)
if (old) {
if (old instanceof Popup) return old.delete().then(() => newToast.insert())
let i = old.replace(newToast)
return i
}
return newToast.insert()
}
_afterAsync(promise, onResolve, onReject, options, oldElement) {
return promise.then(
this._responseHandler(onResolve, "success", options, oldElement),
this._responseHandler(onReject, "alert", options, oldElement)
)
}
_responseHandler(payload, toastName, options, oldElement) {
return result => {
switch (typeof payload) {
case 'undefined':
case 'string':
let msg = toastName === 'alert' ? payload || result : payload
this._addToast(msg, toastName, options, oldElement)
break
default:
oldElement.delete().then(() => {
if (payload) payload(result)
})
}
}
}
_createContainer() {
return new Elem(global.shadowRoot, tConsts.ids.container, `awn-${this.options.position}`).insert().el
}
get container() {
return global.shadowRoot.getElementById(tConsts.ids.container) || this._createContainer()
}
}

View File

@@ -0,0 +1 @@
module.exports = require("./index.js").default

View File

@@ -0,0 +1,129 @@
const defaults = {
maxNotifications: 10,
animationDuration: 300,
position: "bottom-right",
labels: {
tip: "Tip",
info: "Info",
success: "Success",
warning: "Attention",
alert: "Error",
async: "Loading",
confirm: "Confirmation required",
confirmOk: "OK",
confirmCancel: "Cancel"
},
icons: {
tip: "question-circle",
info: "info-circle",
success: "check-circle",
warning: "exclamation-circle",
alert: "exclamation-triangle",
async: "cog fa-spin",
confirm: "exclamation-triangle",
prefix: "<i class='fa fas fa-fw fa-",
suffix: "'></i>",
enabled: true
},
replacements: {
tip: null,
info: null,
success: null,
warning: null,
alert: null,
async: null,
"async-block": null,
modal: null,
confirm: null,
general: {
"<script>": "",
"</script>": ""
}
},
messages: {
tip: "",
info: "",
success: "Action has been succeeded",
warning: "",
alert: "Action has been failed",
confirm: "This action can't be undone. Continue?",
async: "Please, wait...",
"async-block": "Loading"
},
formatError(err) {
if (err.response) {
if (!err.response.data) return '500 API Server Error'
if (err.response.data.errors) {
return err.response.data.errors.map(o => o.detail).join('<br>')
}
if (err.response.statusText) {
return `${err.response.status} ${err.response.statusText}: ${err.response.data}`
}
}
if (err.message) return err.message
return err
},
durations: {
global: 5000,
success: null,
info: null,
tip: null,
warning: null,
alert: null
},
minDurations: {
async: 1000,
"async-block": 1000
},
}
export default class Options {
constructor(options = {}, global = defaults) {
Object.assign(this, this.defaultsDeep(global, options))
}
icon(type) {
if (this.icons.enabled) return `${this.icons.prefix}${this.icons[type]}${this.icons.suffix}`
return ''
}
label(type) {
return this.labels[type]
}
duration(type) {
let duration = this.durations[type]
return duration === null ? this.durations.global : duration
}
toSecs(value) {
return `${value / 1000}s`
}
applyReplacements(str, type) {
if (!str) return this.messages[type] || ""
for (const n of ['general', type]) {
if (!this.replacements[n]) continue
for (const k in this.replacements[n]) {
str = str.replace(k, this.replacements[n][k])
}
}
return str
}
override(options) {
if (options) return new Options(options, this)
return this
}
defaultsDeep(defaults, overrides) {
let result = {}
for (const k in defaults) {
if (overrides.hasOwnProperty(k)) {
result[k] = typeof defaults[k] === "object" && defaults[k] !== null ? this.defaultsDeep(defaults[k], overrides[k]) : overrides[k]
} else {
result[k] = defaults[k]
}
}
return result
}
}

View File

@@ -0,0 +1,86 @@
import Elem from "./elem.js"
import {
mConsts
} from "./constants.js"
import {global} from '../../js/module/variable.js'
export default class extends Elem {
constructor(msg, type = 'modal', options, onOk, onCancel) {
let animationDuration = `animation-duration: ${options.toSecs(options.animationDuration)};`
super(document.body, mConsts.ids.wrapper, null, animationDuration, options)
this[mConsts.ids.confirmOk] = onOk
this[mConsts.ids.confirmCancel] = onCancel
this.className = `${mConsts.prefix}-${type}`
if (!['confirm', 'async-block', 'modal'].includes(type)) type = 'modal'
this.updateType(type)
this.setInnerHtml(msg)
this.insert()
}
setInnerHtml(html) {
let innerHTML = this.options.applyReplacements(html, this.type)
switch (this.type) {
case "confirm":
let buttons = [`<button class='${mConsts.klass.button} ${mConsts.klass.successBtn}'id='${mConsts.ids.confirmOk}'>${this.options.labels.confirmOk}</button>`]
if (this[mConsts.ids.confirmCancel] !== false) {
buttons.push(`<button class='${mConsts.klass.button} ${mConsts.klass.cancelBtn}'id='${mConsts.ids.confirmCancel}'>${this.options.labels.confirmCancel}</button>`)
}
innerHTML = `${this.options.icon(this.type)}<div class='${mConsts.klass.title}'>${this.options.label(this.type)}</div><div class="${mConsts.klass.content}">${innerHTML}</div><div class='${mConsts.klass.buttons} ${mConsts.klass.buttons}-${buttons.length}'>${buttons.join('')}</div>`
break
case "async-block":
innerHTML = `${innerHTML}<div class="${mConsts.klass.dotAnimation}"></div>`
}
this.newNode.innerHTML = `<div class="${mConsts.klass.body} ${this.className}">${innerHTML}</div>`
}
keyupListener(e) {
if (this.type === 'async-block') return e.preventDefault()
switch (e.code) {
case 'Escape':
e.preventDefault()
this.delete()
case 'Tab':
e.preventDefault()
if (this.type !== 'confirm' || this[mConsts.ids.confirmCancel] === false) return true
let next = this.okBtn
if (e.shiftKey) {
if (document.activeElement.id == mConsts.ids.confirmOk) next = this.cancelBtn
} else if (document.activeElement.id !== mConsts.ids.confirmCancel) next = this.cancelBtn
next.focus()
}
}
afterInsert() {
this.listener = e => this.keyupListener(e)
window.addEventListener("keydown", this.listener)
switch (this.type) {
case 'async-block':
this.start = Date.now()
break
case 'confirm':
this.okBtn.focus()
this.addEvent("click", e => {
if (e.target.nodeName !== "BUTTON") return false
this.delete()
if (this[e.target.id]) this[e.target.id]()
})
break
default:
document.activeElement.blur()
this.addEvent("click", e => {
if (e.target.id === this.newNode.id) this.delete()
})
}
}
afterDelete() {
window.removeEventListener("keydown", this.listener)
}
get okBtn() {
return document.getElementById(mConsts.ids.confirmOk)
}
get cancelBtn() {
return document.getElementById(mConsts.ids.confirmCancel)
}
}

View File

@@ -0,0 +1,374 @@
@keyframes awn-fade-in {
0% {
opacity: 0
}
to {
opacity: 1
}
}
@keyframes awn-fade-out {
0% {
opacity: 1
}
to {
opacity: 0
}
}
@keyframes awn-slide-right {
0% {
left: 100%;
opacity: 0
}
to {
left: 0;
opacity: 1
}
}
@keyframes awn-slide-left {
0% {
opacity: 0;
right: 100%
}
to {
opacity: 1;
right: 0
}
}
@keyframes awn-bar {
0% {
right: 100%
}
to {
right: 0
}
}
.awn-popup-loading-dots, .awn-popup-loading-dots:after, .awn-popup-loading-dots:before {
animation-fill-mode: both;
animation: awn-loading-dots 1s ease-in-out infinite;
background: #fff;
border-radius: 50%;
height: 6px;
width: 6px
}
.awn-popup-loading-dots {
animation-delay: -.16s;
color: #fff;
display: inline-block;
margin-left: 24px;
position: relative
}
.awn-popup-loading-dots:after, .awn-popup-loading-dots:before {
content: "";
position: absolute;
top: 0
}
.awn-popup-loading-dots:before {
animation-delay: -.32s;
left: -16px
}
.awn-popup-loading-dots:after {
left: 16px
}
@keyframes awn-loading-dots {
0%, 80%, to {
box-shadow: 0 0 0 0
}
40% {
box-shadow: 0 0 0 2px
}
}
#awn-popup-wrapper {
align-items: center;
animation-fill-mode: both;
animation-name: awn-fade-in;
animation-timing-function: ease-out;
background: rgba(0, 0, 0, .7);
bottom: 0;
display: flex;
justify-content: center;
left: 0;
opacity: 0;
position: fixed;
right: 0;
top: 0;
z-index: 99999
}
#awn-popup-wrapper.awn-hiding {
animation-name: awn-fade-out
}
#awn-popup-wrapper .awn-popup-body {
background: #fff;
border-radius: 6px;
font-size: 14px;
max-width: 500px;
min-width: 320px;
padding: 24px;
position: relative;
word-break: break-word
}
#awn-popup-wrapper .awn-popup-body.awn-popup-confirm {
align-items: center;
display: flex;
flex-direction: column
}
#awn-popup-wrapper .awn-popup-body.awn-popup-confirm .fa {
color: #c26700;
font-size: 44px
}
#awn-popup-wrapper .awn-popup-body.awn-popup-async-block {
background: transparent;
color: #fff;
font-size: 32px;
font-weight: 700;
text-align: center
}
#awn-popup-wrapper .awn-popup-title {
font-size: 14px;
font-weight: 700;
margin-top: 8px;
text-transform: uppercase
}
#awn-popup-wrapper .awn-buttons {
display: flex;
justify-content: space-between;
margin-top: 24px;
width: 100%
}
#awn-popup-wrapper .awn-buttons .awn-btn {
border: 0;
border-radius: 4px;
color: #fff;
font-size: 14px;
font-weight: 700;
line-height: 32px;
transition: background .2s linear;
width: 45%
}
#awn-popup-wrapper .awn-buttons-1 .awn-btn {
width: 100%
}
#awn-popup-wrapper .awn-buttons .awn-btn-success {
background: #40871d
}
#awn-popup-wrapper .awn-buttons .awn-btn-success:hover {
background: #367218
}
#awn-popup-wrapper .awn-buttons .awn-btn-cancel {
background: #1c76a6
}
#awn-popup-wrapper .awn-buttons .awn-btn-cancel:hover {
background: #186690
}
#awn-toast-container {
bottom: 24px;
box-sizing: border-box;
position: fixed;
right: 24px;
z-index: 99998
}
#awn-toast-container.awn-top-left, #awn-toast-container.awn-top-right {
bottom: auto;
top: 24px
}
#awn-toast-container.awn-top-left .awn-toast:first-child, #awn-toast-container.awn-top-right .awn-toast:first-child {
margin-top: 16px
}
#awn-toast-container.awn-bottom-left, #awn-toast-container.awn-top-left {
left: 24px;
right: auto
}
#awn-toast-container.awn-bottom-left .awn-toast, #awn-toast-container.awn-top-left .awn-toast {
animation-name: awn-slide-left;
right: 100%
}
#awn-toast-container.awn-bottom-left .awn-toast.awn-hiding, #awn-toast-container.awn-top-left .awn-toast.awn-hiding {
right: 0
}
#awn-toast-container.awn-bottom-right .awn-toast, #awn-toast-container.awn-top-right .awn-toast {
animation-name: awn-slide-right;
left: 100%
}
#awn-toast-container.awn-bottom-right .awn-toast.awn-hiding, #awn-toast-container.awn-top-right .awn-toast.awn-hiding {
left: 0
}
.awn-toast {
animation-fill-mode: both;
animation-timing-function: linear;
background: #ebebeb;
border-radius: 6px;
color: gray;
cursor: pointer;
font-size: 14px;
margin-top: 16px;
opacity: 0;
overflow: hidden;
position: relative;
width: 320px
}
.awn-toast-content {
word-break: break-word
}
.awn-toast-label {
color: gray;
display: block;
font-size: 18px;
text-transform: uppercase
}
.awn-toast-icon {
align-items: center;
bottom: 0;
display: flex;
justify-content: flex-end;
position: absolute;
right: 16px;
top: 6px
}
.awn-toast-icon .fa {
color: gray;
font-size: 44px
}
.awn-toast-wrapper {
border: 2px solid #d1d1d1;
border-radius: 6px;
padding: 22px 88px 16px 16px
}
.awn-toast-progress-bar {
height: 6px;
left: 0;
position: absolute;
right: 0;
top: 0
}
.awn-toast-progress-bar:after {
animation-duration: inherit;
animation-fill-mode: both;
animation-name: awn-bar;
animation-timing-function: linear;
background: gray;
content: " ";
height: 6px;
position: absolute;
right: 100%;
top: 0;
width: 100%
}
.awn-toast.awn-toast-progress-bar-paused .awn-toast-progress-bar:after {
animation-play-state: paused
}
.awn-toast.awn-hiding {
animation-name: awn-fade-out !important
}
.awn-toast.awn-toast-success {
background: #dff8d3;
color: #40871d
}
.awn-toast.awn-toast-success .awn-toast-wrapper {
border-color: #a7d590
}
.awn-toast.awn-toast-success .fa, .awn-toast.awn-toast-success b {
color: #40871d
}
.awn-toast.awn-toast-success .awn-toast-progress-bar:after {
background: #40871d
}
.awn-toast.awn-toast-info {
background: #d3ebf8;
color: #1c76a6
}
.awn-toast.awn-toast-info .awn-toast-wrapper {
border-color: #9fd3ef
}
.awn-toast.awn-toast-info .fa, .awn-toast.awn-toast-info b {
color: #1c76a6
}
.awn-toast.awn-toast-info .awn-toast-progress-bar:after {
background: #1c76a6
}
.awn-toast.awn-toast-alert {
background: #f8d5d3;
color: #a92019
}
.awn-toast.awn-toast-alert .awn-toast-wrapper {
border-color: #f0a29d
}
.awn-toast.awn-toast-alert .fa, .awn-toast.awn-toast-alert b {
color: #a92019
}
.awn-toast.awn-toast-alert .awn-toast-progress-bar:after {
background: #a92019
}
.awn-toast.awn-toast-warning {
background: #ffe7cc;
color: #c26700
}
.awn-toast.awn-toast-warning .awn-toast-wrapper {
border-color: #ffc480
}
.awn-toast.awn-toast-warning .fa, .awn-toast.awn-toast-warning b {
color: #c26700
}
.awn-toast.awn-toast-warning .awn-toast-progress-bar:after {
background: #c26700
}
[class^=awn-] {
box-sizing: border-box
}

View File

@@ -0,0 +1,101 @@
@keyframes awn-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes awn-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes awn-slide-right {
from {
opacity: 0;
left: 100%;
}
to {
opacity: 1;
left: 0;
}
}
@keyframes awn-slide-left {
from {
opacity: 0;
right: 100%;
}
to {
opacity: 1;
right: 0;
}
}
@keyframes awn-bar {
from {
right: 100%;
}
to {
right: 0;
}
}
.awn-popup-loading-dots,
.awn-popup-loading-dots:before,
.awn-popup-loading-dots:after {
border-radius: 50%;
width: 6px;
height: 6px;
animation-fill-mode: both;
background: #fff;
animation: awn-loading-dots 1s infinite ease-in-out;
}
.awn-popup-loading-dots {
position: relative;
margin-left: 24px;
display: inline-block;
color: #fff;
animation-delay: -0.16s;
}
.awn-popup-loading-dots:before,
.awn-popup-loading-dots:after {
content: "";
position: absolute;
top: 0;
}
.awn-popup-loading-dots:before {
left: -16px;
animation-delay: -0.32s;
}
.awn-popup-loading-dots:after {
left: 16px;
}
@keyframes awn-loading-dots {
0%,
80%,
100% {
box-shadow: 0 0 0 0;
}
40% {
box-shadow: 0 0 0 2px;
}
}

View File

@@ -0,0 +1,97 @@
#awn-popup-wrapper {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
background: $awn-popup-wrapper-bg;
z-index: $awn-popup-wrapper-z-index;
opacity: 0;
animation-name: $awn-popup-show-animation;
animation-timing-function: $awn-popup-animation-timing;
animation-fill-mode: both;
&.awn-hiding {
animation-name: $awn-popup-hide-animation;
}
.awn-popup-body {
position: relative;
border-radius: $awn-popup-border-radius;
word-break: break-word;
background: $awn-popup-bg;
padding: $awn-popup-padding;
min-width: $awn-popup-min-width;
font-size: $awn-popup-font-size;
max-width: $awn-popup-max-width;
&.awn-popup-confirm {
display: flex;
flex-direction: column;
align-items: center;
.fa {
font-size: $awn-popup-icon-size;
color: $awn-warning-color;
}
}
&.awn-popup-async-block {
background: transparent;
font-size: 32px;
font-weight: bold;
color: #fff;
text-align: center;
}
}
.awn-popup-title {
font-size: $awn-popup-font-size;
font-weight: bold;
text-transform: uppercase;
margin-top: 8px;
}
.awn-buttons {
width: 100%;
display: flex;
justify-content: space-between;
margin-top: $awn-popup-padding;
.awn-btn {
border-radius: $awn-popup-btn-border-radius;
border: 0;
font-weight: bold;
transition: background 0.2s linear;
font-size: $awn-popup-font-size;
width: 45%;
line-height: $awn-popup-btn-height;
color: $awn-popup-btn-color;
}
&-1 {
.awn-btn {
width: 100%;
}
}
.awn-btn-success {
background: $awn-success-color;
&:hover {
background: darken($awn-success-color, 5%);
}
}
.awn-btn-cancel {
background: $awn-info-color;
&:hover {
background: darken($awn-info-color, 5%);
}
}
}
}

View File

@@ -0,0 +1,8 @@
@import "variables";
@import "animations";
@import "popups";
@import "toasts";
[class^="awn-"] {
box-sizing: border-box;
}

View File

@@ -0,0 +1,194 @@
#awn-toast-container {
position: fixed;
z-index: $awn-container-z-index;
bottom: $awn-container-padding;
right: $awn-container-padding;
box-sizing: border-box;
&.awn-top-left,
&.awn-top-right {
top: $awn-container-padding;
bottom: auto;
.awn-toast:first-child {
margin-top: $awn-toast-margin;
}
}
&.awn-top-left,
&.awn-bottom-left {
left: $awn-container-padding;
right: auto;
.awn-toast {
right: 100%;
animation-name: $awn-toast-left-show-animation;
&.awn-hiding {
right: 0;
}
}
}
&.awn-top-right,
&.awn-bottom-right {
.awn-toast {
left: 100%;
animation-name: $awn-toast-right-show-animation;
&.awn-hiding {
left: 0;
}
}
}
}
.awn-toast {
position: relative;
cursor: pointer;
overflow: hidden;
opacity: 0;
width: $awn-toast-width;
background: $awn-primary-background;
margin-top: $awn-toast-margin;
border-radius: $awn-toast-border-radius;
color: $awn-primary-color;
font-size: $awn-toast-font-size;
animation-timing-function: $awn-toast-animation-timing;
animation-fill-mode: both;
&-content {
word-break: break-word;
}
&-label {
display: block;
text-transform: uppercase;
color: $awn-primary-color;
font-size: $awn-toast-title-font-size;
}
&-icon {
position: absolute;
right: $awn-toast-padding;
top: $awn-progress-bar-height;
bottom: 0;
display: flex;
align-items: center;
justify-content: flex-end;
.fa {
font-size: $awn-toast-icon-size;
color: $awn-primary-color;
}
}
&-wrapper {
padding: $awn-progress-bar-height + $awn-toast-padding 88px $awn-toast-padding $awn-toast-padding;
border: $awn-border;
border-radius: $awn-toast-border-radius;
}
&-progress-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: $awn-progress-bar-height;
&:after {
content: " ";
background: $awn-primary-color;
position: absolute;
width: 100%;
right: 100%;
top: 0;
height: $awn-progress-bar-height;
animation-name: awn-bar;
animation-duration: inherit;
animation-timing-function: linear;
animation-fill-mode: both;
}
}
&.awn-toast-progress-bar-paused .awn-toast-progress-bar:after {
animation-play-state: paused;
}
&.awn-hiding {
animation-name: $awn-toast-hide-animation !important;
}
&.awn-toast-success {
background: $awn-success-background;
color: $awn-success-color;
.awn-toast-wrapper {
border-color: $awn-success-border-color;
}
b,
.fa {
color: $awn-success-color;
}
.awn-toast-progress-bar:after {
background: $awn-success-color;
}
}
&.awn-toast-info {
background: $awn-info-background;
color: $awn-info-color;
.awn-toast-wrapper {
border-color: $awn-info-border-color;
}
b,
.fa {
color: $awn-info-color;
}
.awn-toast-progress-bar:after {
background: $awn-info-color;
}
}
&.awn-toast-alert {
background: $awn-alert-background;
color: $awn-alert-color;
.awn-toast-wrapper {
border-color: $awn-alert-border-color;
}
b,
.fa {
color: $awn-alert-color;
}
.awn-toast-progress-bar:after {
background: $awn-alert-color;
}
}
&.awn-toast-warning {
background: $awn-warning-background;
color: $awn-warning-color;
.awn-toast-wrapper {
border-color: $awn-warning-border-color;
}
b,
.fa {
color: $awn-warning-color;
}
.awn-toast-progress-bar:after {
background: $awn-warning-color;
}
}
}

View File

@@ -0,0 +1,63 @@
// Container
$awn-container-z-index: 99998 !default;
$awn-container-padding: 24px !default;
// Colors
$awn-primary-color: hsl(0, 0%, 50%) !default;
$awn-primary-background: hsl(0, 0%, 92%) !default;
$awn-primary-border-color: hsl(0, 0%, 82%) !default;
$awn-success-color: hsl(100, 65%, 32%) !default;
$awn-success-background: hsl(100, 74%, 90%) !default;
$awn-success-border-color: hsl(100, 45%, 70%) !default;
$awn-info-color: hsl(201, 71%, 38%) !default;
$awn-info-background: hsl(201, 71%, 90%) !default;
$awn-info-border-color: hsl(201, 71%, 78%) !default;
$awn-alert-color: hsl(3, 74%, 38%) !default;
$awn-alert-background: hsl(3, 74%, 90%) !default;
$awn-alert-border-color: hsl(3, 74%, 78%) !default;
$awn-warning-color: hsl(32, 100%, 38%) !default;
$awn-warning-background: hsl(32, 100%, 90%) !default;
$awn-warning-border-color: hsl(32, 100%, 75%) !default;
// Notifications
$awn-toast-width: 320px !default;
$awn-toast-padding: 16px !default;
$awn-toast-margin: 16px !default;
$awn-toast-border-width: 2px !default;
$awn-toast-border-style: solid !default;
$awn-toast-border-color: $awn-primary-border-color !default;
$awn-toast-border-radius: 6px !default;
$awn-border: $awn-toast-border-width $awn-toast-border-style
$awn-toast-border-color !default;
$awn-progress-bar-height: 6px !default;
$awn-toast-font-size: 14px !default;
$awn-toast-title-font-size: 18px !default;
$awn-toast-icon-size: 44px !default;
// Popups
$awn-popup-wrapper-bg: rgba(0, 0, 0, 0.7) !default;
$awn-popup-wrapper-z-index: 99999 !default;
$awn-popup-bg: #fff !default;
$awn-popup-min-width: 320px !default;
$awn-popup-max-width: 500px !default;
$awn-popup-font-size: 14px !default;
$awn-popup-icon-size: 44px !default;
$awn-popup-padding: 24px !default;
$awn-popup-border-radius: 6px !default;
$awn-popup-btn-height: 32px !default;
$awn-popup-btn-color: #fff !default;
$awn-popup-btn-border-radius: 4px !default;
// Animations
$awn-popup-show-animation: awn-fade-in;
$awn-popup-hide-animation: awn-fade-out;
$awn-popup-animation-timing: ease-out;
$awn-toast-left-show-animation: awn-slide-left;
$awn-toast-right-show-animation: awn-slide-right;
$awn-toast-hide-animation: awn-fade-out;
$awn-toast-animation-timing: linear;

View File

@@ -0,0 +1,25 @@
export default class {
constructor(callback, delay) {
this.callback = callback
this.remaining = delay
this.resume()
}
pause() {
this.paused = true
window.clearTimeout(this.timerId)
this.remaining -= new Date() - this.start
}
resume() {
this.paused = false
this.start = new Date()
window.clearTimeout(this.timerId)
this.timerId = window.setTimeout(() => {
window.clearTimeout(this.timerId)
this.callback()
}, this.remaining)
}
toggle() {
if (this.paused) this.resume()
else this.pause()
}
}

View File

@@ -0,0 +1,61 @@
import Elem from "./elem.js"
import Timer from "./timer.js"
import {
tConsts,
eConsts
} from "./constants.js"
export default class extends Elem {
constructor(msg, type, options, parent) {
super(
parent,
`${tConsts.prefix}-${Math.floor(Date.now() - Math.random() * 100)}`,
`${tConsts.prefix} ${tConsts.prefix}-${type}`,
`animation-duration: ${options.toSecs(options.animationDuration)};`,
options
)
this.updateType(type)
this.setInnerHtml(msg)
}
setInnerHtml(html) {
if (this.type === 'alert' && html) html = this.options.formatError(html)
html = this.options.applyReplacements(html, this.type)
this.newNode.innerHTML = `<div class="awn-toast-wrapper">${this.progressBar}${this.label}<div class="${tConsts.klass.content}">${html}</div><span class="${tConsts.klass.icon}">${this.options.icon(this.type)}</span></div>`
}
beforeInsert() {
if (this.parent.childElementCount >= this.options.maxNotifications) {
let elements = Array.from(this.parent.getElementsByClassName(tConsts.prefix))
this.delete(elements.find(e => !this.isDeleted(e)))
}
}
afterInsert() {
if (this.type == "async") return this.start = Date.now()
this.addEvent("click", () => this.delete())
if (this.duration <= 0) return
this.timer = new Timer(() => this.delete(), this.duration)
for (const e of ["mouseenter", "mouseleave"]) {
this.addEvent(e, () => {
if (this.isDeleted()) return
this.toggleClass(tConsts.klass.progressBarPause)
this.timer.toggle()
})
}
}
isDeleted(el = this.el) {
return el.classList.contains(eConsts.klass.hiding)
}
get progressBar() {
if (this.duration <= 0 || this.type === 'async') return ""
return `<div class='${tConsts.klass.progressBar}' style="animation-duration:${this.options.toSecs(this.duration)};"></div>`
}
get label() {
return `<b class="${tConsts.klass.label}">${this.options.label(this.type)}</b>`
}
}