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

View File

@ -1,204 +1,257 @@
<template>
<div
@click="tryClose"
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="[
{ '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>
<div
@click="tryClose"
data-notify="container"
:class="[
'alert alert-notify',
'fixed w-full sm:w-500 flex items-center justify-between',
{
'rtl:right-0 ltr:left-0' : horizontalAlign == 'left',
'sm:rtl:right-4 sm:ltr:left-4' : horizontalAlign == 'left',
},
{
'ltr:right-0 rtl:left-0' : horizontalAlign == 'right',
'sm:ltr:right-4 sm:rtl:left-4' : horizontalAlign == 'right',
},
'p-4',
'text-black font-bold',
'rounded-lg',
'z-30',
{
'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>
</slot>
</template>
</div>
<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>
</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>
<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>
<script>
export default {
name: 'notification',
components: {
contentRender: {
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;
export default {
name: 'notification',
components: {
contentRender: {
props: ['component'],
render: h => h(this.component)
}
},
description: 'Vertical alignment of notification (top|bottom)'
},
horizontalAlign: {
type: String,
default: 'right',
validator: value => {
let acceptedValues = ['left', 'center', 'right'];
return acceptedValues.indexOf(value) !== -1;
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)'
},
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)'
},
type: {
type: String,
default: 'info',
validator: value => {
let acceptedValues = [
'default',
'info',
'primary',
'danger',
'warning',
'success'
];
return acceptedValues.indexOf(value) !== -1;
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',
'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)'
},
timeout: {
type: Number,
default: 5000,
validator: value => {
return value >= 0;
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;
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)'
},
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'
}
},
data() {
return {
elmHeight: 0,
typeByClass: {
'default': 'black-100',
'info': 'blue-100',
'primary': 'black-100',
'danger': 'red-100',
'warning': 'orange-100',
'success': 'green-100',
methods: {
close() {
this.$emit('close', this.timestamp);
},
tryClose(evt) {
if (this.clickHandler) {
this.clickHandler(evt, this);
}
if (this.closeOnClick) {
this.close();
}
}
},
textByClass: {
'default': 'black-600',
'info': 'blue-600',
'primary': 'black-600',
'danger': 'red-600',
'warning': 'orange-600',
'success': 'green-600',
}
};
},
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>
mounted() {
this.elmHeight = this.$el.clientHeight;
if (this.timeout) {
//setTimeout(this.close, this.timeout);
}
},
};
</script>

View File

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

View File

@ -1,66 +1,81 @@
import Notifications from './Notifications.vue';
const NotificationStore = {
state: [], // here the notifications will be added
settings: {
overlap: false,
verticalAlign: 'top',
horizontalAlign: 'right',
type: 'info',
timeout: 5000,
closeOnClick: true,
showClose: true
},
setOptions(options) {
this.settings = Object.assign(this.settings, options);
},
removeNotification(timestamp) {
const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp);
if (indexToDelete !== -1) {
this.state.splice(indexToDelete, 1);
state: [], // here the notifications will be added
settings: {
overlap: false,
verticalAlign: 'top',
horizontalAlign: 'right',
type: 'info',
timeout: 5000,
closeOnClick: true,
showClose: true
},
setOptions(options) {
this.settings = Object.assign(this.settings, options);
},
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 = {
install(Vue, options) {
let app = new Vue({
data: {
notificationStore: NotificationStore
},
methods: {
notify(notification) {
this.notificationStore.notify(notification);
install(Vue, options) {
let app = new Vue({
data: {
notificationStore: NotificationStore
},
methods: {
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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