UNPKG

qnce-engine

Version:

Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization

175 lines 7.78 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AutosaveIndicator = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const types_1 = require("../types"); const react_2 = require("../../integrations/react"); /** * AutosaveIndicator Component * * Visual indicator showing autosave status with animated feedback, * timestamp display, and customizable positioning. * * Features: * - Real-time autosave status updates (idle, saving, saved, error) * - Animated visual feedback with spinner and status icons * - Configurable positioning (corners, inline) * - Timestamp of last successful save * - Auto-hide functionality * - Accessible with proper ARIA labels */ const AutosaveIndicator = ({ engine, theme: customTheme, className = '', style, variant = 'detailed', position = 'inline', messages = { idle: 'Auto-save ready', saving: 'Saving...', saved: 'Saved', error: 'Save failed', disabled: 'Autosave disabled' }, showTimestamp = true, autoHideDelay = 3000 }) => { const { isEnabled, lastAutosave, isSaving } = (0, react_2.useAutosave)(engine); const [visible, setVisible] = (0, react_1.useState)(true); const [isAnimating, setIsAnimating] = (0, react_1.useState)(false); const [lastSaveSuccessful, setLastSaveSuccessful] = (0, react_1.useState)(true); // Determine status based on hook values const status = (0, react_1.useMemo)(() => { if (!isEnabled) return 'disabled'; if (isSaving) return 'saving'; if (lastAutosave && !isSaving) { return lastSaveSuccessful ? 'saved' : 'error'; } return 'idle'; }, [isEnabled, isSaving, lastAutosave, lastSaveSuccessful]); // Merge custom theme with default theme const theme = (0, react_1.useMemo)(() => ({ ...types_1.defaultTheme, ...customTheme, colors: { ...types_1.defaultTheme.colors, ...customTheme?.colors }, spacing: { ...types_1.defaultTheme.spacing, ...customTheme?.spacing }, borderRadius: { ...types_1.defaultTheme.borderRadius, ...customTheme?.borderRadius }, typography: { ...types_1.defaultTheme.typography, ...customTheme?.typography, fontSize: { ...types_1.defaultTheme.typography.fontSize, ...customTheme?.typography?.fontSize }, fontWeight: { ...types_1.defaultTheme.typography.fontWeight, ...customTheme?.typography?.fontWeight } }, shadows: { ...types_1.defaultTheme.shadows, ...customTheme?.shadows } }), [customTheme]); // Auto-hide logic for saved status (0, react_1.useEffect)(() => { setVisible(true); if (status === 'saved' && autoHideDelay > 0) { const hideTimer = setTimeout(() => { setVisible(false); }, autoHideDelay); return () => clearTimeout(hideTimer); } }, [status, autoHideDelay]); (0, react_1.useEffect)(() => { setIsAnimating(isSaving); }, [isSaving]); // Position styles const getPositionStyles = () => { const basePositionStyle = { position: position === 'inline' ? 'static' : 'fixed', zIndex: 1000 }; switch (position) { case 'top-left': return { ...basePositionStyle, top: theme.spacing.md, left: theme.spacing.md }; case 'top-right': return { ...basePositionStyle, top: theme.spacing.md, right: theme.spacing.md }; case 'bottom-left': return { ...basePositionStyle, bottom: theme.spacing.md, left: theme.spacing.md }; case 'bottom-right': return { ...basePositionStyle, bottom: theme.spacing.md, right: theme.spacing.md }; default: return basePositionStyle; } }; // Status-based styling const getStatusColor = (currentStatus) => { switch (currentStatus) { case 'saving': return theme.colors.warning; case 'saved': return theme.colors.success; case 'error': return theme.colors.error; default: return theme.colors.secondary; } }; // Icons and indicators const StatusIcon = ({ status }) => { const iconStyle = { fontSize: '16px', lineHeight: 1, color: getStatusColor(status) }; switch (status) { case 'saving': return ((0, jsx_runtime_1.jsx)("span", { style: { ...iconStyle, animation: isAnimating ? 'spin 1s linear infinite' : 'none' }, children: "\u27F3" })); case 'saved': return (0, jsx_runtime_1.jsx)("span", { style: iconStyle, children: "\u2713" }); case 'error': return (0, jsx_runtime_1.jsx)("span", { style: iconStyle, children: "\u2717" }); default: return (0, jsx_runtime_1.jsx)("span", { style: iconStyle, children: "\u25CB" }); } }; // Format timestamp const formatTimestamp = (date) => { if (!date) return ''; return date.toLocaleTimeString([], { hour12: false, timeStyle: 'short' }); }; // Component styles const containerStyle = { display: visible ? 'flex' : 'none', alignItems: 'center', gap: theme.spacing.xs, padding: variant === 'icon-only' ? theme.spacing.xs : theme.spacing.sm, backgroundColor: theme.colors.surface, border: `1px solid ${getStatusColor(status)}`, borderRadius: theme.borderRadius.md, boxShadow: theme.shadows.sm, fontFamily: theme.typography.fontFamily, fontSize: theme.typography.fontSize.sm, color: theme.colors.text, transition: 'all 0.2s ease-in-out', opacity: visible ? 1 : 0, transform: visible ? 'translateY(0)' : 'translateY(-10px)', ...getPositionStyles(), ...style }; const textStyle = { fontWeight: theme.typography.fontWeight.medium, color: getStatusColor(status) }; const timestampStyle = { fontSize: theme.typography.fontSize.sm, color: theme.colors.textSecondary, fontWeight: theme.typography.fontWeight.normal, opacity: 0.8 }; // Show disabled state when autosave is disabled, otherwise don't render if (!isEnabled && status === 'disabled') { // Render disabled state } else if (!isEnabled) { return null; } return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { children: ` @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ` }), (0, jsx_runtime_1.jsxs)("div", { className: `qnce-autosave-indicator qnce-autosave-${status} ${className}`, style: containerStyle, role: "status", "aria-live": "polite", "aria-label": `Autosave status: ${messages[status] ?? status}`, children: [(0, jsx_runtime_1.jsx)(StatusIcon, { status: status }), variant !== 'icon-only' && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { style: textStyle, children: messages[status] ?? status }), showTimestamp && lastAutosave && ((0, jsx_runtime_1.jsx)("span", { style: timestampStyle, children: formatTimestamp(lastAutosave) })), showTimestamp && !lastAutosave && ((0, jsx_runtime_1.jsx)("span", { style: timestampStyle, children: "Never" }))] }))] })] })); }; exports.AutosaveIndicator = AutosaveIndicator; //# sourceMappingURL=AutosaveIndicator.js.map