Changed notification message position ( #86696p66d )

This commit is contained in:
Cüneyt Şentürk 2023-05-23 17:44:28 +03:00
parent 0863da593b
commit 4d7dfa181a
10 changed files with 399 additions and 299 deletions

8
public/css/app.css vendored
View File

@ -65184,6 +65184,10 @@ body{
right: 1rem; right: 1rem;
} }
:is([dir="ltr"] .sm\:ltr\:left-4){
left: 1rem;
}
:is([dir="ltr"] .sm\:ltr\:pl-10){ :is([dir="ltr"] .sm\:ltr\:pl-10){
padding-left: 2.5rem; padding-left: 2.5rem;
} }
@ -65192,6 +65196,10 @@ body{
left: 1rem; left: 1rem;
} }
:is([dir="rtl"] .sm\:rtl\:right-4){
right: 1rem;
}
:is([dir="rtl"] .sm\:rtl\:space-x-reverse) > :not([hidden]) ~ :not([hidden]){ :is([dir="rtl"] .sm\:rtl\:space-x-reverse) > :not([hidden]) ~ :not([hidden]){
--tw-space-x-reverse: 1; --tw-space-x-reverse: 1;
} }

View File

@ -1,204 +1,257 @@
<template> <template>
<div <div
@click="tryClose" @click="tryClose"
data-notify="container" data-notify="container"
class="alert alert-notify fixed w-full sm:w-500 flex items-center justify-between ltr:right-0 rtl:left-0 sm:ltr:right-4 sm:rtl:left-4 p-4 text-black font-bold rounded-lg z-30" :class="[
:class="[ 'alert alert-notify',
{ 'alert-with-icon': icon }, 'fixed w-full sm:w-500 flex items-center justify-between',
verticalAlign, {
horizontalAlign, 'rtl:right-0 ltr:left-0' : horizontalAlign == 'left',
alertType 'sm:rtl:right-4 sm:ltr:left-4' : horizontalAlign == 'left',
]" },
role="alert" {
:style="customPosition" 'ltr:right-0 rtl:left-0' : horizontalAlign == 'right',
data-notify-position="top-center" 'sm:ltr:right-4 sm:rtl:left-4' : horizontalAlign == 'right',
> },
<div class="flex items-center ltr:pr-3 rtl:pl-3"> 'p-4',
<template v-if="icon || $slots.icon"> 'text-black font-bold',
<slot name="icon"> 'rounded-lg',
<span class="alert-icon flex items-center ltr:mr-2 rtl:ml-2" data-notify="icon"> 'z-30',
<span class="material-icons text-2xl">{{ icon }}</span> {
'alert-with-icon': icon
},
verticalAlign,
horizontalAlign,
alertType
]"
role="alert"
:style="customPosition"
data-notify-position="top-center"
>
<div class="flex items-center ltr:pr-3 rtl:pl-3">
<template v-if="icon || $slots.icon">
<slot name="icon">
<span class="alert-icon flex items-center ltr:mr-2 rtl:ml-2" data-notify="icon">
<span class="material-icons text-2xl">{{ icon }}</span>
</span>
</slot>
</template>
<span class="alert-text">
<span v-if="title" class="title">
<b>{{ title }}<br/></b>
</span>
<span v-if="message" v-html="message"></span>
<content-render v-if="!message && component" :component="component"></content-render>
</span> </span>
</slot> </div>
</template>
<span class="alert-text"> <slot name="dismiss-icon">
<span v-if="title" class="title"> <button type="button"
<b>{{ title }}<br/></b> class="close text-2xl"
</span> data-dismiss="alert"
<span v-if="message" v-html="message"></span> aria-label="Close"
<content-render @click="close"
v-if="!message && component" >
:component="component" <span aria-hidden="true">×</span>
></content-render> </button>
</span> </slot>
</div> </div>
<slot name="dismiss-icon">
<button type="button"
class="close text-2xl"
data-dismiss="alert"
aria-label="Close"
@click="close">
<span aria-hidden="true">×</span>
</button>
</slot>
</div>
</template> </template>
<script> <script>
export default { export default {
name: 'notification', name: 'notification',
components: {
contentRender: { components: {
props: ['component'], contentRender: {
render: h => h(this.component) props: ['component'],
} render: h => h(this.component)
}, }
props: {
message: String,
title: {
type: String,
description: 'Notification title'
},
icon: {
type: String,
description: 'Notification icon'
},
verticalAlign: {
type: String,
default: 'top',
validator: value => {
let acceptedValues = ['top', 'bottom'];
return acceptedValues.indexOf(value) !== -1;
}, },
description: 'Vertical alignment of notification (top|bottom)'
}, props: {
horizontalAlign: { message: String,
type: String,
default: 'right', title: {
validator: value => { type: String,
let acceptedValues = ['left', 'center', 'right']; description: 'Notification title'
return acceptedValues.indexOf(value) !== -1; },
icon: {
type: String,
description: 'Notification icon'
},
verticalAlign: {
type: String,
default: 'top',
validator: value => {
let acceptedValues = ['top', 'bottom'];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Vertical alignment of notification (top|bottom)'
},
horizontalAlign: {
type: String,
default: 'right',
validator: value => {
let acceptedValues = ['left', 'center', 'right'];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Horizontal alignment of notification (left|center|right)'
},
type: {
type: String,
default: 'info',
validator: value => {
let acceptedValues = [
'default',
'info',
'primary',
'danger',
'warning',
'success'
];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Notification type of notification (gray-300|blue-300|gray-300|red-300|orange-300|green-300)'
},
timeout: {
type: Number,
default: 5000,
validator: value => {
return value >= 0;
},
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)'
},
timestamp: {
type: Date,
default: () => new Date(),
description: 'Notification timestamp (used internally to handle notification removal correctly)'
},
component: {
type: [Object, Function],
description: 'Custom content component. Cane be a `.vue` component or render function'
},
showClose: {
type: Boolean,
default: true,
description: 'Whether to show close button'
},
closeOnClick: {
type: Boolean,
default: true,
description: 'Whether to close notification when clicking it\' body'
},
clickHandler: {
type: Function,
description: 'Custom notification click handler'
}
}, },
description: 'Horizontal alignment of notification (left|center|right)'
}, data() {
type: { return {
type: String, elmHeight: 0,
default: 'info',
validator: value => { typeByClass: {
let acceptedValues = [ 'default': 'black-100',
'default', 'info': 'blue-100',
'info', 'primary': 'black-100',
'primary', 'danger': 'red-100',
'danger', 'warning': 'orange-100',
'warning', 'success': 'green-100',
'success' },
];
return acceptedValues.indexOf(value) !== -1; textByClass: {
'default': 'black-600',
'info': 'blue-600',
'primary': 'black-600',
'danger': 'red-600',
'warning': 'orange-600',
'success': 'green-600',
}
};
}, },
description: 'Notification type of notification (gray-300|blue-300|gray-300|red-300|orange-300|green-300)'
}, computed: {
timeout: { hasIcon() {
type: Number, return this.icon && this.icon.length > 0;
default: 5000, },
validator: value => {
return value >= 0; alertType() {
return `bg-${this.typeByClass[this.type]} text-${this.textByClass[this.type]}`;
},
customPosition() {
let initialMargin = 20;
let alertHeight = this.elmHeight + 10;
let sameAlertsCount = this.$notifications.state.filter(alert => {
return (
alert.horizontalAlign === this.horizontalAlign &&
alert.verticalAlign === this.verticalAlign &&
alert.timestamp <= this.timestamp
);
}).length;
if (this.$notifications.settings.overlap) {
sameAlertsCount = 1;
}
let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin;
if (sameAlertsCount > 1) {
pixels = 30 + this.$parent.children[sameAlertsCount - 2].elm.offsetHeight;
}
let styles = {};
if (this.verticalAlign === 'top') {
styles.top = `${pixels}px`;
} else {
styles.bottom = `${pixels}px`;
}
return styles;
}
}, },
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)'
}, methods: {
timestamp: { close() {
type: Date, this.$emit('close', this.timestamp);
default: () => new Date(), },
description: 'Notification timestamp (used internally to handle notification removal correctly)'
}, tryClose(evt) {
component: { if (this.clickHandler) {
type: [Object, Function], this.clickHandler(evt, this);
description: 'Custom content component. Cane be a `.vue` component or render function' }
},
showClose: { if (this.closeOnClick) {
type: Boolean, this.close();
default: true, }
description: 'Whether to show close button' }
},
closeOnClick: {
type: Boolean,
default: true,
description: 'Whether to close notification when clicking it\' body'
},
clickHandler: {
type: Function,
description: 'Custom notification click handler'
}
},
data() {
return {
elmHeight: 0,
typeByClass: {
'default': 'black-100',
'info': 'blue-100',
'primary': 'black-100',
'danger': 'red-100',
'warning': 'orange-100',
'success': 'green-100',
}, },
textByClass: {
'default': 'black-600', mounted() {
'info': 'blue-600', this.elmHeight = this.$el.clientHeight;
'primary': 'black-600',
'danger': 'red-600', if (this.timeout) {
'warning': 'orange-600', //setTimeout(this.close, this.timeout);
'success': 'green-600', }
} },
}; };
}, </script>
computed: {
hasIcon() {
return this.icon && this.icon.length > 0;
},
alertType() {
return `bg-${this.typeByClass[this.type]} text-${this.textByClass[this.type]}`;
},
customPosition() {
let initialMargin = 20;
let alertHeight = this.elmHeight + 10;
let sameAlertsCount = this.$notifications.state.filter(alert => {
return (
alert.horizontalAlign === this.horizontalAlign &&
alert.verticalAlign === this.verticalAlign &&
alert.timestamp <= this.timestamp
);
}).length;
if (this.$notifications.settings.overlap) {
sameAlertsCount = 1;
}
let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin;
let styles = {};
if (this.verticalAlign === 'top') {
styles.top = `${pixels}px`;
} else {
styles.bottom = `${pixels}px`;
}
return styles;
}
},
methods: {
close() {
this.$emit('close', this.timestamp);
},
tryClose(evt) {
if (this.clickHandler) {
this.clickHandler(evt, this);
}
if (this.closeOnClick) {
this.close();
}
}
},
mounted() {
this.elmHeight = this.$el.clientHeight;
if (this.timeout) {
setTimeout(this.close, this.timeout);
}
}
};
</script>

View File

@ -1,55 +1,63 @@
<template> <template>
<div class="notifications"> <div class="notifications">
<slide-y-up-transition :duration="transitionDuration" <slide-y-up-transition :duration="transitionDuration"
group group
mode="out-in"> mode="out-in"
<notification >
v-for="notification in notifications" <notification
v-bind="notification" v-for="notification in notifications"
:clickHandler="notification.onClick" v-bind="notification"
:key="notification.timestamp.getTime()" :clickHandler="notification.onClick"
@close="removeNotification" :key="notification.timestamp.getTime()"
> @close="removeNotification"
</notification> >
</slide-y-up-transition> </notification>
</div> </slide-y-up-transition>
</div>
</template> </template>
<script>
import Notification from './Notification.vue';
import { SlideYUpTransition } from 'vue2-transitions';
export default { <script>
components: { import Notification from './Notification.vue';
SlideYUpTransition, import { SlideYUpTransition } from 'vue2-transitions';
Notification
}, export default {
props: { components: {
transitionDuration: { SlideYUpTransition,
type: Number, Notification
default: 200 },
},
overlap: { props: {
type: Boolean, transitionDuration: {
default: false type: Number,
} default: 200
}, },
data() {
return { overlap: {
notifications: this.$notifications.state type: Boolean,
}; default: false
}, }
methods: { },
removeNotification(timestamp) {
this.$notifications.removeNotification(timestamp); data() {
} return {
}, notifications: this.$notifications.state
created() { };
this.$notifications.settings.overlap = this.overlap; },
},
watch: { methods: {
overlap: function (newVal) { removeNotification(timestamp) {
this.$notifications.settings.overlap = newVal; this.$notifications.removeNotification(timestamp);
} }
} },
};
created() {
this.$notifications.settings.overlap = this.overlap;
},
watch: {
overlap: function (newVal) {
this.$notifications.settings.overlap = newVal;
}
}
};
</script> </script>

View File

@ -1,66 +1,81 @@
import Notifications from './Notifications.vue'; import Notifications from './Notifications.vue';
const NotificationStore = { const NotificationStore = {
state: [], // here the notifications will be added state: [], // here the notifications will be added
settings: {
overlap: false, settings: {
verticalAlign: 'top', overlap: false,
horizontalAlign: 'right', verticalAlign: 'top',
type: 'info', horizontalAlign: 'right',
timeout: 5000, type: 'info',
closeOnClick: true, timeout: 5000,
showClose: true closeOnClick: true,
}, showClose: true
setOptions(options) { },
this.settings = Object.assign(this.settings, options);
}, setOptions(options) {
removeNotification(timestamp) { this.settings = Object.assign(this.settings, options);
const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp); },
if (indexToDelete !== -1) {
this.state.splice(indexToDelete, 1); removeNotification(timestamp) {
const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp);
if (indexToDelete !== -1) {
this.state.splice(indexToDelete, 1);
}
},
addNotification(notification) {
if (typeof notification === 'string' || notification instanceof String) {
notification = {
message: notification
};
}
notification.timestamp = new Date();
notification.timestamp.setMilliseconds(
notification.timestamp.getMilliseconds() + this.state.length
);
notification = Object.assign({}, this.settings, notification);
this.state.push(notification);
},
notify(notification) {
if (Array.isArray(notification)) {
notification.forEach(notificationInstance => {
this.addNotification(notificationInstance);
});
} else {
this.addNotification(notification);
}
} }
},
addNotification(notification) {
if (typeof notification === 'string' || notification instanceof String) {
notification = { message: notification };
}
notification.timestamp = new Date();
notification.timestamp.setMilliseconds(
notification.timestamp.getMilliseconds() + this.state.length
);
notification = Object.assign({}, this.settings, notification);
this.state.push(notification);
},
notify(notification) {
if (Array.isArray(notification)) {
notification.forEach(notificationInstance => {
this.addNotification(notificationInstance);
});
} else {
this.addNotification(notification);
}
}
}; };
const NotificationsPlugin = { const NotificationsPlugin = {
install(Vue, options) { install(Vue, options) {
let app = new Vue({ let app = new Vue({
data: { data: {
notificationStore: NotificationStore notificationStore: NotificationStore
}, },
methods: {
notify(notification) { methods: {
this.notificationStore.notify(notification); notify(notification) {
this.notificationStore.notify(notification);
}
}
});
Vue.prototype.$notify = app.notify;
Vue.prototype.$notifications = app.notificationStore;
Vue.component('Notifications', Notifications);
if (options) {
NotificationStore.setOptions(options);
} }
}
});
Vue.prototype.$notify = app.notify;
Vue.prototype.$notifications = app.notificationStore;
Vue.component('Notifications', Notifications);
if (options) {
NotificationStore.setOptions(options);
} }
}
}; };
export default NotificationsPlugin; export default NotificationsPlugin;

View File

@ -271,6 +271,8 @@ export default {
} }
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: notify.message, message: notify.message,
timeout: timeout, timeout: timeout,
icon: 'error_outline', icon: 'error_outline',
@ -1120,6 +1122,8 @@ export default {
document.execCommand('copy'); document.execCommand('copy');
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: this.share.success_message, message: this.share.success_message,
timeout: 5000, timeout: 5000,
icon: 'error_outline', icon: 'error_outline',

View File

@ -72,6 +72,8 @@ export default {
} }
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: response.data.message, message: response.data.message,
timeout: timeout, timeout: timeout,
icon: "error_outline", icon: "error_outline",
@ -92,6 +94,8 @@ export default {
} }
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: event.message, message: event.message,
timeout: timeout, timeout: timeout,
icon: "error_outline", icon: "error_outline",

View File

@ -54,6 +54,8 @@ const login = new Vue({
let type = notify.level; let type = notify.level;
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: notify.message, message: notify.message,
timeout: 5000, timeout: 5000,
icon: '', icon: '',

View File

@ -69,6 +69,8 @@ const app = new Vue({
if (response.data.error) { if (response.data.error) {
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: response.data.message, message: response.data.message,
timeout: 0, timeout: 0,
icon: 'fas fa-bell', icon: 'fas fa-bell',

View File

@ -106,6 +106,8 @@ const app = new Vue({
add_to_cart_promise.then(response => { add_to_cart_promise.then(response => {
if (response.data.success) { if (response.data.success) {
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: response.data.message, message: response.data.message,
timeout: 0, timeout: 0,
icon: "shopping_cart_checkout", icon: "shopping_cart_checkout",

View File

@ -112,6 +112,8 @@ export default {
}) })
.catch((error) => { .catch((error) => {
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: this.translations.finish.error_message, message: this.translations.finish.error_message,
timeout: 1000, timeout: 1000,
icon: "", icon: "",