From 0096c91e9c5a7d8432bd3ef45287c7f9aa43113d Mon Sep 17 00:00:00 2001
From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com>
Date: Fri, 23 Oct 2026 10:49:09 +0100
Subject: [PATCH 1/4] Show the small sidebar on larger screens
This increases the size of screen we show the smaller sidebar so that it's show on sizes that reduces the need to scroll the blocks editor.
This change apples to all projects as there is little downside in the smaller sidebar
---
src/assets/stylesheets/Sidebar.scss | 14 +++++++++-----
src/assets/stylesheets/_breakpoints.scss | 1 -
2 files changed, 9 insertions(+), 6 deletions(-)
delete mode 100644 src/assets/stylesheets/_breakpoints.scss
diff --git a/src/assets/stylesheets/Sidebar.scss b/src/assets/stylesheets/Sidebar.scss
index d2d5d3307..994ff43ff 100644
--- a/src/assets/stylesheets/Sidebar.scss
+++ b/src/assets/stylesheets/Sidebar.scss
@@ -1,6 +1,10 @@
@use "@raspberrypifoundation/design-system-core/scss/mixins/typography";
@use "./rpf_design_system/mixins" as *;
-@use "./breakpoints" as *;
+
+$sidebar-min-width: 350px;
+$block-editor-min-width: 1024px;
+$sidebar-gap: 16px;
+$small-sidebar-breakpoint: calc($sidebar-min-width + $block-editor-min-width + $sidebar-gap);
/* Sidebar */
.sidebar {
@@ -28,7 +32,7 @@
margin: var(--space-1-5);
display: var(--editor-logo-display, inherit);
- @include unique-max-width($stacked-editor-max-width) {
+ @include unique-max-width($small-sidebar-breakpoint) {
block-size: var(--space-4);
inline-size: var(--space-4);
margin: var(--space-1) var(--space-0-5);
@@ -87,7 +91,7 @@
padding: var(--space-1);
border: 1px solid transparent;
- @include unique-max-width($stacked-editor-max-width) {
+ @include unique-max-width($small-sidebar-breakpoint) {
padding: var(--space-0-5);
}
@@ -108,7 +112,7 @@
block-size: 60px;
background-color: var(--sidebar-panel-background);
- @include unique-max-width($stacked-editor-max-width) {
+ @include unique-max-width($small-sidebar-breakpoint) {
inset-block: -4px -1px;
inset-inline-end: -8px;
block-size: 40px;
@@ -136,7 +140,7 @@
margin: 0;
}
- @include unique-max-width($stacked-editor-max-width) {
+ @include unique-max-width($small-sidebar-breakpoint) {
&.btn {
min-block-size: 32px;
min-inline-size: 32px;
diff --git a/src/assets/stylesheets/_breakpoints.scss b/src/assets/stylesheets/_breakpoints.scss
deleted file mode 100644
index ac07cac02..000000000
--- a/src/assets/stylesheets/_breakpoints.scss
+++ /dev/null
@@ -1 +0,0 @@
-$stacked-editor-max-width: 1146px;
From 0eb86a3b8e4f3b141347fd72212e29b55235f7d6 Mon Sep 17 00:00:00 2001
From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com>
Date: Fri, 23 Oct 2026 10:51:01 +0100
Subject: [PATCH 2/4] Use a smaller sidebar to stop blocks editor scrolling
I've increased the size that the smaller sidebar shows for the blocks editor.
I've only applied this to the blocks editor as it can make instructions harder to follow, especially when they contain code blocks.
---
.../InstructionsPanel.test.jsx | 1 +
.../SettingsPanel/SettingsPanel.test.jsx | 2 +
src/components/Menus/Sidebar/SidebarPanel.jsx | 27 +++--
.../Menus/Sidebar/SidebarPanel.test.jsx | 114 +++++++++++++-----
src/utils/mediaQueryBreakpoints.js | 2 -
5 files changed, 107 insertions(+), 39 deletions(-)
diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.jsx
index 218233655..0ab6cd50c 100644
--- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.jsx
+++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.jsx
@@ -558,6 +558,7 @@ describe("When instructions are not editable", () => {
renderWithProviders(, {
preloadedState: {
editor: {
+ project: {},
instructionsEditable: false,
},
instructions: {
diff --git a/src/components/Menus/Sidebar/SettingsPanel/SettingsPanel.test.jsx b/src/components/Menus/Sidebar/SettingsPanel/SettingsPanel.test.jsx
index dca618836..c69ba9863 100644
--- a/src/components/Menus/Sidebar/SettingsPanel/SettingsPanel.test.jsx
+++ b/src/components/Menus/Sidebar/SettingsPanel/SettingsPanel.test.jsx
@@ -13,6 +13,7 @@ describe("Settings panel", () => {
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
+ project: {},
isThemeable: true,
},
};
@@ -46,6 +47,7 @@ describe("Settings panel", () => {
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
+ project: {},
isThemeable: false,
},
};
diff --git a/src/components/Menus/Sidebar/SidebarPanel.jsx b/src/components/Menus/Sidebar/SidebarPanel.jsx
index 4b4ff8b9b..b0c1712d1 100644
--- a/src/components/Menus/Sidebar/SidebarPanel.jsx
+++ b/src/components/Menus/Sidebar/SidebarPanel.jsx
@@ -1,11 +1,9 @@
import React, { useContext, useState } from "react";
import ResizableWithHandle from "../../../utils/ResizableWithHandle";
+import { useSelector } from "react-redux";
import classNames from "classnames";
import PropTypes from "prop-types";
-import {
- MOBILE_MEDIA_QUERY,
- SMALL_SCREEN_MEDIA_QUERY,
-} from "../../../utils/mediaQueryBreakpoints";
+import { MOBILE_MEDIA_QUERY } from "../../../utils/mediaQueryBreakpoints";
import { useMediaQuery } from "react-responsive";
import {
SidebarActiveOptionContext,
@@ -15,6 +13,20 @@ import {
const MIN_WIDTH = "280px";
+export const SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS = {
+ code_editor_scratch: "(max-width: 1430px)",
+};
+
+export const SMALL_SIDEBAR_PROJECT_TYPE = "(max-width: 1146px)";
+
+export const sidebarStartingWidth = (projectType, defaultWidth) => {
+ const breakpoint =
+ SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS[projectType] ||
+ SMALL_SIDEBAR_PROJECT_TYPE;
+
+ return window.matchMedia(breakpoint).matches ? MIN_WIDTH : defaultWidth;
+};
+
const SidebarPanel = (props) => {
const {
children,
@@ -26,6 +38,7 @@ const SidebarPanel = (props) => {
panelRef,
defaultWidth = "320px",
} = props;
+ const projectType = useSelector((state) => state.editor.project.project_type);
const activeOption = useContext(SidebarActiveOptionContext);
const tabpanelProps = activeOption
? {
@@ -36,10 +49,8 @@ const SidebarPanel = (props) => {
}
: {};
const isMobile = useMediaQuery({ query: MOBILE_MEDIA_QUERY });
- const [initialWidth] = useState(() =>
- window.matchMedia(SMALL_SCREEN_MEDIA_QUERY).matches
- ? MIN_WIDTH
- : defaultWidth,
+ const [initialWidth] = useState(
+ sidebarStartingWidth(projectType, defaultWidth),
);
const buttonsIsEmptyArray =
diff --git a/src/components/Menus/Sidebar/SidebarPanel.test.jsx b/src/components/Menus/Sidebar/SidebarPanel.test.jsx
index 38b30ca3a..68d413730 100644
--- a/src/components/Menus/Sidebar/SidebarPanel.test.jsx
+++ b/src/components/Menus/Sidebar/SidebarPanel.test.jsx
@@ -1,32 +1,51 @@
import React from "react";
import { render, screen } from "@testing-library/react";
-import SidebarPanel from "./SidebarPanel";
-import { SMALL_SCREEN_MEDIA_QUERY } from "../../../utils/mediaQueryBreakpoints";
+import configureStore from "redux-mock-store";
+import { Provider } from "react-redux";
+import SidebarPanel, {
+ SMALL_SIDEBAR_PROJECT_TYPE,
+ SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS,
+} from "./SidebarPanel";
const Footer = () =>
footer
;
-beforeEach(() => {
- render(
-
- some content
- ,
- );
-});
+const renderWithProjectType = (ui, projectType) => {
+ const mockStore = configureStore([]);
+ const store = mockStore({
+ editor: { project: { project_type: projectType } },
+ });
+
+ return {
+ store,
+ ...render({ui}),
+ };
+};
test("Renders heading", () => {
+ renderWithProjectType(
+ some content,
+ );
expect(screen.queryByText("heading")).toBeInTheDocument();
});
test("Renders content", () => {
+ renderWithProjectType(
+ some content,
+ );
expect(screen.queryByText("some content")).toBeInTheDocument();
});
test("Renders footer", () => {
+ renderWithProjectType(
+
+ some content
+ ,
+ );
expect(screen.queryByText("footer")).toBeInTheDocument();
});
test("Renders a single button", () => {
- render(
+ renderWithProjectType(
button]}>
some content
,
@@ -35,7 +54,7 @@ test("Renders a single button", () => {
});
test("Renders arbitrary header content", () => {
- render(
+ renderWithProjectType(
a link}>
some content
,
@@ -44,7 +63,7 @@ test("Renders arbitrary header content", () => {
});
test("Renders header content alongside buttons", () => {
- render(
+ renderWithProjectType(
button]}
@@ -58,7 +77,7 @@ test("Renders header content alongside buttons", () => {
});
test("Renders multiple buttons", () => {
- render(
+ renderWithProjectType(
{
describe("Default width", () => {
const originalMatchMedia = window.matchMedia;
- const mockSmallScreen = (matches) => {
+ const mockMatchingMediaQuery = (matchingQuery) => {
window.matchMedia = (query) => ({
...originalMatchMedia(query),
- matches: matches && query === SMALL_SCREEN_MEDIA_QUERY,
+ matches: query === matchingQuery,
});
};
@@ -88,27 +107,64 @@ describe("Default width", () => {
});
test("Uses the default width on larger screens", () => {
- mockSmallScreen(false);
- render(some content);
- const panel = screen.getAllByTestId("sidebar__panel").at(-1);
- expect(panel).toHaveStyle({ width: "320px" });
+ mockMatchingMediaQuery(null);
+ renderWithProjectType(
+ some content,
+ );
+ expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
+ width: "320px",
+ });
});
test("Uses the minimum width on smaller screens", () => {
- mockSmallScreen(true);
- render(some content);
- const panel = screen.getAllByTestId("sidebar__panel").at(-1);
- expect(panel).toHaveStyle({ width: "280px" });
+ mockMatchingMediaQuery(SMALL_SIDEBAR_PROJECT_TYPE);
+ renderWithProjectType(
+ some content,
+ );
+ expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
+ width: "280px",
+ });
+ });
+
+ test("Uses the minimum width sooner for project types that need more room", () => {
+ mockMatchingMediaQuery(
+ SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS.code_editor_scratch,
+ );
+ renderWithProjectType(
+ some content,
+ "code_editor_scratch",
+ );
+ expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
+ width: "280px",
+ });
+ });
+
+ test("Uses the default width at that same size for other project types", () => {
+ mockMatchingMediaQuery(
+ SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS.code_editor_scratch,
+ );
+ renderWithProjectType(
+ some content,
+ "python",
+ );
+ expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
+ width: "320px",
+ });
});
test("Keeps its width when the screen size changes", () => {
- mockSmallScreen(true);
- const { rerender } = render(
+ mockMatchingMediaQuery(SMALL_SIDEBAR_PROJECT_TYPE);
+ const { rerender, store } = renderWithProjectType(
some content,
);
- mockSmallScreen(false);
- rerender(some content);
- const panel = screen.getAllByTestId("sidebar__panel").at(-1);
- expect(panel).toHaveStyle({ width: "280px" });
+ mockMatchingMediaQuery(null);
+ rerender(
+
+ some content
+ ,
+ );
+ expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
+ width: "280px",
+ });
});
});
diff --git a/src/utils/mediaQueryBreakpoints.js b/src/utils/mediaQueryBreakpoints.js
index 6f60bb927..1da98634b 100644
--- a/src/utils/mediaQueryBreakpoints.js
+++ b/src/utils/mediaQueryBreakpoints.js
@@ -1,4 +1,2 @@
export const MOBILE_BREAKPOINT = "600px";
export const MOBILE_MEDIA_QUERY = `(max-width: ${MOBILE_BREAKPOINT})`;
-export const SMALL_SCREEN_BREAKPOINT = "1146px";
-export const SMALL_SCREEN_MEDIA_QUERY = `(max-width: ${SMALL_SCREEN_BREAKPOINT})`;
From 000c96e0659b5f557128ca55e91054a5c57f6702 Mon Sep 17 00:00:00 2001
From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com>
Date: Fri, 23 Oct 2026 10:53:47 +0100
Subject: [PATCH 3/4] Refactor to use renderWithProviders in sidebar panel test
---
.../Menus/Sidebar/SidebarPanel.test.jsx | 59 ++++++++-----------
1 file changed, 25 insertions(+), 34 deletions(-)
diff --git a/src/components/Menus/Sidebar/SidebarPanel.test.jsx b/src/components/Menus/Sidebar/SidebarPanel.test.jsx
index 68d413730..56e61635a 100644
--- a/src/components/Menus/Sidebar/SidebarPanel.test.jsx
+++ b/src/components/Menus/Sidebar/SidebarPanel.test.jsx
@@ -1,7 +1,6 @@
import React from "react";
-import { render, screen } from "@testing-library/react";
-import configureStore from "redux-mock-store";
-import { Provider } from "react-redux";
+import { screen } from "@testing-library/react";
+import { renderWithProviders } from "../../../utils/renderWithProviders";
import SidebarPanel, {
SMALL_SIDEBAR_PROJECT_TYPE,
SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS,
@@ -9,34 +8,22 @@ import SidebarPanel, {
const Footer = () => footer
;
-const renderWithProjectType = (ui, projectType) => {
- const mockStore = configureStore([]);
- const store = mockStore({
- editor: { project: { project_type: projectType } },
- });
-
- return {
- store,
- ...render({ui}),
- };
-};
-
test("Renders heading", () => {
- renderWithProjectType(
+ renderWithProviders(
some content,
);
expect(screen.queryByText("heading")).toBeInTheDocument();
});
test("Renders content", () => {
- renderWithProjectType(
+ renderWithProviders(
some content,
);
expect(screen.queryByText("some content")).toBeInTheDocument();
});
test("Renders footer", () => {
- renderWithProjectType(
+ renderWithProviders(
some content
,
@@ -45,7 +32,7 @@ test("Renders footer", () => {
});
test("Renders a single button", () => {
- renderWithProjectType(
+ renderWithProviders(
button]}>
some content
,
@@ -54,7 +41,7 @@ test("Renders a single button", () => {
});
test("Renders arbitrary header content", () => {
- renderWithProjectType(
+ renderWithProviders(
a link}>
some content
,
@@ -63,7 +50,7 @@ test("Renders arbitrary header content", () => {
});
test("Renders header content alongside buttons", () => {
- renderWithProjectType(
+ renderWithProviders(
button]}
@@ -77,7 +64,7 @@ test("Renders header content alongside buttons", () => {
});
test("Renders multiple buttons", () => {
- renderWithProjectType(
+ renderWithProviders(
{
test("Uses the default width on larger screens", () => {
mockMatchingMediaQuery(null);
- renderWithProjectType(
+ renderWithProviders(
some content,
);
expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
@@ -118,7 +105,7 @@ describe("Default width", () => {
test("Uses the minimum width on smaller screens", () => {
mockMatchingMediaQuery(SMALL_SIDEBAR_PROJECT_TYPE);
- renderWithProjectType(
+ renderWithProviders(
some content,
);
expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
@@ -130,9 +117,13 @@ describe("Default width", () => {
mockMatchingMediaQuery(
SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS.code_editor_scratch,
);
- renderWithProjectType(
+ renderWithProviders(
some content,
- "code_editor_scratch",
+ {
+ preloadedState: {
+ editor: { project: { project_type: "code_editor_scratch" } },
+ },
+ },
);
expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
width: "280px",
@@ -143,9 +134,13 @@ describe("Default width", () => {
mockMatchingMediaQuery(
SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS.code_editor_scratch,
);
- renderWithProjectType(
+ renderWithProviders(
some content,
- "python",
+ {
+ preloadedState: {
+ editor: { project: { project_type: "python" } },
+ },
+ },
);
expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
width: "320px",
@@ -154,15 +149,11 @@ describe("Default width", () => {
test("Keeps its width when the screen size changes", () => {
mockMatchingMediaQuery(SMALL_SIDEBAR_PROJECT_TYPE);
- const { rerender, store } = renderWithProjectType(
+ const { rerender } = renderWithProviders(
some content,
);
mockMatchingMediaQuery(null);
- rerender(
-
- some content
- ,
- );
+ rerender(some content);
expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
width: "280px",
});
From 251ab54bd7afcba2f1d2d4a6e237790f2994d591 Mon Sep 17 00:00:00 2001
From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com>
Date: Fri, 23 Oct 2026 13:37:37 +0100
Subject: [PATCH 4/4] Keep sidebar breakpoint in step with the resize handle
Previously the sidebar breakpoint used its own $sidebar-gap variable
that happened to duplicate the 16px resize handle size hard-coded in
Project.scss, so changing the handle size would silently leave the
breakpoint wrong.
This change introduces a shared $resize-handle-size Sass variable,
uses it for the breakpoint calculation, and assigns it to the
--resize-handle-size custom property so both derive from one value.
Co-Authored-By: Claude Opus 5 (1M context)
---
src/assets/stylesheets/Project.scss | 3 ++-
src/assets/stylesheets/Sidebar.scss | 6 ++++--
src/assets/stylesheets/_sizes.scss | 1 +
3 files changed, 7 insertions(+), 3 deletions(-)
create mode 100644 src/assets/stylesheets/_sizes.scss
diff --git a/src/assets/stylesheets/Project.scss b/src/assets/stylesheets/Project.scss
index 207b2b318..5da294adb 100644
--- a/src/assets/stylesheets/Project.scss
+++ b/src/assets/stylesheets/Project.scss
@@ -1,4 +1,5 @@
@use "./ScratchContainer";
+@use "./sizes" as *;
.proj {
display: flex;
@@ -34,7 +35,7 @@
flex-direction: row;
overflow-y: hidden;
- --resize-handle-size: 16px;
+ --resize-handle-size: #{$resize-handle-size};
gap: var(--resize-handle-size);
margin-block-start: var(--editor-margin-block-start, var(--space-2));
diff --git a/src/assets/stylesheets/Sidebar.scss b/src/assets/stylesheets/Sidebar.scss
index 994ff43ff..4b87f4b63 100644
--- a/src/assets/stylesheets/Sidebar.scss
+++ b/src/assets/stylesheets/Sidebar.scss
@@ -1,10 +1,12 @@
@use "@raspberrypifoundation/design-system-core/scss/mixins/typography";
@use "./rpf_design_system/mixins" as *;
+@use "./sizes" as *;
$sidebar-min-width: 350px;
$block-editor-min-width: 1024px;
-$sidebar-gap: 16px;
-$small-sidebar-breakpoint: calc($sidebar-min-width + $block-editor-min-width + $sidebar-gap);
+$small-sidebar-breakpoint: calc(
+ $sidebar-min-width + $block-editor-min-width + $resize-handle-size
+);
/* Sidebar */
.sidebar {
diff --git a/src/assets/stylesheets/_sizes.scss b/src/assets/stylesheets/_sizes.scss
new file mode 100644
index 000000000..f0d8f32a3
--- /dev/null
+++ b/src/assets/stylesheets/_sizes.scss
@@ -0,0 +1 @@
+$resize-handle-size: 16px;