268 lines
7.0 KiB
JavaScript
268 lines
7.0 KiB
JavaScript
import Toastify from 'toastify-js';
|
||
|
||
export class ToastComponent {
|
||
static defaultOptions = {
|
||
duration: 4000,
|
||
position: 'right',
|
||
gravity: 'top',
|
||
close: true,
|
||
stopOnFocus: true,
|
||
style: {
|
||
background: 'transparent',
|
||
boxShadow: 'none',
|
||
padding: '0',
|
||
borderRadius: '0'
|
||
},
|
||
offset: {
|
||
x: 16,
|
||
y: 16
|
||
}
|
||
};
|
||
|
||
static types = {
|
||
success: {
|
||
className: 'toast-success',
|
||
icon: '✓'
|
||
},
|
||
error: {
|
||
className: 'toast-error',
|
||
icon: '✕'
|
||
},
|
||
warning: {
|
||
className: 'toast-warning',
|
||
icon: '⚠'
|
||
},
|
||
info: {
|
||
className: 'toast-info',
|
||
icon: 'ℹ'
|
||
},
|
||
default: {
|
||
className: 'toast-default',
|
||
icon: '●'
|
||
}
|
||
};
|
||
|
||
static show(message, type = 'default', options = {}) {
|
||
if (!message) {
|
||
console.warn('ToastComponent: Message is required');
|
||
return null;
|
||
}
|
||
|
||
const typeConfig = this.types[type] || this.types.default;
|
||
const config = { ...this.defaultOptions, ...options };
|
||
|
||
// Build toast content
|
||
const content = this.buildToastContent(message, config.title, typeConfig.icon);
|
||
|
||
const toastOptions = {
|
||
text: content,
|
||
duration: config.duration,
|
||
gravity: config.gravity,
|
||
position: config.position,
|
||
stopOnFocus: config.stopOnFocus,
|
||
className: `toastify ${typeConfig.className}`,
|
||
escapeMarkup: false,
|
||
offset: config.offset,
|
||
style: {
|
||
...this.defaultOptions.style,
|
||
...config.style
|
||
},
|
||
onClick: config.onClick,
|
||
onClose: config.onClose
|
||
};
|
||
|
||
// Create and show toast
|
||
const toast = Toastify(toastOptions);
|
||
toast.showToast();
|
||
|
||
// Add progress bar if duration is set
|
||
if (config.duration > 0 && config.showProgress !== false) {
|
||
this.addProgressBar(toast, config.duration);
|
||
}
|
||
|
||
return toast;
|
||
}
|
||
|
||
static buildToastContent(message, title, icon) {
|
||
let content = '<div class="toast-enhanced">';
|
||
|
||
if (icon) {
|
||
content += `<div class="toast-enhanced__icon">${icon}</div>`;
|
||
}
|
||
|
||
content += '<div class="toast-enhanced__content">';
|
||
|
||
if (title) {
|
||
content += `<div class="toast-enhanced__title">${this.escapeHtml(title)}</div>`;
|
||
}
|
||
|
||
content += `<div class="toast-enhanced__message">${this.escapeHtml(message)}</div>`;
|
||
content += '</div>';
|
||
|
||
content += '<button class="toast-enhanced__close" onclick="this.closest(\'.toastify\').remove()"></button>';
|
||
content += '</div>';
|
||
|
||
return content;
|
||
}
|
||
|
||
static addProgressBar(toast, duration) {
|
||
const toastElement = toast.toastElement;
|
||
if (!toastElement) return;
|
||
|
||
const progressBar = document.createElement('div');
|
||
progressBar.className = 'toast-progress';
|
||
progressBar.innerHTML = '<div class="toast-progress__bar"></div>';
|
||
|
||
toastElement.appendChild(progressBar);
|
||
|
||
const bar = progressBar.querySelector('.toast-progress__bar');
|
||
if (bar) {
|
||
bar.style.animationDuration = `${duration}ms`;
|
||
}
|
||
}
|
||
|
||
static escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// Convenience methods
|
||
static success(message, options = {}) {
|
||
return this.show(message, 'success', options);
|
||
}
|
||
|
||
static error(message, options = {}) {
|
||
return this.show(message, 'error', options);
|
||
}
|
||
|
||
static warning(message, options = {}) {
|
||
return this.show(message, 'warning', options);
|
||
}
|
||
|
||
static info(message, options = {}) {
|
||
return this.show(message, 'info', options);
|
||
}
|
||
|
||
// Advanced toast types
|
||
static promise(promise, messages = {}, options = {}) {
|
||
const {
|
||
loading = 'Загрузка...',
|
||
success = 'Успешно!',
|
||
error = 'Ошибка!'
|
||
} = messages;
|
||
|
||
// Show loading toast
|
||
const loadingToast = this.show(loading, 'info', {
|
||
duration: 0, // Don't auto-close
|
||
showProgress: false,
|
||
...options
|
||
});
|
||
|
||
return promise
|
||
.then(result => {
|
||
// Close loading toast
|
||
if (loadingToast && loadingToast.toastElement) {
|
||
loadingToast.toastElement.remove();
|
||
}
|
||
|
||
// Show success toast
|
||
const successMessage = typeof success === 'function' ? success(result) : success;
|
||
this.success(successMessage, options);
|
||
|
||
return result;
|
||
})
|
||
.catch(err => {
|
||
// Close loading toast
|
||
if (loadingToast && loadingToast.toastElement) {
|
||
loadingToast.toastElement.remove();
|
||
}
|
||
|
||
// Show error toast
|
||
const errorMessage = typeof error === 'function' ? error(err) : error;
|
||
this.error(errorMessage, options);
|
||
|
||
throw err;
|
||
});
|
||
}
|
||
|
||
static confirm(message, options = {}) {
|
||
return new Promise((resolve) => {
|
||
const confirmOptions = {
|
||
duration: 0,
|
||
title: 'Подтверждение',
|
||
showProgress: false,
|
||
...options
|
||
};
|
||
|
||
const content = `
|
||
<div class="toast-enhanced__content">
|
||
<div class="toast-enhanced__icon">?</div>
|
||
<div class="toast-enhanced__text">
|
||
<div class="toast-enhanced__title">${this.escapeHtml(confirmOptions.title)}</div>
|
||
<div class="toast-enhanced__message">${this.escapeHtml(message)}</div>
|
||
<div class="toast-enhanced__actions" style="margin-top: 12px; display: flex; gap: 8px;">
|
||
<button class="btn btn--sm btn--primary toast-confirm-yes">Да</button>
|
||
<button class="btn btn--sm btn--secondary toast-confirm-no">Нет</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
const toast = Toastify({
|
||
text: content,
|
||
gravity: 'top',
|
||
position: 'center',
|
||
className: 'toastify toast-warning toast-enhanced toast-confirm',
|
||
escapeMarkup: false,
|
||
duration: 0,
|
||
stopOnFocus: true
|
||
});
|
||
|
||
toast.showToast();
|
||
|
||
// Add event listeners
|
||
const toastElement = toast.toastElement;
|
||
if (toastElement) {
|
||
const yesBtn = toastElement.querySelector('.toast-confirm-yes');
|
||
const noBtn = toastElement.querySelector('.toast-confirm-no');
|
||
|
||
if (yesBtn) {
|
||
yesBtn.addEventListener('click', () => {
|
||
toastElement.remove();
|
||
resolve(true);
|
||
});
|
||
}
|
||
|
||
if (noBtn) {
|
||
noBtn.addEventListener('click', () => {
|
||
toastElement.remove();
|
||
resolve(false);
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Utility method to remove all toasts
|
||
static clear() {
|
||
const toasts = document.querySelectorAll('.toastify');
|
||
toasts.forEach(toast => toast.remove());
|
||
}
|
||
|
||
// Method to update toast position for responsive design
|
||
static updatePosition() {
|
||
const isMobile = window.innerWidth < 768;
|
||
this.defaultOptions.position = isMobile ? 'center' : 'right';
|
||
}
|
||
}
|
||
|
||
// Update position on resize
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener('resize', () => {
|
||
ToastComponent.updatePosition();
|
||
});
|
||
|
||
// Set initial position
|
||
ToastComponent.updatePosition();
|
||
} |