Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/assets/stylesheets/Project.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@use "./ScratchContainer";
@use "./sizes" as *;

.proj {
display: flex;
Expand Down Expand Up @@ -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));
Expand Down
16 changes: 11 additions & 5 deletions src/assets/stylesheets/Sidebar.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
@use "@raspberrypifoundation/design-system-core/scss/mixins/typography";
@use "./rpf_design_system/mixins" as *;
@use "./breakpoints" as *;
@use "./sizes" as *;

$sidebar-min-width: 350px;
$block-editor-min-width: 1024px;
$small-sidebar-breakpoint: calc(
$sidebar-min-width + $block-editor-min-width + $resize-handle-size
);

/* Sidebar */
.sidebar {
Expand Down Expand Up @@ -28,7 +34,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);
Expand Down Expand Up @@ -87,7 +93,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);
}

Expand All @@ -108,7 +114,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;
Expand Down Expand Up @@ -136,7 +142,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;
Expand Down
1 change: 0 additions & 1 deletion src/assets/stylesheets/_breakpoints.scss

This file was deleted.

1 change: 1 addition & 0 deletions src/assets/stylesheets/_sizes.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$resize-handle-size: 16px;
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ describe("When instructions are not editable", () => {
renderWithProviders(<InstructionsPanel />, {
preloadedState: {
editor: {
project: {},
instructionsEditable: false,
},
instructions: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe("Settings panel", () => {
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
project: {},
isThemeable: true,
},
};
Expand Down Expand Up @@ -46,6 +47,7 @@ describe("Settings panel", () => {
const mockStore = configureStore(middlewares);
const initialState = {
editor: {
project: {},
isThemeable: false,
},
};
Expand Down
27 changes: 19 additions & 8 deletions src/components/Menus/Sidebar/SidebarPanel.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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
? {
Expand All @@ -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 =
Expand Down
107 changes: 77 additions & 30 deletions src/components/Menus/Sidebar/SidebarPanel.test.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import SidebarPanel from "./SidebarPanel";
import { SMALL_SCREEN_MEDIA_QUERY } from "../../../utils/mediaQueryBreakpoints";
import { screen } from "@testing-library/react";
import { renderWithProviders } from "../../../utils/renderWithProviders";
import SidebarPanel, {
SMALL_SIDEBAR_PROJECT_TYPE,
SMALL_SIDEBAR_PROJECT_TYPE_BREAKPOINTS,
} from "./SidebarPanel";

const Footer = () => <p>footer</p>;

beforeEach(() => {
render(
<SidebarPanel heading="heading" Footer={Footer}>
some content
</SidebarPanel>,
);
});

test("Renders heading", () => {
renderWithProviders(
<SidebarPanel heading="heading">some content</SidebarPanel>,
);
expect(screen.queryByText("heading")).toBeInTheDocument();
});

test("Renders content", () => {
renderWithProviders(
<SidebarPanel heading="heading">some content</SidebarPanel>,
);
expect(screen.queryByText("some content")).toBeInTheDocument();
});

test("Renders footer", () => {
renderWithProviders(
<SidebarPanel heading="heading" Footer={Footer}>
some content
</SidebarPanel>,
);
expect(screen.queryByText("footer")).toBeInTheDocument();
});

test("Renders a single button", () => {
render(
renderWithProviders(
<SidebarPanel heading="heading" buttons={[<button key="1">button</button>]}>
some content
</SidebarPanel>,
Expand All @@ -35,7 +41,7 @@ test("Renders a single button", () => {
});

test("Renders arbitrary header content", () => {
render(
renderWithProviders(
<SidebarPanel heading="heading" headerContent={<a href="/x">a link</a>}>
some content
</SidebarPanel>,
Expand All @@ -44,7 +50,7 @@ test("Renders arbitrary header content", () => {
});

test("Renders header content alongside buttons", () => {
render(
renderWithProviders(
<SidebarPanel
heading="heading"
buttons={[<button key="1">button</button>]}
Expand All @@ -58,7 +64,7 @@ test("Renders header content alongside buttons", () => {
});

test("Renders multiple buttons", () => {
render(
renderWithProviders(
<SidebarPanel
heading="heading"
buttons={[
Expand All @@ -76,10 +82,10 @@ test("Renders multiple buttons", () => {
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,
});
};

Expand All @@ -88,27 +94,68 @@ describe("Default width", () => {
});

test("Uses the default width on larger screens", () => {
mockSmallScreen(false);
render(<SidebarPanel heading="wide panel">some content</SidebarPanel>);
const panel = screen.getAllByTestId("sidebar__panel").at(-1);
expect(panel).toHaveStyle({ width: "320px" });
mockMatchingMediaQuery(null);
renderWithProviders(
<SidebarPanel heading="wide panel">some content</SidebarPanel>,
);
expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
width: "320px",
});
});

test("Uses the minimum width on smaller screens", () => {
mockSmallScreen(true);
render(<SidebarPanel heading="narrow panel">some content</SidebarPanel>);
const panel = screen.getAllByTestId("sidebar__panel").at(-1);
expect(panel).toHaveStyle({ width: "280px" });
mockMatchingMediaQuery(SMALL_SIDEBAR_PROJECT_TYPE);
renderWithProviders(
<SidebarPanel heading="narrow panel">some content</SidebarPanel>,
);
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,
);
renderWithProviders(
<SidebarPanel heading="scratch panel">some content</SidebarPanel>,
{
preloadedState: {
editor: { project: { project_type: "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,
);
renderWithProviders(
<SidebarPanel heading="python panel">some content</SidebarPanel>,
{
preloadedState: {
editor: { project: { project_type: "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 } = renderWithProviders(
<SidebarPanel heading="resized panel">some content</SidebarPanel>,
);
mockSmallScreen(false);
mockMatchingMediaQuery(null);
rerender(<SidebarPanel heading="resized panel">some content</SidebarPanel>);
const panel = screen.getAllByTestId("sidebar__panel").at(-1);
expect(panel).toHaveStyle({ width: "280px" });
expect(screen.getByTestId("sidebar__panel")).toHaveStyle({
width: "280px",
});
});
});
2 changes: 0 additions & 2 deletions src/utils/mediaQueryBreakpoints.js
Original file line number Diff line number Diff line change
@@ -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})`;
Loading