diff --git a/package-lock.json b/package-lock.json index b184ae86a..7f6c2ec38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17026,6 +17026,11 @@ "node": ">=4" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" + }, "node_modules/playwright-core": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz", @@ -26671,6 +26676,7 @@ "immer": "^9.0.21", "lodash": "^4.17.21", "path-browserify": "^1.0.1", + "platform": "^1.3.6", "postcss": "^8.4.27", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/studio-ui/.size-limit.cjs b/packages/studio-ui/.size-limit.cjs index 342ba4516..20236c854 100644 --- a/packages/studio-ui/.size-limit.cjs +++ b/packages/studio-ui/.size-limit.cjs @@ -1,7 +1,7 @@ module.exports = [ { path: "lib/**/*.js", - limit: "850 kB", + limit: "870 kB", gzip: false, }, ]; diff --git a/packages/studio-ui/package.json b/packages/studio-ui/package.json index 15051064f..0d2a18246 100644 --- a/packages/studio-ui/package.json +++ b/packages/studio-ui/package.json @@ -34,6 +34,7 @@ "immer": "^9.0.21", "lodash": "^4.17.21", "path-browserify": "^1.0.1", + "platform": "^1.3.6", "postcss": "^8.4.27", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -41,9 +42,9 @@ "react-select": "^5.7.4", "react-toastify": "^9.1.1", "react-tooltip": "^5.18.0", - "true-myth": "^6.2.0", "tailwind-merge": "^1.8.1", "tailwindcss": "^3.3.3", + "true-myth": "^6.2.0", "zundo": "2.0.0-beta.12", "zustand": "^4.3.2" }, diff --git a/packages/studio-ui/src/components/UndoRedo.tsx b/packages/studio-ui/src/components/UndoRedo.tsx index eaad55ae5..14cb75bae 100644 --- a/packages/studio-ui/src/components/UndoRedo.tsx +++ b/packages/studio-ui/src/components/UndoRedo.tsx @@ -1,7 +1,8 @@ import useTemporalStore from "../store/useTemporalStore"; import { ReactComponent as Undo } from "../icons/undo.svg"; -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; import classNames from "classnames"; +import platform from "platform"; /** * Buttons for undo and redo actions. @@ -22,6 +23,23 @@ export default function UndoRedo(): JSX.Element { redo(); }, [redo]); + const handleUndoKeydown = useCallback( + (event: KeyboardEvent) => { + const actionKey = + platform.os.family === "OS X" ? event.metaKey : event.ctrlKey; + if (actionKey && event.key === "z") { + event.preventDefault(); + undo(); + } + }, + [undo] + ); + + useEffect(() => { + document.addEventListener("keydown", handleUndoKeydown); + return () => document.removeEventListener("keydown", handleUndoKeydown); + }, [handleUndoKeydown]); + const disableUndo = pastStates.length === 0; const disableRedo = futureStates.length === 0; const undoClasses = classNames("w-4", { diff --git a/packages/studio-ui/tests/components/UndoRedo.test.tsx b/packages/studio-ui/tests/components/UndoRedo.test.tsx index 3e1b6f9cd..f24303a2f 100644 --- a/packages/studio-ui/tests/components/UndoRedo.test.tsx +++ b/packages/studio-ui/tests/components/UndoRedo.test.tsx @@ -5,6 +5,16 @@ import useStudioStore from "../../src/store/useStudioStore"; import useTemporalStore from "../../src/store/useTemporalStore"; import { searchBarComponent } from "../__fixtures__/componentStates"; import mockStore from "../__utils__/mockStore"; +import platform from "platform"; + +jest.mock("platform", () => ({ + __esModule: true, + default: { + os: { + family: null, + }, + }, +})); describe("Undo/redo", () => { beforeEach(() => { @@ -46,6 +56,34 @@ describe("Undo/redo", () => { expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined(); }); + it("undoes last state update using control + z if OS is not OS X", async () => { + platform.os.family = "Windows"; + render(); + expect(useStudioStore.getState().pages.activeComponentUUID).toBe( + "searchbar-uuid" + ); + await userEvent.keyboard("{Meta>}z{/Meta}"); + expect(useStudioStore.getState().pages.activeComponentUUID).toBe( + "searchbar-uuid" + ); + await userEvent.keyboard("{Control>}z{/Control}"); + expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined(); + }); + + it("undoes last state update using command + z if OS is OS X", async () => { + platform.os.family = "OS X"; + render(); + expect(useStudioStore.getState().pages.activeComponentUUID).toBe( + "searchbar-uuid" + ); + await userEvent.keyboard("{Control>}z{/Control}"); + expect(useStudioStore.getState().pages.activeComponentUUID).toBe( + "searchbar-uuid" + ); + await userEvent.keyboard("{Meta>}z{/Meta}"); + expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined(); + }); + it("redoes single state update when redo is clicked", async () => { const undo = useTemporalStore((store) => store.undo); undo();