EmberClone/apps/web/src/components/UndoStack.tsx

80 lines
2.2 KiB
TypeScript

import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
type UndoAction = {
description: string;
undoFn: () => void | Promise<void>;
};
type UndoStackContextType = {
pushUndo: (action: UndoAction) => void;
undo: () => void;
stack: UndoAction[];
};
const UndoStackContext = createContext<UndoStackContextType | undefined>(undefined);
export function UndoStackProvider({ children }: { children: React.ReactNode }) {
const [stack, setStack] = useState<UndoAction[]>([]);
const pushUndo = useCallback((action: UndoAction) => {
setStack((prev) => [...prev, action]);
}, []);
const undo = useCallback(async () => {
setStack((prev) => {
if (prev.length === 0) return prev;
const lastAction = prev[prev.length - 1];
// Execute the undo function
lastAction.undoFn();
return prev.slice(0, -1);
});
}, []);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
e.preventDefault();
undo();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [undo]);
return (
<UndoStackContext.Provider value={{ pushUndo, undo, stack }}>
{children}
</UndoStackContext.Provider>
);
}
export function useUndoStack() {
const context = useContext(UndoStackContext);
if (!context) {
throw new Error('useUndoStack must be used within an UndoStackProvider');
}
return context;
}
export const UndoStack = () => {
const { stack, undo } = useUndoStack();
if (stack.length === 0) return null;
return (
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
<div className="bg-slate-800 text-white px-3 py-2 rounded-lg shadow-lg text-xs flex items-center gap-3 animate-in fade-in slide-in-from-bottom-2">
<span>Last action: <strong>{stack[stack.length - 1].description}</strong></span>
<button
onClick={undo}
className="bg-blue-600 hover:bg-blue-500 px-2 py-1 rounded transition-colors"
>
Undo (Ctrl+Z)
</button>
</div>
</div>
);
};