80 lines
2.2 KiB
TypeScript
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>
|
|
);
|
|
}; |