use icons for toggle labels (#2315)

This commit is contained in:
Noel Schnierer 2020-11-01 20:08:48 +01:00 committed by GitHub
parent 856ab50090
commit 7491fcc3f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 338 additions and 26 deletions

View File

@ -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 }) => (
<fieldset>
<legend>{t("labels.fill")}</legend>
<ButtonSelect
<ButtonIconSelect
options={[
{ value: "hachure", text: t("labels.hachure") },
{ value: "cross-hatch", text: t("labels.crossHatch") },
{ value: "solid", text: t("labels.solid") },
{
value: "hachure",
text: t("labels.hachure"),
icon: <FillHachureIcon appearance={appState.appearance} />,
},
{
value: "cross-hatch",
text: t("labels.crossHatch"),
icon: <FillCrossHatchIcon appearance={appState.appearance} />,
},
{
value: "solid",
text: t("labels.solid"),
icon: <FillSolidIcon appearance={appState.appearance} />,
},
]}
group="fill"
value={getFormValue(
@ -178,12 +205,39 @@ export const actionChangeStrokeWidth = register({
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.strokeWidth")}</legend>
<ButtonSelect
<ButtonIconSelect
group="stroke-width"
options={[
{ value: 1, text: t("labels.thin") },
{ value: 2, text: t("labels.bold") },
{ value: 4, text: t("labels.extraBold") },
{
value: 1,
text: t("labels.thin"),
icon: (
<StrokeWidthIcon
appearance={appState.appearance}
strokeWidth={2}
/>
),
},
{
value: 2,
text: t("labels.bold"),
icon: (
<StrokeWidthIcon
appearance={appState.appearance}
strokeWidth={6}
/>
),
},
{
value: 4,
text: t("labels.extraBold"),
icon: (
<StrokeWidthIcon
appearance={appState.appearance}
strokeWidth={10}
/>
),
},
]}
value={getFormValue(
elements,
@ -214,12 +268,24 @@ export const actionChangeSloppiness = register({
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.sloppiness")}</legend>
<ButtonSelect
<ButtonIconSelect
group="sloppiness"
options={[
{ value: 0, text: t("labels.architect") },
{ value: 1, text: t("labels.artist") },
{ value: 2, text: t("labels.cartoonist") },
{
value: 0,
text: t("labels.architect"),
icon: <SloppinessArchitectIcon appearance={appState.appearance} />,
},
{
value: 1,
text: t("labels.artist"),
icon: <SloppinessArtistIcon appearance={appState.appearance} />,
},
{
value: 2,
text: t("labels.cartoonist"),
icon: <SloppinessCartoonistIcon appearance={appState.appearance} />,
},
]}
value={getFormValue(
elements,
@ -249,12 +315,24 @@ export const actionChangeStrokeStyle = register({
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.strokeStyle")}</legend>
<ButtonSelect
<ButtonIconSelect
group="strokeStyle"
options={[
{ value: "solid", text: t("labels.strokeStyle_solid") },
{ value: "dashed", text: t("labels.strokeStyle_dashed") },
{ value: "dotted", text: t("labels.strokeStyle_dotted") },
{
value: "solid",
text: t("labels.strokeStyle_solid"),
icon: <StrokeStyleSolidIcon appearance={appState.appearance} />,
},
{
value: "dashed",
text: t("labels.strokeStyle_dashed"),
icon: <StrokeStyleDashedIcon appearance={appState.appearance} />,
},
{
value: "dotted",
text: t("labels.strokeStyle_dotted"),
icon: <StrokeStyleDottedIcon appearance={appState.appearance} />,
},
]}
value={getFormValue(
elements,
@ -488,11 +566,19 @@ export const actionChangeSharpness = register({
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.edges")}</legend>
<ButtonSelect
<ButtonIconSelect
group="edges"
options={[
{ value: "sharp", text: t("labels.sharp") },
{ value: "round", text: t("labels.round") },
{
value: "sharp",
text: t("labels.sharp"),
icon: <EdgeSharpIcon appearance={appState.appearance} />,
},
{
value: "round",
text: t("labels.round"),
icon: <EdgeRoundIcon appearance={appState.appearance} />,
},
]}
value={getFormValue(
elements,

View File

@ -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 <ButtonSelect />
export const ButtonIconSelect = <T extends Object>({
options,
value,
onChange,
group,
}: {
options: { value: T; text: string; icon: JSX.Element }[];
value: T | null;
onChange: (value: T) => void;
group: string;
}) => (
<div className="buttonList buttonListIcon">
{options.map((option) => (
<label
key={option.text}
className={clsx({ active: value === option.value })}
title={option.text}
>
<input
type="radio"
name={group}
onChange={() => onChange(option.value)}
checked={value === option.value ? true : false}
/>
{option.icon}
</label>
))}
</div>
);

View File

@ -504,3 +504,180 @@ export const UngroupIcon = React.memo(
{ width: 182, height: 182 },
),
);
export const FillHachureIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<g stroke={iconFillColor(appearance)} fill="none">
<path d="M0 0s0 0 0 0m0 0s0 0 0 0m.133 12.04L10.63-.033M.133 12.04L10.63-.034M2.234 21.818L21.26-.07M2.234 21.818L21.26-.07m-8.395 21.852L31.89-.103M12.865 21.783L31.89-.103m-8.395 21.852L41.208 1.37M23.495 21.75L41.208 1.37m-7.083 20.343l7.216-8.302m-7.216 8.302l7.216-8.302" />
<path
d="M0 0h40M0 0h40m0 0v20m0-20v20m0 0H0m40 0H0m0 0V0m0 20V0"
strokeWidth={2}
/>
</g>,
{ width: 40, height: 20 },
),
);
export const FillCrossHatchIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<g stroke={iconFillColor(appearance)} fill="none">
<path d="M0 0s0 0 0 0m0 0s0 0 0 0m.133 12.04L10.63-.033M.133 12.04L10.63-.034M2.234 21.818L21.26-.07M2.234 21.818L21.26-.07m-8.395 21.852L31.89-.103M12.865 21.783C17.87 16.025 22.875 10.266 31.89-.103m-8.395 21.852L41.208 1.37M23.495 21.75L41.208 1.37m-7.083 20.343l7.216-8.302m-7.216 8.302l7.216-8.302M-.09 19.92s0 0 0 0m0 0s0 0 0 0m12.04-.133L-.126 9.29m12.075 10.497L-.126 9.29m24.871 11.02C19.872 16.075 15 11.84.595-.684m24.15 20.994L.595-.684m36.19 20.861L12.636-.817m24.15 20.994L12.636-.817m30.909 16.269L24.676-.95m18.868 16.402L24.676-.95m18.833 5.771L37.472-.427m6.037 5.248L37.472-.427" />
<path
d="M0 0h40M0 0h40m0 0v20m0-20v20m0 0H0m40 0H0m0 0V0m0 20V0"
strokeWidth={2}
/>
</g>,
{ width: 40, height: 20 },
),
);
export const FillSolidIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<>
<path d="M0 0h120v60H0" strokeWidth={0} />
<path
d="M0 0h40M0 0h40m0 0v20m0-20v20m0 0H0m40 0H0m0 0V0m0 20V0"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>
</>,
{ width: 40, height: 20 },
),
);
export const StrokeWidthIcon = React.memo(
({
appearance,
strokeWidth,
}: {
appearance: "light" | "dark";
strokeWidth: number;
}) =>
createIcon(
<path
d="M0 10h40M0 10h40"
stroke={iconFillColor(appearance)}
strokeWidth={strokeWidth}
fill="none"
/>,
{ width: 40, height: 20 },
),
);
export const StrokeStyleSolidIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<path
d="M0 10h40M0 10h40"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>,
{ width: 40, height: 20 },
),
);
export const StrokeStyleDashedIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<path
d="M3.286 9.998h32.759"
stroke={iconFillColor(appearance)}
strokeWidth={2.5}
fill="none"
strokeDasharray="12 8"
/>,
{ width: 40, height: 20 },
),
);
export const StrokeStyleDottedIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<path
d="M0 10h40M0 10h40"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
strokeDasharray="3 6"
/>,
{ width: 40, height: 20 },
),
);
export const SloppinessArchitectIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<path
d="M.268 17.938C4.05 15.093 19.414.725 22.96.868c3.547.143-4.149 16.266-1.41 17.928 2.738 1.662 14.866-6.632 17.84-7.958m-39.123 7.1C4.05 15.093 19.414.725 22.96.868c3.547.143-4.149 16.266-1.41 17.928 2.738 1.662 14.866-6.632 17.84-7.958"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>,
{ width: 40, height: 20 },
),
);
export const SloppinessArtistIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<path
d="M2.663 18.134c3.963-2.578 18.855-12.098 22.675-12.68 3.82-.58-1.966 8.367.242 9.196 2.209.828 10.649-3.14 13.01-4.224M7.037 15.474c4.013-2.198 14.19-14.648 17.18-14.32 2.99.329-1.749 14.286.759 16.292 2.507 2.006 12.284-2.68 14.286-4.256"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>,
{ width: 40, height: 20 },
),
);
export const SloppinessCartoonistIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<>
<path
d="M1.944 17.15C6.056 14.637 22.368 1.86 26.615 2.083c4.248.223-.992 14.695.815 16.406 1.807 1.71 8.355-5.117 10.026-6.14m-35.512 4.8C6.056 14.637 22.368 1.86 26.615 2.083c4.248.223-.992 14.695.815 16.406 1.807 1.71 8.355-5.117 10.026-6.14"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>
<path
d="M3.114 10.534c2.737-1.395 12.854-8.814 16.42-8.368 3.568.445 2.35 10.282 4.984 11.04 2.635.756 9.019-5.416 10.822-6.5M3.114 10.535c2.737-1.395 12.854-8.814 16.42-8.368 3.568.445 2.35 10.282 4.984 11.04 2.635.756 9.019-5.416 10.822-6.5"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>
</>,
{ width: 40, height: 20 },
),
);
export const EdgeSharpIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<path
d="M9.18 19.68V6.346m0 13.336V6.345m0 0h29.599m-29.6 0h29.6"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>,
{ width: 40, height: 20 },
),
);
export const EdgeRoundIcon = React.memo(
({ appearance }: { appearance: "light" | "dark" }) =>
createIcon(
<path
d="M9.444 19.537c.484-2.119-2.1-10.449 2.904-12.71 5.004-2.263 22.601-.72 27.121-.863M9.444 19.537c.484-2.119-2.1-10.449 2.904-12.71 5.004-2.263 22.601-.72 27.121-.863"
stroke={iconFillColor(appearance)}
strokeWidth={2}
fill="none"
/>,
{ width: 40, height: 20 },
),
);

View File

@ -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;

View File

@ -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" },