**Problem 1: Validation footer covers mobile menu** - Fixed bottom validation panel (z-index 1000) overlapped mobile nav - Solution: Removed bottom panel, added inline validation in config panel header **Problem 2: Alert dialogs for save success** - alert() blocks UI and requires OK click for every save - Solution: Toast notifications (auto-close after 3s, non-blocking) **Problem 3: Validation shows only counts, not details** - Footer showed "1 Error, 2 Warnings" without details - Solution: Inline display shows all error/warning messages with click-to-navigate **New Components:** - Toast.jsx: Auto-closing notifications (success/error/warning/info) - ConfirmDialog.jsx: Modal confirmation dialogs (for future save-on-close) **Changes:** - WorkflowEditorPage: Inline validation in config panel, toast state - Removed fixed bottom .validation-panel (no mobile overlap) - Toast for save success instead of alert() **Still TODO (separate commit):** - Save confirmation when closing/switching nodes with unsaved changes - Dirty state tracking Part 3: Inline Prompts - UX polish (validation + notifications)
96 lines
2.2 KiB
JavaScript
96 lines
2.2 KiB
JavaScript
import { useEffect } from 'react'
|
||
|
||
/**
|
||
* Toast Notification Component
|
||
*
|
||
* Auto-closing notification that appears at the top of the screen
|
||
*
|
||
* Props:
|
||
* - message: string
|
||
* - type: 'success' | 'error' | 'warning' | 'info'
|
||
* - duration: number (ms, default 3000)
|
||
* - onClose: callback when toast closes
|
||
*/
|
||
export function Toast({ message, type = 'info', duration = 3000, onClose }) {
|
||
useEffect(() => {
|
||
const timer = setTimeout(() => {
|
||
if (onClose) onClose()
|
||
}, duration)
|
||
|
||
return () => clearTimeout(timer)
|
||
}, [duration, onClose])
|
||
|
||
const styles = {
|
||
success: {
|
||
background: '#4CAF50',
|
||
color: 'white',
|
||
icon: '✅'
|
||
},
|
||
error: {
|
||
background: 'var(--danger)',
|
||
color: 'white',
|
||
icon: '❌'
|
||
},
|
||
warning: {
|
||
background: '#FFC107',
|
||
color: '#856404',
|
||
icon: '⚠️'
|
||
},
|
||
info: {
|
||
background: 'var(--accent)',
|
||
color: 'white',
|
||
icon: 'ℹ️'
|
||
}
|
||
}
|
||
|
||
const style = styles[type] || styles.info
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
position: 'fixed',
|
||
top: '80px',
|
||
left: '50%',
|
||
transform: 'translateX(-50%)',
|
||
background: style.background,
|
||
color: style.color,
|
||
padding: '12px 24px',
|
||
borderRadius: '8px',
|
||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||
zIndex: 10000,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '8px',
|
||
fontSize: '14px',
|
||
fontWeight: 500,
|
||
minWidth: '300px',
|
||
maxWidth: '600px',
|
||
animation: 'slideDown 0.3s ease-out'
|
||
}}
|
||
onClick={onClose}
|
||
>
|
||
<span style={{ fontSize: '18px' }}>{style.icon}</span>
|
||
<span>{message}</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Add animation CSS if not already in global styles
|
||
const styleElement = document.createElement('style')
|
||
styleElement.textContent = `
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(-50%) translateY(-20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(-50%) translateY(0);
|
||
}
|
||
}
|
||
`
|
||
if (!document.querySelector('style[data-toast-styles]')) {
|
||
styleElement.setAttribute('data-toast-styles', 'true')
|
||
document.head.appendChild(styleElement)
|
||
}
|