feat: zigzag fill easter egg (#6439)

This commit is contained in:
David Luzar 2023-04-10 15:38:50 +02:00 committed by GitHub
parent ec215362a1
commit e4d8ba226f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 120 additions and 62 deletions

View File

@ -1,4 +1,5 @@
import { AppState } from "../../src/types"; import { AppState } from "../../src/types";
import { trackEvent } from "../analytics";
import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { ButtonIconSelect } from "../components/ButtonIconSelect";
import { ColorPicker } from "../components/ColorPicker"; import { ColorPicker } from "../components/ColorPicker";
import { IconPicker } from "../components/IconPicker"; import { IconPicker } from "../components/IconPicker";
@ -37,6 +38,7 @@ import {
TextAlignLeftIcon, TextAlignLeftIcon,
TextAlignCenterIcon, TextAlignCenterIcon,
TextAlignRightIcon, TextAlignRightIcon,
FillZigZagIcon,
} from "../components/icons"; } from "../components/icons";
import { import {
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
@ -294,7 +296,12 @@ export const actionChangeBackgroundColor = register({
export const actionChangeFillStyle = register({ export const actionChangeFillStyle = register({
name: "changeFillStyle", name: "changeFillStyle",
trackEvent: false, trackEvent: false,
perform: (elements, appState, value) => { perform: (elements, appState, value, app) => {
trackEvent(
"element",
"changeFillStyle",
`${value} (${app.device.isMobile ? "mobile" : "desktop"})`,
);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -305,40 +312,55 @@ export const actionChangeFillStyle = register({
commitToHistory: true, commitToHistory: true,
}; };
}, },
PanelComponent: ({ elements, appState, updateData }) => ( PanelComponent: ({ elements, appState, updateData }) => {
<fieldset> const selectedElements = getSelectedElements(elements, appState);
<legend>{t("labels.fill")}</legend> const allElementsZigZag = selectedElements.every(
<ButtonIconSelect (el) => el.fillStyle === "zigzag",
options={[ );
{
value: "hachure", return (
text: t("labels.hachure"), <fieldset>
icon: FillHachureIcon, <legend>{t("labels.fill")}</legend>
}, <ButtonIconSelect
{ type="button"
value: "cross-hatch", options={[
text: t("labels.crossHatch"), {
icon: FillCrossHatchIcon, value: "hachure",
}, text: t("labels.hachure"),
{ icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
value: "solid", active: allElementsZigZag ? true : undefined,
text: t("labels.solid"), },
icon: FillSolidIcon, {
}, value: "cross-hatch",
]} text: t("labels.crossHatch"),
group="fill" icon: FillCrossHatchIcon,
value={getFormValue( },
elements, {
appState, value: "solid",
(element) => element.fillStyle, text: t("labels.solid"),
appState.currentItemFillStyle, icon: FillSolidIcon,
)} },
onChange={(value) => { ]}
updateData(value); value={getFormValue(
}} elements,
/> appState,
</fieldset> (element) => element.fillStyle,
), appState.currentItemFillStyle,
)}
onClick={(value, event) => {
const nextValue =
event.altKey &&
value === "hachure" &&
selectedElements.every((el) => el.fillStyle === "hachure")
? "zigzag"
: value;
updateData(nextValue);
}}
/>
</fieldset>
);
},
}); });
export const actionChangeStrokeWidth = register({ export const actionChangeStrokeWidth = register({

View File

@ -1,33 +1,59 @@
import clsx from "clsx"; import clsx from "clsx";
// TODO: It might be "clever" to add option.icon to the existing component <ButtonSelect /> // TODO: It might be "clever" to add option.icon to the existing component <ButtonSelect />
export const ButtonIconSelect = <T extends Object>({ export const ButtonIconSelect = <T extends Object>(
options, props: {
value, options: {
onChange, value: T;
group, text: string;
}: { icon: JSX.Element;
options: { value: T; text: string; icon: JSX.Element; testId?: string }[]; testId?: string;
value: T | null; /** if not supplied, defaults to value identity check */
onChange: (value: T) => void; active?: boolean;
group: string; }[];
}) => ( value: T | null;
type?: "radio" | "button";
} & (
| { type?: "radio"; group: string; onChange: (value: T) => void }
| {
type: "button";
onClick: (
value: T,
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => void;
}
),
) => (
<div className="buttonList buttonListIcon"> <div className="buttonList buttonListIcon">
{options.map((option) => ( {props.options.map((option) =>
<label props.type === "button" ? (
key={option.text} <button
className={clsx({ active: value === option.value })} key={option.text}
title={option.text} onClick={(event) => props.onClick(option.value, event)}
> className={clsx({
<input active: option.active ?? props.value === option.value,
type="radio" })}
name={group}
onChange={() => onChange(option.value)}
checked={value === option.value}
data-testid={option.testId} data-testid={option.testId}
/> title={option.text}
{option.icon} >
</label> {option.icon}
))} </button>
) : (
<label
key={option.text}
className={clsx({ active: props.value === option.value })}
title={option.text}
>
<input
type="radio"
name={props.group}
onChange={() => props.onChange(option.value)}
checked={props.value === option.value}
data-testid={option.testId}
/>
{option.icon}
</label>
),
)}
</div> </div>
); );

View File

@ -1008,6 +1008,13 @@ export const UngroupIcon = React.memo(({ theme }: { theme: Theme }) =>
), ),
); );
export const FillZigZagIcon = createIcon(
<g strokeWidth={1.25}>
<path d="M5.879 2.625h8.242a3.27 3.27 0 0 1 3.254 3.254v8.242a3.27 3.27 0 0 1-3.254 3.254H5.88a3.27 3.27 0 0 1-3.254-3.254V5.88A3.27 3.27 0 0 1 5.88 2.626l-.001-.001ZM4.518 16.118l7.608-12.83m.198 13.934 5.051-9.897M2.778 9.675l9.348-6.387m-7.608 12.83 12.857-8.793" />
</g>,
modifiedTablerIconProps,
);
export const FillHachureIcon = createIcon( export const FillHachureIcon = createIcon(
<> <>
<path <path

View File

@ -155,6 +155,9 @@
margin: 1px; margin: 1px;
} }
.welcome-screen-menu-item:focus-visible,
.dropdown-menu-item:focus-visible,
button:focus-visible,
.buttonList label:focus-within, .buttonList label:focus-within,
input:focus-visible { input:focus-visible {
outline: transparent; outline: transparent;

View File

@ -9,7 +9,7 @@ import {
import { MarkNonNullable, ValueOf } from "../utility-types"; import { MarkNonNullable, ValueOf } from "../utility-types";
export type ChartType = "bar" | "line"; export type ChartType = "bar" | "line";
export type FillStyle = "hachure" | "cross-hatch" | "solid"; export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
export type FontFamilyKeys = keyof typeof FONT_FAMILY; export type FontFamilyKeys = keyof typeof FONT_FAMILY;
export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys]; export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys];
export type Theme = typeof THEME[keyof typeof THEME]; export type Theme = typeof THEME[keyof typeof THEME];