Skip to content

Commit 3c97fa0

Browse files
authored
Add native queries and remove react-container-query (#1504)
## Summary - Replace `react-container-query` with a small internal `ResizeObserver` hook for the editor container breakpoint. - Keep existing native CSS container-query layout behavior unchanged. - Preserve the current 720px responsive behavior for editor/output resizing: - desktop: side-by-side layout, right resize handle, `50%` width / `100%` height - stacked: column layout, bottom resize handle, `100%` width / `50%` height - Remove the unused `containerQueries.js` helper and the `react-container-query` dependency.
1 parent f9a64e5 commit 3c97fa0

7 files changed

Lines changed: 177 additions & 73 deletions

File tree

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"react": "^19.2.7",
5555
"react-app-polyfill": "^2.0.0",
5656
"react-confirm-alert": "^2.8.0",
57-
"react-container-query": "^0.13.0",
5857
"react-cookie": "^4.1.1",
5958
"react-dom": "^19.2.7",
6059
"react-i18next": "^12.0.0",

src/components/Editor/Project/Project.jsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
/* eslint-disable react-hooks/exhaustive-deps */
2-
import React, { useEffect, useMemo, useState } from "react";
1+
import React, { useEffect, useState } from "react";
32
import { useSelector } from "react-redux";
43
import "react-tabs/style/react-tabs.css";
54
import "react-toastify/dist/ReactToastify.css";
6-
import { useContainerQuery } from "react-container-query";
75
import classnames from "classnames";
86

97
import "../../../assets/stylesheets/Project.scss";
@@ -14,7 +12,7 @@ import ScratchProjectBar from "../../ProjectBar/ScratchProjectBar";
1412
import Sidebar from "../../Menus/Sidebar/Sidebar";
1513
import EditorInput from "../EditorInput/EditorInput";
1614
import ResizableWithHandle from "../../../utils/ResizableWithHandle";
17-
import { projContainer } from "../../../utils/containerQueries";
15+
import { useContainerMinWidth } from "../../../hooks/useContainerMinWidth";
1816
import ScratchContainer from "./ScratchContainer";
1917

2018
const Project = (props) => {
@@ -43,21 +41,12 @@ const Project = (props) => {
4341
}
4442
}, [autosave, isCodeEditorScratchProject, saving]);
4543

46-
const [params, containerRef] = useContainerQuery(projContainer);
47-
const [defaultWidth, setDefaultWidth] = useState("auto");
48-
const [defaultHeight, setDefaultHeight] = useState("auto");
49-
const [maxWidth, setMaxWidth] = useState("100%");
50-
const [handleDirection, setHandleDirection] = useState("right");
44+
const [isDesktop, containerRef] = useContainerMinWidth(720);
5145
const [loading, setLoading] = useState(true);
52-
53-
useMemo(() => {
54-
const isDesktop = params["width-larger-than-720"];
55-
56-
setDefaultWidth(isDesktop ? "50%" : "100%");
57-
setDefaultHeight(isDesktop ? "100%" : "50%");
58-
setMaxWidth(isDesktop ? "75%" : "100%");
59-
setHandleDirection(isDesktop ? "right" : "bottom");
60-
}, [params["width-larger-than-720"]]);
46+
const defaultWidth = isDesktop ? "50%" : "100%";
47+
const defaultHeight = isDesktop ? "100%" : "50%";
48+
const maxWidth = isDesktop ? "75%" : "100%";
49+
const handleDirection = isDesktop ? "right" : "bottom";
6150

6251
useEffect(() => {
6352
setLoading(false);

src/components/Editor/Project/Project.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import configureStore from "redux-mock-store";
66
import Project from "./Project";
77
import { showSavedMessage } from "../../../utils/Notifications";
88
import { MemoryRouter } from "react-router-dom";
9+
import { useContainerMinWidth } from "../../../hooks/useContainerMinWidth";
910

1011
window.HTMLElement.prototype.scrollIntoView = jest.fn();
1112

@@ -15,9 +16,16 @@ jest.mock("react-router-dom", () => ({
1516
}));
1617

1718
jest.mock("../../../utils/Notifications");
19+
jest.mock("../../../hooks/useContainerMinWidth", () => ({
20+
useContainerMinWidth: jest.fn(),
21+
}));
1822

1923
jest.useFakeTimers();
2024

25+
beforeEach(() => {
26+
useContainerMinWidth.mockReturnValue([false, jest.fn()]);
27+
});
28+
2129
const user1 = {
2230
access_token: "myAccessToken",
2331
profile: {
@@ -204,6 +212,46 @@ describe("When not logged in and falling on default container width", () => {
204212
.length,
205213
).toBe(1);
206214
});
215+
216+
test("Shows right drag bar with expected params on desktop containers", () => {
217+
useContainerMinWidth.mockReturnValue([true, jest.fn()]);
218+
219+
const middlewares = [];
220+
const mockStore = configureStore(middlewares);
221+
const initialState = {
222+
editor: {
223+
project: project,
224+
openFiles: [["main.py"]],
225+
focussedFileIndices: [0],
226+
webComponent: false,
227+
},
228+
auth: {},
229+
instructions: {},
230+
};
231+
const mockedStore = mockStore(initialState);
232+
const { getByTestId } = render(
233+
<Provider store={mockedStore}>
234+
<MemoryRouter>
235+
<div id="app">
236+
<Project />
237+
</div>
238+
</MemoryRouter>
239+
</Provider>,
240+
);
241+
242+
const container = getByTestId("proj-editor-container");
243+
expect(container).toHaveStyle({
244+
"min-width": "25%",
245+
"max-width": "75%",
246+
width: "50%",
247+
height: "100%",
248+
});
249+
250+
expect(
251+
container.getElementsByClassName("resizable-with-handle__handle--right")
252+
.length,
253+
).toBe(1);
254+
});
207255
});
208256

209257
test("Successful manual save prompts project saved message", async () => {

src/hooks/useContainerMinWidth.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useCallback, useLayoutEffect, useState } from "react";
2+
3+
const getObservedWidth = (entry) => {
4+
const contentBoxSize = Array.isArray(entry.contentBoxSize)
5+
? entry.contentBoxSize[0]
6+
: entry.contentBoxSize;
7+
8+
return contentBoxSize?.inlineSize ?? entry.contentRect?.width;
9+
};
10+
11+
export const useContainerMinWidth = (minWidth) => {
12+
const [container, setContainer] = useState(null);
13+
const [matches, setMatches] = useState(false);
14+
const containerRef = useCallback((node) => setContainer(node), []);
15+
16+
useLayoutEffect(() => {
17+
if (!container) return undefined;
18+
19+
const update = (width) => {
20+
if (typeof width === "number") {
21+
setMatches(width >= minWidth);
22+
}
23+
};
24+
25+
update(container.getBoundingClientRect().width);
26+
27+
if (!window.ResizeObserver) return undefined;
28+
29+
const observer = new window.ResizeObserver((entries) => {
30+
if (entries[0]) update(getObservedWidth(entries[0]));
31+
});
32+
observer.observe(container);
33+
34+
return () => observer.disconnect();
35+
}, [container, minWidth]);
36+
37+
return [matches, containerRef];
38+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { act, renderHook } from "@testing-library/react";
2+
3+
import { useContainerMinWidth } from "./useContainerMinWidth";
4+
5+
const originalResizeObserver = window.ResizeObserver;
6+
let observers;
7+
8+
class MockResizeObserver {
9+
constructor(callback) {
10+
this.callback = callback;
11+
this.observe = jest.fn();
12+
this.disconnect = jest.fn();
13+
observers.push(this);
14+
}
15+
}
16+
17+
const makeElement = (width) => ({
18+
getBoundingClientRect: jest.fn(() => ({ width })),
19+
});
20+
21+
const renderObservedHook = (width) => {
22+
const hook = renderHook(() => useContainerMinWidth(720));
23+
const element = makeElement(width);
24+
25+
act(() => {
26+
hook.result.current[1](element);
27+
});
28+
29+
return { ...hook, element };
30+
};
31+
32+
describe("useContainerMinWidth", () => {
33+
beforeEach(() => {
34+
observers = [];
35+
window.ResizeObserver = MockResizeObserver;
36+
});
37+
38+
afterEach(() => {
39+
window.ResizeObserver = originalResizeObserver;
40+
});
41+
42+
test("reports whether the container is at least the minimum width", () => {
43+
const { result } = renderObservedHook(600);
44+
45+
expect(result.current[0]).toBe(false);
46+
expect(observers[0].observe).toHaveBeenCalled();
47+
48+
act(() => {
49+
observers[0].callback([{ contentRect: { width: 720 } }]);
50+
});
51+
52+
expect(result.current[0]).toBe(true);
53+
});
54+
55+
test("uses contentBoxSize when available", () => {
56+
const { result } = renderObservedHook(600);
57+
58+
act(() => {
59+
observers[0].callback([
60+
{ contentBoxSize: [{ inlineSize: 721 }], contentRect: { width: 600 } },
61+
]);
62+
});
63+
64+
expect(result.current[0]).toBe(true);
65+
});
66+
67+
test("disconnects the observer on unmount", () => {
68+
const { unmount } = renderObservedHook(800);
69+
const observer = observers[0];
70+
71+
unmount();
72+
73+
expect(observer.disconnect).toHaveBeenCalled();
74+
});
75+
76+
test("falls back to a one-time measurement without ResizeObserver", () => {
77+
window.ResizeObserver = undefined;
78+
79+
const { result } = renderObservedHook(800);
80+
81+
expect(result.current[0]).toBe(true);
82+
expect(observers).toEqual([]);
83+
});
84+
});

src/utils/containerQueries.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

yarn.lock

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4222,7 +4222,6 @@ __metadata:
42224222
react: "npm:^19.2.7"
42234223
react-app-polyfill: "npm:^2.0.0"
42244224
react-confirm-alert: "npm:^2.8.0"
4225-
react-container-query: "npm:^0.13.0"
42264225
react-cookie: "npm:^4.1.1"
42274226
react-dev-utils: "npm:^11.0.3"
42284227
react-dom: "npm:^19.2.7"
@@ -7086,13 +7085,6 @@ __metadata:
70867085
languageName: node
70877086
linkType: hard
70887087

7089-
"batch-processor@npm:^1.0.0":
7090-
version: 1.0.0
7091-
resolution: "batch-processor@npm:1.0.0"
7092-
checksum: 10/59452655203eeb94101770a4c31a3aa81a60f6403ef4e66870f2970f0873ebc795c442610aa420be34535f1e51d644a12f0c5a37fb3bde08bf5c00109ee67d97
7093-
languageName: node
7094-
linkType: hard
7095-
70967088
"batch@npm:0.6.1":
70977089
version: 0.6.1
70987090
resolution: "batch@npm:0.6.1"
@@ -8251,13 +8243,6 @@ __metadata:
82518243
languageName: node
82528244
linkType: hard
82538245

8254-
"container-query-toolkit@npm:0.1.3":
8255-
version: 0.1.3
8256-
resolution: "container-query-toolkit@npm:0.1.3"
8257-
checksum: 10/ac9931c73fa66d80eb25a61b374c8b90b6424d0205b7d5b81d8b208ab03c34d1c69e10561250ca142e1a9f2194e07876c290c06110d58a681bdb613faff150bb
8258-
languageName: node
8259-
linkType: hard
8260-
82618246
"content-disposition@npm:0.5.4":
82628247
version: 0.5.4
82638248
resolution: "content-disposition@npm:0.5.4"
@@ -9904,15 +9889,6 @@ __metadata:
99049889
languageName: node
99059890
linkType: hard
99069891

9907-
"element-resize-detector@npm:1.1.13":
9908-
version: 1.1.13
9909-
resolution: "element-resize-detector@npm:1.1.13"
9910-
dependencies:
9911-
batch-processor: "npm:^1.0.0"
9912-
checksum: 10/206f82f91f2fde29bb9f484f38012b184beb55d9abaaa25372b9e76930d0d917e446ac4302da161824d1ba4d7dc1a620bc8f50fe9a35fefb39274546b9293e10
9913-
languageName: node
9914-
linkType: hard
9915-
99169892
"element-size@npm:^1.1.1":
99179893
version: 1.1.1
99189894
resolution: "element-size@npm:1.1.1"
@@ -17968,19 +17944,6 @@ __metadata:
1796817944
languageName: node
1796917945
linkType: hard
1797017946

17971-
"react-container-query@npm:^0.13.0":
17972-
version: 0.13.0
17973-
resolution: "react-container-query@npm:0.13.0"
17974-
dependencies:
17975-
container-query-toolkit: "npm:0.1.3"
17976-
resize-observer-lite: "npm:0.2.3"
17977-
peerDependencies:
17978-
react: ^0.14.0 || ^15.0.0-0 || ^16.0.0-0 || ^17 || ^18
17979-
react-dom: ^0.14.0 || ^15.0.0-0 || ^16.0.0-0 || ^17 || ^18
17980-
checksum: 10/2c310dd6f232c2ed9d5fbd6b9fd72e5a7892a4bcbba667db07046388206a5a4928f5b4c61342528c2761d29cb228a1e2ee09fd541d30fd4a0a8875de03ab4573
17981-
languageName: node
17982-
linkType: hard
17983-
1798417947
"react-cookie@npm:^4.1.1":
1798517948
version: 4.1.1
1798617949
resolution: "react-cookie@npm:4.1.1"
@@ -18894,15 +18857,6 @@ __metadata:
1889418857
languageName: node
1889518858
linkType: hard
1889618859

18897-
"resize-observer-lite@npm:0.2.3":
18898-
version: 0.2.3
18899-
resolution: "resize-observer-lite@npm:0.2.3"
18900-
dependencies:
18901-
element-resize-detector: "npm:1.1.13"
18902-
checksum: 10/4e947e2df787b39e3608ef779b5af31aafe16138b82322cedb8249106d607299f0d58fee4c375ca97dd689f63685b46ae00a02a8e5a08b1ff9d72308e0638e94
18903-
languageName: node
18904-
linkType: hard
18905-
1890618860
"resolve-cwd@npm:^3.0.0":
1890718861
version: 3.0.0
1890818862
resolution: "resolve-cwd@npm:3.0.0"

0 commit comments

Comments
 (0)