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();