qnce-engine
Version:
Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization
175 lines • 7.78 kB
JavaScript
;
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