From 7491fcc3f397f731498c0f279e48b25428a10003 Mon Sep 17 00:00:00 2001 From: Noel Schnierer Date: Sun, 1 Nov 2020 20:08:48 +0100 Subject: [PATCH] use icons for toggle labels (#2315) --- src/actions/actionProperties.tsx | 124 ++++++++++++++++--- src/components/ButtonIconSelect.tsx | 33 ++++++ src/components/icons.tsx | 177 ++++++++++++++++++++++++++++ src/css/styles.scss | 16 +++ src/tests/regressionTests.test.tsx | 14 +-- 5 files changed, 338 insertions(+), 26 deletions(-) create mode 100644 src/components/ButtonIconSelect.tsx diff --git a/src/actions/actionProperties.tsx b/src/actions/actionProperties.tsx index 7ae58578..c2834101 100644 --- a/src/actions/actionProperties.tsx +++ b/src/actions/actionProperties.tsx @@ -12,6 +12,7 @@ import { canChangeSharpness, } from "../scene"; import { ButtonSelect } from "../components/ButtonSelect"; +import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { isTextElement, redrawTextBoundingBox, @@ -25,6 +26,20 @@ import { register } from "./register"; import { newElementWith } from "../element/mutateElement"; import { DEFAULT_FONT_SIZE, DEFAULT_FONT_FAMILY } from "../constants"; import { randomInteger } from "../random"; +import { + FillHachureIcon, + FillCrossHatchIcon, + FillSolidIcon, + StrokeWidthIcon, + StrokeStyleSolidIcon, + StrokeStyleDashedIcon, + StrokeStyleDottedIcon, + EdgeSharpIcon, + EdgeRoundIcon, + SloppinessArchitectIcon, + SloppinessArtistIcon, + SloppinessCartoonistIcon, +} from "../components/icons"; const changeProperty = ( elements: readonly ExcalidrawElement[], @@ -141,11 +156,23 @@ export const actionChangeFillStyle = register({ PanelComponent: ({ elements, appState, updateData }) => (
{t("labels.fill")} - , + }, + { + value: "cross-hatch", + text: t("labels.crossHatch"), + icon: , + }, + { + value: "solid", + text: t("labels.solid"), + icon: , + }, ]} group="fill" value={getFormValue( @@ -178,12 +205,39 @@ export const actionChangeStrokeWidth = register({ PanelComponent: ({ elements, appState, updateData }) => (
{t("labels.strokeWidth")} - + ), + }, + { + value: 2, + text: t("labels.bold"), + icon: ( + + ), + }, + { + value: 4, + text: t("labels.extraBold"), + icon: ( + + ), + }, ]} value={getFormValue( elements, @@ -214,12 +268,24 @@ export const actionChangeSloppiness = register({ PanelComponent: ({ elements, appState, updateData }) => (
{t("labels.sloppiness")} - , + }, + { + value: 1, + text: t("labels.artist"), + icon: , + }, + { + value: 2, + text: t("labels.cartoonist"), + icon: , + }, ]} value={getFormValue( elements, @@ -249,12 +315,24 @@ export const actionChangeStrokeStyle = register({ PanelComponent: ({ elements, appState, updateData }) => (
{t("labels.strokeStyle")} - , + }, + { + value: "dashed", + text: t("labels.strokeStyle_dashed"), + icon: , + }, + { + value: "dotted", + text: t("labels.strokeStyle_dotted"), + icon: , + }, ]} value={getFormValue( elements, @@ -488,11 +566,19 @@ export const actionChangeSharpness = register({ PanelComponent: ({ elements, appState, updateData }) => (
{t("labels.edges")} - , + }, + { + value: "round", + text: t("labels.round"), + icon: , + }, ]} value={getFormValue( elements, diff --git a/src/components/ButtonIconSelect.tsx b/src/components/ButtonIconSelect.tsx new file mode 100644 index 00000000..74d2e018 --- /dev/null +++ b/src/components/ButtonIconSelect.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import clsx from "clsx"; + +// TODO: It might be "clever" to add option.icon to the existing component +export const ButtonIconSelect = ({ + options, + value, + onChange, + group, +}: { + options: { value: T; text: string; icon: JSX.Element }[]; + value: T | null; + onChange: (value: T) => void; + group: string; +}) => ( +
+ {options.map((option) => ( + + ))} +
+); diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 832e3d9c..3cec715b 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -504,3 +504,180 @@ export const UngroupIcon = React.memo( { width: 182, height: 182 }, ), ); + +export const FillHachureIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + + + + , + { width: 40, height: 20 }, + ), +); + +export const FillCrossHatchIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + + + + , + { width: 40, height: 20 }, + ), +); + +export const FillSolidIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + <> + + + , + { width: 40, height: 20 }, + ), +); + +export const StrokeWidthIcon = React.memo( + ({ + appearance, + strokeWidth, + }: { + appearance: "light" | "dark"; + strokeWidth: number; + }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); + +export const StrokeStyleSolidIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); + +export const StrokeStyleDashedIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); + +export const StrokeStyleDottedIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); + +export const SloppinessArchitectIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); + +export const SloppinessArtistIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); + +export const SloppinessCartoonistIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + <> + + + , + { width: 40, height: 20 }, + ), +); + +export const EdgeSharpIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); + +export const EdgeRoundIcon = React.memo( + ({ appearance }: { appearance: "light" | "dark" }) => + createIcon( + , + { width: 40, height: 20 }, + ), +); diff --git a/src/css/styles.scss b/src/css/styles.scss index 730d7e07..2350b9f5 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -172,6 +172,22 @@ } } + .buttonList.buttonListIcon { + label { + display: inline-flex; + justify-content: center; + align-items: center; + svg { + width: 36px; + height: 18px; + opacity: 0.6; + } + &.active svg { + opacity: 1; + } + } + } + .App-bottom-bar { position: absolute; top: 0; diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index c4a84cdd..b4dee6d8 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -513,18 +513,18 @@ describe("regression tests", () => { // select rectangle tool to show properties menu UI.clickTool("rectangle"); // english lang should display `hachure` label - expect(screen.queryByText(/hachure/i)).not.toBeNull(); + expect(screen.queryByTitle(/hachure/i)).not.toBeNull(); fireEvent.change(document.querySelector(".dropdown-select__language")!, { target: { value: "de-DE" }, }); // switching to german, `hachure` label should no longer exist - await waitFor(() => expect(screen.queryByText(/hachure/i)).toBeNull()); + await waitFor(() => expect(screen.queryByTitle(/hachure/i)).toBeNull()); // reset language fireEvent.change(document.querySelector(".dropdown-select__language")!, { target: { value: "en" }, }); // switching back to English - await waitFor(() => expect(screen.queryByText(/hachure/i)).not.toBeNull()); + await waitFor(() => expect(screen.queryByTitle(/hachure/i)).not.toBeNull()); }); it("make a group and duplicate it", () => { @@ -877,13 +877,13 @@ describe("regression tests", () => { clickLabeledElement("Background"); clickLabeledElement("#e64980"); // Fill style - fireEvent.click(screen.getByLabelText("Cross-hatch")); + fireEvent.click(screen.getByTitle("Cross-hatch")); // Stroke width - fireEvent.click(screen.getByLabelText("Bold")); + fireEvent.click(screen.getByTitle("Bold")); // Stroke style - fireEvent.click(screen.getByLabelText("Dotted")); + fireEvent.click(screen.getByTitle("Dotted")); // Roughness - fireEvent.click(screen.getByLabelText("Cartoonist")); + fireEvent.click(screen.getByTitle("Cartoonist")); // Opacity fireEvent.change(screen.getByLabelText("Opacity"), { target: { value: "60" },