feat: add undo/redo buttons & tweak footer (#3832)

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
David Luzar 2021-07-15 18:48:03 +02:00 committed by GitHub
parent 685abac81a
commit 99623334d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 152 additions and 125 deletions

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
import { ColorPicker } from "../components/ColorPicker"; import { ColorPicker } from "../components/ColorPicker";
import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons"; import { trash, zoomIn, zoomOut } from "../components/icons";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
import { DarkModeToggle } from "../components/DarkModeToggle"; import { DarkModeToggle } from "../components/DarkModeToggle";
import { ZOOM_STEP } from "../constants"; import { ZOOM_STEP } from "../constants";
@ -17,6 +17,7 @@ import { getNewZoom } from "../scene/zoom";
import { AppState, NormalizedZoomValue } from "../types"; import { AppState, NormalizedZoomValue } from "../types";
import { getShortcutKey } from "../utils"; import { getShortcutKey } from "../utils";
import { register } from "./register"; import { register } from "./register";
import { Tooltip } from "../components/Tooltip";
export const actionChangeViewBackgroundColor = register({ export const actionChangeViewBackgroundColor = register({
name: "changeViewBackgroundColor", name: "changeViewBackgroundColor",
@ -108,6 +109,7 @@ export const actionZoomIn = register({
onClick={() => { onClick={() => {
updateData(null); updateData(null);
}} }}
size="small"
/> />
), ),
keyTest: (event) => keyTest: (event) =>
@ -142,6 +144,7 @@ export const actionZoomOut = register({
onClick={() => { onClick={() => {
updateData(null); updateData(null);
}} }}
size="small"
/> />
), ),
keyTest: (event) => keyTest: (event) =>
@ -168,16 +171,21 @@ export const actionResetZoom = register({
commitToHistory: false, commitToHistory: false,
}; };
}, },
PanelComponent: ({ updateData }) => ( PanelComponent: ({ updateData, appState }) => (
<Tooltip label={t("buttons.resetZoom")}>
<ToolButton <ToolButton
type="button" type="button"
icon={resetZoom} className="reset-zoom-button"
title={t("buttons.resetZoom")} title={t("buttons.resetZoom")}
aria-label={t("buttons.resetZoom")} aria-label={t("buttons.resetZoom")}
onClick={() => { onClick={() => {
updateData(null); updateData(null);
}} }}
/> size="small"
>
{(appState.zoom.value * 100).toFixed(0)}%
</ToolButton>
</Tooltip>
), ),
keyTest: (event) => keyTest: (event) =>
(event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) && (event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&

View File

@ -70,7 +70,7 @@ export const actionChangeExportScale = register({
return ( return (
<ToolButton <ToolButton
key={s} key={s}
size="s" size="small"
type="radio" type="radio"
icon={`${s}x`} icon={`${s}x`}
name="export-canvas-scale" name="export-canvas-scale"
@ -120,7 +120,7 @@ export const actionChangeExportEmbedScene = register({
> >
{t("labels.exportEmbedScene")} {t("labels.exportEmbedScene")}
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}> <Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
<div className="Tooltip-icon">{questionCircle}</div> <div className="excalidraw-tooltip-icon">{questionCircle}</div>
</Tooltip> </Tooltip>
</CheckboxItem> </CheckboxItem>
), ),

View File

@ -69,12 +69,13 @@ export const createUndoAction: ActionCreator = (history) => ({
event[KEYS.CTRL_OR_CMD] && event[KEYS.CTRL_OR_CMD] &&
event.key.toLowerCase() === KEYS.Z && event.key.toLowerCase() === KEYS.Z &&
!event.shiftKey, !event.shiftKey,
PanelComponent: ({ updateData }) => ( PanelComponent: ({ updateData, data }) => (
<ToolButton <ToolButton
type="button" type="button"
icon={undo} icon={undo}
aria-label={t("buttons.undo")} aria-label={t("buttons.undo")}
onClick={updateData} onClick={updateData}
size={data?.size || "medium"}
/> />
), ),
commitToHistory: () => false, commitToHistory: () => false,
@ -89,12 +90,13 @@ export const createRedoAction: ActionCreator = (history) => ({
event.shiftKey && event.shiftKey &&
event.key.toLowerCase() === KEYS.Z) || event.key.toLowerCase() === KEYS.Z) ||
(isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y), (isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
PanelComponent: ({ updateData }) => ( PanelComponent: ({ updateData, data }) => (
<ToolButton <ToolButton
type="button" type="button"
icon={redo} icon={redo}
aria-label={t("buttons.redo")} aria-label={t("buttons.redo")}
onClick={updateData} onClick={updateData}
size={data?.size || "medium"}
/> />
), ),
commitToHistory: () => false, commitToHistory: () => false,

View File

@ -30,8 +30,8 @@ export const actionGoToCollaborator = register({
commitToHistory: false, commitToHistory: false,
}; };
}, },
PanelComponent: ({ appState, updateData, id }) => { PanelComponent: ({ appState, updateData, data }) => {
const clientId = id; const clientId: string | undefined = data?.id;
if (!clientId) { if (!clientId) {
return null; return null;
} }

View File

@ -5,6 +5,7 @@ import {
UpdaterFn, UpdaterFn,
ActionName, ActionName,
ActionResult, ActionResult,
PanelComponentProps,
} from "./types"; } from "./types";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { AppProps, AppState } from "../types"; import { AppProps, AppState } from "../types";
@ -107,11 +108,10 @@ export class ActionManager implements ActionsManagerInterface {
); );
} }
// Id is an attribute that we can use to pass in data like keys. /**
// This is needed for dynamically generated action components * @param data additional data sent to the PanelComponent
// like the user list. We can use this key to extract more */
// data from app state. This is an alternative to generic prop hell! renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
renderAction = (name: ActionName, id?: string) => {
const canvasActions = this.app.props.UIOptions.canvasActions; const canvasActions = this.app.props.UIOptions.canvasActions;
if ( if (
@ -139,8 +139,8 @@ export class ActionManager implements ActionsManagerInterface {
elements={this.getElementsIncludingDeleted()} elements={this.getElementsIncludingDeleted()}
appState={this.getAppState()} appState={this.getAppState()}
updateData={updateData} updateData={updateData}
id={id}
appProps={this.app.props} appProps={this.app.props}
data={data}
/> />
); );
} }

View File

@ -2,6 +2,7 @@ import React from "react";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { AppState, ExcalidrawProps } from "../types"; import { AppState, ExcalidrawProps } from "../types";
import Library from "../data/library"; import Library from "../data/library";
import { ToolButtonSize } from "../components/ToolButton";
/** if false, the action should be prevented */ /** if false, the action should be prevented */
export type ActionResult = export type ActionResult =
@ -102,15 +103,17 @@ export type ActionName =
| "exportWithDarkMode" | "exportWithDarkMode"
| "toggleTheme"; | "toggleTheme";
export interface Action { export type PanelComponentProps = {
name: ActionName;
PanelComponent?: React.FC<{
elements: readonly ExcalidrawElement[]; elements: readonly ExcalidrawElement[];
appState: AppState; appState: AppState;
updateData: (formData?: any) => void; updateData: (formData?: any) => void;
appProps: ExcalidrawProps; appProps: ExcalidrawProps;
id?: string; data?: Partial<{ id: string; size: ToolButtonSize }>;
}>; };
export interface Action {
name: ActionName;
PanelComponent?: React.FC<PanelComponentProps>;
perform: ActionFn; perform: ActionFn;
keyPriority?: number; keyPriority?: number;
keyTest?: ( keyTest?: (

View File

@ -207,9 +207,6 @@ export const ZoomActions = ({
{renderAction("zoomIn")} {renderAction("zoomIn")}
{renderAction("zoomOut")} {renderAction("zoomOut")}
{renderAction("resetZoom")} {renderAction("resetZoom")}
<div style={{ marginInlineStart: 4 }}>
{(zoom.value * 100).toFixed(0)}%
</div>
</Stack.Row> </Stack.Row>
</Stack.Col> </Stack.Col>
); );

View File

@ -81,7 +81,7 @@
align-items: center; align-items: center;
} }
.Tooltip-icon { .excalidraw-tooltip-icon {
width: 1em; width: 1em;
height: 1em; height: 1em;
} }

View File

@ -73,10 +73,10 @@
} }
:root[dir="ltr"] &.layer-ui__wrapper__footer-left--transition-left { :root[dir="ltr"] &.layer-ui__wrapper__footer-left--transition-left {
transform: translate(-92px, 0); transform: translate(-76px, 0);
} }
:root[dir="rtl"] &.layer-ui__wrapper__footer-left--transition-left { :root[dir="rtl"] &.layer-ui__wrapper__footer-left--transition-left {
transform: translate(92px, 0); transform: translate(76px, 0);
} }
&.layer-ui__wrapper__footer-left--transition-bottom { &.layer-ui__wrapper__footer-left--transition-bottom {
@ -120,5 +120,15 @@
.disable-zen-mode--visible { .disable-zen-mode--visible {
pointer-events: all; pointer-events: all;
} }
.layer-ui__wrapper__footer-left {
margin-bottom: 0.2em;
}
.layer-ui__wrapper__footer-right {
margin-top: auto;
margin-bottom: auto;
margin-inline-end: 1em;
}
} }
} }

View File

@ -632,7 +632,9 @@ const LayerUI = ({
label={client.username || "Unknown user"} label={client.username || "Unknown user"}
key={clientId} key={clientId}
> >
{actionManager.renderAction("goToCollaborator", clientId)} {actionManager.renderAction("goToCollaborator", {
id: clientId,
})}
</Tooltip> </Tooltip>
))} ))}
</UserList> </UserList>
@ -665,6 +667,16 @@ const LayerUI = ({
zoom={appState.zoom} zoom={appState.zoom}
/> />
</Island> </Island>
{!viewModeEnabled && (
<div
className={clsx("undo-redo-buttons zen-mode-transition", {
"layer-ui__wrapper__footer-left--transition-bottom": zenModeEnabled,
})}
>
{actionManager.renderAction("undo", { size: "small" })}
{actionManager.renderAction("redo", { size: "small" })}
</div>
)}
</Section> </Section>
</Stack.Col> </Stack.Col>
</div> </div>

View File

@ -21,7 +21,7 @@ export const LibraryButton: React.FC<{
<label <label
className={clsx( className={clsx(
"ToolIcon ToolIcon_type_floating ToolIcon__library zen-mode-visibility", "ToolIcon ToolIcon_type_floating ToolIcon__library zen-mode-visibility",
`ToolIcon_size_m`, `ToolIcon_size_medium`,
{ {
"zen-mode-visibility--hidden": appState.zenModeEnabled, "zen-mode-visibility--hidden": appState.zenModeEnabled,
}, },

View File

@ -2,8 +2,7 @@ import "./ToolIcon.scss";
import React from "react"; import React from "react";
import clsx from "clsx"; import clsx from "clsx";
import { ToolButtonSize } from "./ToolButton";
type LockIconSize = "s" | "m";
type LockIconProps = { type LockIconProps = {
title?: string; title?: string;
@ -13,7 +12,7 @@ type LockIconProps = {
zenModeEnabled?: boolean; zenModeEnabled?: boolean;
}; };
const DEFAULT_SIZE: LockIconSize = "m"; const DEFAULT_SIZE: ToolButtonSize = "medium";
const ICONS = { const ICONS = {
CHECKED: ( CHECKED: (

View File

@ -168,10 +168,9 @@ export const MobileMenu = ({
) )
.map(([clientId, client]) => ( .map(([clientId, client]) => (
<React.Fragment key={clientId}> <React.Fragment key={clientId}>
{actionManager.renderAction( {actionManager.renderAction("goToCollaborator", {
"goToCollaborator", id: clientId,
clientId, })}
)}
</React.Fragment> </React.Fragment>
))} ))}
</UserList> </UserList>

View File

@ -4,7 +4,7 @@ import React from "react";
import clsx from "clsx"; import clsx from "clsx";
import { useExcalidrawContainer } from "./App"; import { useExcalidrawContainer } from "./App";
type ToolIconSize = "s" | "m"; export type ToolButtonSize = "small" | "medium";
type ToolButtonBaseProps = { type ToolButtonBaseProps = {
icon?: React.ReactNode; icon?: React.ReactNode;
@ -15,7 +15,7 @@ type ToolButtonBaseProps = {
title?: string; title?: string;
name?: string; name?: string;
id?: string; id?: string;
size?: ToolIconSize; size?: ToolButtonSize;
keyBindingLabel?: string; keyBindingLabel?: string;
showAriaLabel?: boolean; showAriaLabel?: boolean;
hidden?: boolean; hidden?: boolean;
@ -41,13 +41,11 @@ type ToolButtonProps =
onChange?(): void; onChange?(): void;
}); });
const DEFAULT_SIZE: ToolIconSize = "m";
export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => { export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
const { id: excalId } = useExcalidrawContainer(); const { id: excalId } = useExcalidrawContainer();
const innerRef = React.useRef(null); const innerRef = React.useRef(null);
React.useImperativeHandle(ref, () => innerRef.current); React.useImperativeHandle(ref, () => innerRef.current);
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`; const sizeCn = `ToolIcon_size_${props.size}`;
if (props.type === "button" || props.type === "icon") { if (props.type === "button" || props.type === "icon") {
return ( return (
@ -118,4 +116,5 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
ToolButton.defaultProps = { ToolButton.defaultProps = {
visible: true, visible: true,
className: "", className: "",
size: "medium",
}; };

View File

@ -60,9 +60,9 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.ToolIcon_size_s .ToolIcon__icon { .ToolIcon_size_small .ToolIcon__icon {
width: 1.4rem; width: 2rem;
height: 1.4rem; height: 2rem;
font-size: 0.8em; font-size: 0.8em;
} }

View File

@ -1,4 +1,6 @@
@import "../css/variables.module"; @import "../css/variables.module";
// container in body where the actual tooltip is appended to
.excalidraw-tooltip { .excalidraw-tooltip {
position: absolute; position: absolute;
z-index: 1000; z-index: 1000;
@ -24,8 +26,13 @@
} }
} }
.excalidraw { // wraps the element we want to apply the tooltip to
.Tooltip-icon { .excalidraw-tooltip-wrapper {
display: flex;
height: 100%;
}
.excalidraw-tooltip-icon {
width: 0.9em; width: 0.9em;
height: 0.9em; height: 0.9em;
margin-left: 5px; margin-left: 5px;
@ -35,5 +42,4 @@
@include isMobile { @include isMobile {
display: none; display: none;
} }
}
} }

View File

@ -74,6 +74,7 @@ export const Tooltip = ({ children, label, long = false }: TooltipProps) => {
return ( return (
<div <div
className="excalidraw-tooltip-wrapper"
onPointerEnter={(event) => onPointerEnter={(event) =>
updateTooltip( updateTooltip(
event.currentTarget as HTMLDivElement, event.currentTarget as HTMLDivElement,

View File

@ -414,22 +414,6 @@
&:active { &:active {
background-color: var(--button-gray-2); background-color: var(--button-gray-2);
} }
&.dropdown-select--floating {
margin: 0.5em;
}
}
.dropdown-select__language.dropdown-select--floating {
bottom: 10px;
:root[dir="ltr"] & {
right: 44px;
}
:root[dir="rtl"] & {
left: 44px;
}
} }
.zIndexButton { .zIndexButton {
@ -456,26 +440,32 @@
} }
.help-icon { .help-icon {
display: flex;
cursor: pointer; cursor: pointer;
fill: $oc-gray-6; fill: $oc-gray-6;
width: 1.5rem; width: 1.5rem;
padding: 0; padding: 0;
margin: 0; margin: 0;
margin-top: 5px;
background: none; background: none;
color: var(--icon-fill-color); color: var(--icon-fill-color);
&:hover { &:hover {
background: none; background: none;
} }
:root[dir="ltr"] & {
margin-right: 14px;
} }
:root[dir="rtl"] & { .reset-zoom-button {
margin-left: 14px; padding: 0.2em;
background: transparent;
color: var(--text-primary-color);
font-family: var(--ui-font);
} }
.undo-redo-buttons {
display: flex;
gap: 0.4em;
margin-top: auto;
margin-bottom: auto;
margin-inline-start: 0.6em;
} }
@include isMobile { @include isMobile {

View File

@ -1,23 +1,18 @@
import React from "react"; import React from "react";
import clsx from "clsx";
import * as i18n from "../../i18n"; import * as i18n from "../../i18n";
export const LanguageList = ({ export const LanguageList = ({
onChange, onChange,
languages = i18n.languages, languages = i18n.languages,
currentLangCode = i18n.getLanguage().code, currentLangCode = i18n.getLanguage().code,
floating,
}: { }: {
languages?: { code: string; label: string }[]; languages?: { code: string; label: string }[];
onChange: (langCode: i18n.Language["code"]) => void; onChange: (langCode: i18n.Language["code"]) => void;
currentLangCode?: i18n.Language["code"]; currentLangCode?: i18n.Language["code"];
floating?: boolean;
}) => ( }) => (
<React.Fragment> <React.Fragment>
<select <select
className={clsx("dropdown-select dropdown-select__language", { className="dropdown-select dropdown-select__language"
"dropdown-select--floating": floating,
})}
onChange={({ target }) => onChange(target.value)} onChange={({ target }) => onChange(target.value)}
value={currentLangCode} value={currentLangCode}
aria-label={i18n.t("buttons.selectLanguage")} aria-label={i18n.t("buttons.selectLanguage")}

View File

@ -2,12 +2,18 @@
.layer-ui__wrapper__footer-center { .layer-ui__wrapper__footer-center {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-top: auto;
margin-bottom: auto;
margin-inline-start: auto;
} }
.encrypted-icon { .encrypted-icon {
border-radius: var(--space-factor); border-radius: var(--space-factor);
color: var(--icon-green-fill-color); color: var(--icon-green-fill-color);
margin-top: 13px; margin-top: auto;
margin-bottom: auto;
margin-inline-start: auto;
margin-inline-end: 0.6em;
svg { svg {
width: 1.2rem; width: 1.2rem;

View File

@ -348,11 +348,8 @@ const ExcalidrawWrapper = () => {
const renderLanguageList = () => ( const renderLanguageList = () => (
<LanguageList <LanguageList
onChange={(langCode) => { onChange={(langCode) => setLangCode(langCode)}
setLangCode(langCode);
}}
languages={languages} languages={languages}
floating={!isMobile}
currentLangCode={langCode} currentLangCode={langCode}
/> />
); );

View File

@ -17,7 +17,7 @@ beforeEach(async () => {
mouse.reset(); mouse.reset();
await setLanguage(defaultLang); await setLanguage(defaultLang);
render(<App />); await render(<App />);
}); });
const createAndSelectOneRectangle = (angle: number = 0) => { const createAndSelectOneRectangle = (angle: number = 0) => {

View File

@ -25,7 +25,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
> >
<button <button
aria-label="Reset the canvas" aria-label="Reset the canvas"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="clear-canvas-button" data-testid="clear-canvas-button"
title="Reset the canvas" title="Reset the canvas"
type="button" type="button"
@ -53,7 +53,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
/> />
<button <button
aria-label="Load" aria-label="Load"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="load-button" data-testid="load-button"
title="Load" title="Load"
type="button" type="button"
@ -78,7 +78,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
</button> </button>
<button <button
aria-label="Export" aria-label="Export"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="json-export-button" data-testid="json-export-button"
title="Export" title="Export"
type="button" type="button"
@ -103,7 +103,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
</button> </button>
<button <button
aria-label="Save as image" aria-label="Save as image"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="image-export-button" data-testid="image-export-button"
title="Save as image" title="Save as image"
type="button" type="button"
@ -170,7 +170,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
> >
<button <button
aria-label="Dark mode" aria-label="Dark mode"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon ToolIcon--plain"
data-testid="toggle-dark-mode" data-testid="toggle-dark-mode"
title="Dark mode" title="Dark mode"
type="button" type="button"
@ -224,7 +224,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
> >
<button <button
aria-label="Reset the canvas" aria-label="Reset the canvas"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="clear-canvas-button" data-testid="clear-canvas-button"
title="Reset the canvas" title="Reset the canvas"
type="button" type="button"
@ -252,7 +252,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
/> />
<button <button
aria-label="Load" aria-label="Load"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="load-button" data-testid="load-button"
title="Load" title="Load"
type="button" type="button"
@ -277,7 +277,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
</button> </button>
<button <button
aria-label="Export" aria-label="Export"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="json-export-button" data-testid="json-export-button"
title="Export" title="Export"
type="button" type="button"
@ -302,7 +302,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
</button> </button>
<button <button
aria-label="Save as image" aria-label="Save as image"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
data-testid="image-export-button" data-testid="image-export-button"
title="Save as image" title="Save as image"
type="button" type="button"
@ -369,7 +369,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
> >
<button <button
aria-label="Dark mode" aria-label="Dark mode"
class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain" class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon ToolIcon--plain"
data-testid="toggle-dark-mode" data-testid="toggle-dark-mode"
title="Dark mode" title="Dark mode"
type="button" type="button"

View File

@ -45,14 +45,17 @@ describe("resize rectangle ellipses and diamond elements", () => {
${"se"} | ${[-30, -10]} | ${[70, 90]} | ${[elemData.x, elemData.y]} ${"se"} | ${[-30, -10]} | ${[70, 90]} | ${[elemData.x, elemData.y]}
${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]} ${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]}
${"sw"} | ${[40, -20]} | ${[60, 80]} | ${[40, 0]} ${"sw"} | ${[40, -20]} | ${[60, 80]} | ${[40, 0]}
`("resizes with handle $handle", ({ handle, move, dimensions, topLeft }) => { `(
render(<App />); "resizes with handle $handle",
async ({ handle, move, dimensions, topLeft }) => {
await render(<App />);
const rectangle = UI.createElement("rectangle", elemData); const rectangle = UI.createElement("rectangle", elemData);
resize(rectangle, handle, move); resize(rectangle, handle, move);
const element = h.elements[0]; const element = h.elements[0];
expect([element.width, element.height]).toEqual(dimensions); expect([element.width, element.height]).toEqual(dimensions);
expect([element.x, element.y]).toEqual(topLeft); expect([element.x, element.y]).toEqual(topLeft);
}); },
);
it.each` it.each`
handle | move | dimensions | topLeft handle | move | dimensions | topLeft
@ -61,8 +64,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
${"sw"} | ${[40, -20]} | ${[80, 80]} | ${[20, 0]} ${"sw"} | ${[40, -20]} | ${[80, 80]} | ${[20, 0]}
`( `(
"resizes with fixed side ratios on handle $handle", "resizes with fixed side ratios on handle $handle",
({ handle, move, dimensions, topLeft }) => { async ({ handle, move, dimensions, topLeft }) => {
render(<App />); await render(<App />);
const rectangle = UI.createElement("rectangle", elemData); const rectangle = UI.createElement("rectangle", elemData);
resize(rectangle, handle, move, { shift: true }); resize(rectangle, handle, move, { shift: true });
const element = h.elements[0]; const element = h.elements[0];
@ -79,8 +82,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
${"n"} | ${[_, 150]} | ${[50, 50]} | ${[25, 100]} ${"n"} | ${[_, 150]} | ${[50, 50]} | ${[25, 100]}
`( `(
"Flips while resizing and keeping side ratios on handle $handle", "Flips while resizing and keeping side ratios on handle $handle",
({ handle, move, dimensions, topLeft }) => { async ({ handle, move, dimensions, topLeft }) => {
render(<App />); await render(<App />);
const rectangle = UI.createElement("rectangle", elemData); const rectangle = UI.createElement("rectangle", elemData);
resize(rectangle, handle, move, { shift: true }); resize(rectangle, handle, move, { shift: true });
const element = h.elements[0]; const element = h.elements[0];
@ -95,8 +98,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
${"s"} | ${[_, -20]} | ${[100, 60]} | ${[0, 20]} ${"s"} | ${[_, -20]} | ${[100, 60]} | ${[0, 20]}
`( `(
"Resizes from center on handle $handle", "Resizes from center on handle $handle",
({ handle, move, dimensions, topLeft }) => { async ({ handle, move, dimensions, topLeft }) => {
render(<App />); await render(<App />);
const rectangle = UI.createElement("rectangle", elemData); const rectangle = UI.createElement("rectangle", elemData);
resize(rectangle, handle, move, { alt: true }); resize(rectangle, handle, move, { alt: true });
const element = h.elements[0]; const element = h.elements[0];
@ -111,8 +114,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
${"e"} | ${[-130, _]} | ${[160, 160]} | ${[-30, -30]} ${"e"} | ${[-130, _]} | ${[160, 160]} | ${[-30, -30]}
`( `(
"Resizes from center, flips and keeps side rations on handle $handle", "Resizes from center, flips and keeps side rations on handle $handle",
({ handle, move, dimensions, topLeft }) => { async ({ handle, move, dimensions, topLeft }) => {
render(<App />); await render(<App />);
const rectangle = UI.createElement("rectangle", elemData); const rectangle = UI.createElement("rectangle", elemData);
resize(rectangle, handle, move, { alt: true, shift: true }); resize(rectangle, handle, move, { alt: true, shift: true });
const element = h.elements[0]; const element = h.elements[0];