mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-02 03:25:53 +01:00
Some a11y fixes (#534)
* Rename ToolIcon to ToolButton It makes more semantic sense * Label and keyboard shortcuts announcement * Refactor common props for ToolButton * Better doc outline and form controls * Adjust color picker * Styling fixes Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
This commit is contained in:
parent
5fd6c4d853
commit
69061e20ac
@ -81,7 +81,7 @@
|
|||||||
<noscript>
|
<noscript>
|
||||||
You need to enable JavaScript to run this app.
|
You need to enable JavaScript to run this app.
|
||||||
</noscript>
|
</noscript>
|
||||||
|
<h1 class="visually-hidden">Excalidraw</h1>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<!-- https://github.com/tholman/github-corners -->
|
<!-- https://github.com/tholman/github-corners -->
|
||||||
|
@ -3,7 +3,7 @@ import { Action } from "./types";
|
|||||||
import { ColorPicker } from "../components/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { trash } from "../components/icons";
|
import { trash } from "../components/icons";
|
||||||
import { ToolIcon } from "../components/ToolIcon";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor: Action = {
|
export const actionChangeViewBackgroundColor: Action = {
|
||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
@ -14,6 +14,7 @@ export const actionChangeViewBackgroundColor: Action = {
|
|||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
|
label="Canvas Background"
|
||||||
type="canvasBackground"
|
type="canvasBackground"
|
||||||
color={appState.viewBackgroundColor}
|
color={appState.viewBackgroundColor}
|
||||||
onChange={color => updateData(color)}
|
onChange={color => updateData(color)}
|
||||||
@ -32,7 +33,7 @@ export const actionClearCanvas: Action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData, t }) => (
|
PanelComponent: ({ updateData, t }) => (
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={trash}
|
icon={trash}
|
||||||
title={t("buttons.clearReset")}
|
title={t("buttons.clearReset")}
|
||||||
|
@ -3,7 +3,7 @@ import { Action } from "./types";
|
|||||||
import { EditableText } from "../components/EditableText";
|
import { EditableText } from "../components/EditableText";
|
||||||
import { saveAsJSON, loadFromJSON } from "../scene";
|
import { saveAsJSON, loadFromJSON } from "../scene";
|
||||||
import { load, save } from "../components/icons";
|
import { load, save } from "../components/icons";
|
||||||
import { ToolIcon } from "../components/ToolIcon";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
|
||||||
export const actionChangeProjectName: Action = {
|
export const actionChangeProjectName: Action = {
|
||||||
name: "changeProjectName",
|
name: "changeProjectName",
|
||||||
@ -44,7 +44,7 @@ export const actionSaveScene: Action = {
|
|||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData, t }) => (
|
PanelComponent: ({ updateData, t }) => (
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={save}
|
icon={save}
|
||||||
title={t("buttons.save")}
|
title={t("buttons.save")}
|
||||||
@ -64,7 +64,7 @@ export const actionLoadScene: Action = {
|
|||||||
return { elements: loadedElements, appState: loadedAppState };
|
return { elements: loadedElements, appState: loadedAppState };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData, t }) => (
|
PanelComponent: ({ updateData, t }) => (
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={load}
|
icon={load}
|
||||||
title={t("buttons.load")}
|
title={t("buttons.load")}
|
||||||
|
@ -47,9 +47,10 @@ export const actionChangeStrokeColor: Action = {
|
|||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<>
|
||||||
<h5>{t("labels.stroke")}</h5>
|
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
type="elementStroke"
|
type="elementStroke"
|
||||||
|
label={t("labels.stroke")}
|
||||||
color={getFormValue(
|
color={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
@ -76,9 +77,10 @@ export const actionChangeBackgroundColor: Action = {
|
|||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<>
|
||||||
<h5>{t("labels.background")}</h5>
|
<h3 aria-hidden="true">{t("labels.background")}</h3>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
type="elementBackground"
|
type="elementBackground"
|
||||||
|
label={t("labels.background")}
|
||||||
color={getFormValue(
|
color={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
@ -103,14 +105,15 @@ export const actionChangeFillStyle: Action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<fieldset>
|
||||||
<h5>{t("labels.fill")}</h5>
|
<legend>{t("labels.fill")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
options={[
|
options={[
|
||||||
{ value: "solid", text: t("labels.solid") },
|
{ value: "solid", text: t("labels.solid") },
|
||||||
{ value: "hachure", text: t("labels.hachure") },
|
{ value: "hachure", text: t("labels.hachure") },
|
||||||
{ value: "cross-hatch", text: t("labels.crossHatch") },
|
{ value: "cross-hatch", text: t("labels.crossHatch") },
|
||||||
]}
|
]}
|
||||||
|
group="fill"
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
@ -120,7 +123,7 @@ export const actionChangeFillStyle: Action = {
|
|||||||
updateData(value);
|
updateData(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</fieldset>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -136,9 +139,10 @@ export const actionChangeStrokeWidth: Action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<fieldset>
|
||||||
<h5>{t("labels.strokeWidth")}</h5>
|
<legend>{t("labels.strokeWidth")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
|
group="stroke-width"
|
||||||
options={[
|
options={[
|
||||||
{ value: 1, text: t("labels.thin") },
|
{ value: 1, text: t("labels.thin") },
|
||||||
{ value: 2, text: t("labels.bold") },
|
{ value: 2, text: t("labels.bold") },
|
||||||
@ -151,7 +155,7 @@ export const actionChangeStrokeWidth: Action = {
|
|||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</fieldset>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -167,9 +171,10 @@ export const actionChangeSloppiness: Action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<fieldset>
|
||||||
<h5>{t("labels.sloppiness")}</h5>
|
<legend>{t("labels.sloppiness")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
|
group="sloppiness"
|
||||||
options={[
|
options={[
|
||||||
{ value: 0, text: t("labels.architect") },
|
{ value: 0, text: t("labels.architect") },
|
||||||
{ value: 1, text: t("labels.artist") },
|
{ value: 1, text: t("labels.artist") },
|
||||||
@ -182,7 +187,7 @@ export const actionChangeSloppiness: Action = {
|
|||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</fieldset>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -198,8 +203,8 @@ export const actionChangeOpacity: Action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<label className="control-label">
|
||||||
<h5>{t("labels.opacity")}</h5>
|
{t("labels.opacity")}
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
@ -214,7 +219,7 @@ export const actionChangeOpacity: Action = {
|
|||||||
) ?? undefined
|
) ?? undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</label>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -238,9 +243,10 @@ export const actionChangeFontSize: Action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<fieldset>
|
||||||
<h5>{t("labels.fontSize")}</h5>
|
<legend>{t("labels.fontSize")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
|
group="font-size"
|
||||||
options={[
|
options={[
|
||||||
{ value: 16, text: t("labels.small") },
|
{ value: 16, text: t("labels.small") },
|
||||||
{ value: 20, text: t("labels.medium") },
|
{ value: 20, text: t("labels.medium") },
|
||||||
@ -254,7 +260,7 @@ export const actionChangeFontSize: Action = {
|
|||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</fieldset>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -278,9 +284,10 @@ export const actionChangeFontFamily: Action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
<>
|
<fieldset>
|
||||||
<h5>{t("labels.fontFamily")}</h5>
|
<legend>{t("labels.fontFamily")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
|
group="font-family"
|
||||||
options={[
|
options={[
|
||||||
{ value: "Virgil", text: t("labels.handDrawn") },
|
{ value: "Virgil", text: t("labels.handDrawn") },
|
||||||
{ value: "Helvetica", text: t("labels.normal") },
|
{ value: "Helvetica", text: t("labels.normal") },
|
||||||
@ -293,6 +300,6 @@ export const actionChangeFontFamily: Action = {
|
|||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</fieldset>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -4,21 +4,28 @@ export function ButtonSelect<T>({
|
|||||||
options,
|
options,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
group,
|
||||||
}: {
|
}: {
|
||||||
options: { value: T; text: string }[];
|
options: { value: T; text: string }[];
|
||||||
value: T | null;
|
value: T | null;
|
||||||
onChange: (value: T) => void;
|
onChange: (value: T) => void;
|
||||||
|
group: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="buttonList">
|
<div className="buttonList">
|
||||||
{options.map(option => (
|
{options.map(option => (
|
||||||
<button
|
<label
|
||||||
key={option.text}
|
key={option.text}
|
||||||
onClick={() => onChange(option.value)}
|
|
||||||
className={value === option.value ? "active" : ""}
|
className={value === option.value ? "active" : ""}
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={group}
|
||||||
|
onChange={() => onChange(option.value)}
|
||||||
|
checked={value === option.value ? true : false}
|
||||||
|
/>
|
||||||
{option.text}
|
{option.text}
|
||||||
</button>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -10,10 +10,12 @@ const Picker = function({
|
|||||||
colors,
|
colors,
|
||||||
color,
|
color,
|
||||||
onChange,
|
onChange,
|
||||||
|
label,
|
||||||
}: {
|
}: {
|
||||||
colors: string[];
|
colors: string[];
|
||||||
color: string | null;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
|
label: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="color-picker">
|
<div className="color-picker">
|
||||||
@ -42,6 +44,7 @@ const Picker = function({
|
|||||||
</div>
|
</div>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
color={color}
|
color={color}
|
||||||
|
label={label}
|
||||||
onChange={color => {
|
onChange={color => {
|
||||||
onChange(color);
|
onChange(color);
|
||||||
}}
|
}}
|
||||||
@ -54,9 +57,11 @@ const Picker = function({
|
|||||||
function ColorInput({
|
function ColorInput({
|
||||||
color,
|
color,
|
||||||
onChange,
|
onChange,
|
||||||
|
label,
|
||||||
}: {
|
}: {
|
||||||
color: string | null;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
|
label: string;
|
||||||
}) {
|
}) {
|
||||||
const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
|
const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/;
|
||||||
const [innerValue, setInnerValue] = React.useState(color);
|
const [innerValue, setInnerValue] = React.useState(color);
|
||||||
@ -71,7 +76,7 @@ function ColorInput({
|
|||||||
<input
|
<input
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
className="color-picker-input"
|
className="color-picker-input"
|
||||||
aria-label="Hex color code"
|
aria-label={label}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
if (value.match(colorRegex)) {
|
if (value.match(colorRegex)) {
|
||||||
@ -91,10 +96,12 @@ export function ColorPicker({
|
|||||||
type,
|
type,
|
||||||
color,
|
color,
|
||||||
onChange,
|
onChange,
|
||||||
|
label,
|
||||||
}: {
|
}: {
|
||||||
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
||||||
color: string | null;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
|
label: string;
|
||||||
}) {
|
}) {
|
||||||
const [isActive, setActive] = React.useState(false);
|
const [isActive, setActive] = React.useState(false);
|
||||||
|
|
||||||
@ -103,12 +110,13 @@ export function ColorPicker({
|
|||||||
<div className="color-picker-control-container">
|
<div className="color-picker-control-container">
|
||||||
<button
|
<button
|
||||||
className="color-picker-label-swatch"
|
className="color-picker-label-swatch"
|
||||||
aria-label="Change color"
|
aria-label={label}
|
||||||
style={color ? { backgroundColor: color } : undefined}
|
style={color ? { backgroundColor: color } : undefined}
|
||||||
onClick={() => setActive(!isActive)}
|
onClick={() => setActive(!isActive)}
|
||||||
/>
|
/>
|
||||||
<ColorInput
|
<ColorInput
|
||||||
color={color}
|
color={color}
|
||||||
|
label={label}
|
||||||
onChange={color => {
|
onChange={color => {
|
||||||
onChange(color);
|
onChange(color);
|
||||||
}}
|
}}
|
||||||
@ -123,6 +131,7 @@ export function ColorPicker({
|
|||||||
onChange={changedColor => {
|
onChange={changedColor => {
|
||||||
onChange(changedColor);
|
onChange(changedColor);
|
||||||
}}
|
}}
|
||||||
|
label={label}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -3,7 +3,7 @@ import "./ExportDialog.css";
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { Modal } from "./Modal";
|
import { Modal } from "./Modal";
|
||||||
import { ToolIcon } from "./ToolIcon";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { clipboard, exportFile, downloadFile, link } from "./icons";
|
import { clipboard, exportFile, downloadFile, link } from "./icons";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
@ -91,7 +91,7 @@ export function ExportDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
onClick={() => setModalIsShown(true)}
|
onClick={() => setModalIsShown(true)}
|
||||||
icon={exportFile}
|
icon={exportFile}
|
||||||
type="button"
|
type="button"
|
||||||
@ -109,7 +109,7 @@ export function ExportDialog({
|
|||||||
<div className="ExportDialog__preview" ref={previewRef}></div>
|
<div className="ExportDialog__preview" ref={previewRef}></div>
|
||||||
<div className="ExportDialog__actions">
|
<div className="ExportDialog__actions">
|
||||||
<Stack.Row gap={2}>
|
<Stack.Row gap={2}>
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={downloadFile}
|
icon={downloadFile}
|
||||||
title={t("buttons.exportToPng")}
|
title={t("buttons.exportToPng")}
|
||||||
@ -117,7 +117,7 @@ export function ExportDialog({
|
|||||||
onClick={() => onExportToPng(exportedElements, scale)}
|
onClick={() => onExportToPng(exportedElements, scale)}
|
||||||
/>
|
/>
|
||||||
{probablySupportsClipboard && (
|
{probablySupportsClipboard && (
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={clipboard}
|
icon={clipboard}
|
||||||
title={t("buttons.copyToClipboard")}
|
title={t("buttons.copyToClipboard")}
|
||||||
@ -127,7 +127,7 @@ export function ExportDialog({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={link}
|
icon={link}
|
||||||
title={t("buttons.getShareableLink")}
|
title={t("buttons.getShareableLink")}
|
||||||
@ -147,12 +147,13 @@ export function ExportDialog({
|
|||||||
<div className="ExportDialog__scales">
|
<div className="ExportDialog__scales">
|
||||||
<Stack.Row gap={1} align="baseline">
|
<Stack.Row gap={1} align="baseline">
|
||||||
{scales.map(s => (
|
{scales.map(s => (
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
key={s}
|
key={s}
|
||||||
size="s"
|
size="s"
|
||||||
type="radio"
|
type="radio"
|
||||||
icon={"x" + s}
|
icon={"x" + s}
|
||||||
name="export-canvas-scale"
|
name="export-canvas-scale"
|
||||||
|
aria-label="Export"
|
||||||
id="export-canvas-scale"
|
id="export-canvas-scale"
|
||||||
checked={scale === s}
|
checked={scale === s}
|
||||||
onChange={() => setScale(s)}
|
onChange={() => setScale(s)}
|
||||||
|
62
src/components/ToolButton.tsx
Normal file
62
src/components/ToolButton.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import "./ToolIcon.scss";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type ToolIconSize = "s" | "m";
|
||||||
|
|
||||||
|
type ToolButtonBaseProps = {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
"aria-label": string;
|
||||||
|
"aria-keyshortcuts"?: string;
|
||||||
|
title?: string;
|
||||||
|
name?: string;
|
||||||
|
id?: string;
|
||||||
|
size?: ToolIconSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ToolButtonProps =
|
||||||
|
| (ToolButtonBaseProps & { type: "button"; onClick?(): void })
|
||||||
|
| (ToolButtonBaseProps & {
|
||||||
|
type: "radio";
|
||||||
|
|
||||||
|
checked: boolean;
|
||||||
|
onChange?(): void;
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_SIZE: ToolIconSize = "m";
|
||||||
|
|
||||||
|
export function ToolButton(props: ToolButtonProps) {
|
||||||
|
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
|
||||||
|
|
||||||
|
if (props.type === "button")
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`ToolIcon_type_button ToolIcon ${sizeCn}`}
|
||||||
|
title={props.title}
|
||||||
|
aria-label={props["aria-label"]}
|
||||||
|
type="button"
|
||||||
|
onClick={props.onClick}
|
||||||
|
>
|
||||||
|
<div className="ToolIcon__icon" aria-hidden="true">
|
||||||
|
{props.icon}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className="ToolIcon">
|
||||||
|
<input
|
||||||
|
className={`ToolIcon_type_radio ${sizeCn}`}
|
||||||
|
type="radio"
|
||||||
|
name={props.name}
|
||||||
|
title={props.title}
|
||||||
|
aria-label={props["aria-label"]}
|
||||||
|
aria-keyshortcuts={props["aria-keyshortcuts"]}
|
||||||
|
id={props.id}
|
||||||
|
onChange={props.onChange}
|
||||||
|
checked={props.checked}
|
||||||
|
/>
|
||||||
|
<div className="ToolIcon__icon">{props.icon}</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
import "./ToolIcon.scss";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
type ToolIconSize = "s" | "m";
|
|
||||||
|
|
||||||
type ToolIconProps =
|
|
||||||
| {
|
|
||||||
type: "button";
|
|
||||||
icon: React.ReactNode;
|
|
||||||
"aria-label": string;
|
|
||||||
title?: string;
|
|
||||||
name?: string;
|
|
||||||
id?: string;
|
|
||||||
onClick?(): void;
|
|
||||||
size?: ToolIconSize;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "radio";
|
|
||||||
icon: React.ReactNode;
|
|
||||||
title?: string;
|
|
||||||
name?: string;
|
|
||||||
id?: string;
|
|
||||||
checked: boolean;
|
|
||||||
onChange?(): void;
|
|
||||||
size?: ToolIconSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_SIZE: ToolIconSize = "m";
|
|
||||||
|
|
||||||
export function ToolIcon(props: ToolIconProps) {
|
|
||||||
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
|
|
||||||
|
|
||||||
if (props.type === "button")
|
|
||||||
return (
|
|
||||||
<label className={`ToolIcon ${sizeCn}`} title={props.title}>
|
|
||||||
<button
|
|
||||||
className="ToolIcon_type_button"
|
|
||||||
aria-label={props["aria-label"]}
|
|
||||||
type="button"
|
|
||||||
onClick={props.onClick}
|
|
||||||
>
|
|
||||||
<div className="ToolIcon__icon">{props.icon}</div>
|
|
||||||
</button>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label className={`ToolIcon ${sizeCn}`} title={props.title}>
|
|
||||||
<input
|
|
||||||
className="ToolIcon_type_radio"
|
|
||||||
type="radio"
|
|
||||||
name={props.name}
|
|
||||||
id={props.id}
|
|
||||||
onChange={props.onChange}
|
|
||||||
checked={props.checked}
|
|
||||||
/>
|
|
||||||
<div className="ToolIcon__icon">{props.icon}</div>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
@ -80,7 +80,7 @@ import { getDefaultAppState } from "./appState";
|
|||||||
import { Island } from "./components/Island";
|
import { Island } from "./components/Island";
|
||||||
import Stack from "./components/Stack";
|
import Stack from "./components/Stack";
|
||||||
import { FixedSideContainer } from "./components/FixedSideContainer";
|
import { FixedSideContainer } from "./components/FixedSideContainer";
|
||||||
import { ToolIcon } from "./components/ToolIcon";
|
import { ToolButton } from "./components/ToolButton";
|
||||||
import { LockIcon } from "./components/LockIcon";
|
import { LockIcon } from "./components/LockIcon";
|
||||||
import { ExportDialog } from "./components/ExportDialog";
|
import { ExportDialog } from "./components/ExportDialog";
|
||||||
import { withTranslation } from "react-i18next";
|
import { withTranslation } from "react-i18next";
|
||||||
@ -501,7 +501,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
{SHAPES.map(({ value, icon }, index) => {
|
{SHAPES.map(({ value, icon }, index) => {
|
||||||
const label = t(`toolBar.${value}`);
|
const label = t(`toolBar.${value}`);
|
||||||
return (
|
return (
|
||||||
<ToolIcon
|
<ToolButton
|
||||||
key={value}
|
key={value}
|
||||||
type="radio"
|
type="radio"
|
||||||
icon={icon}
|
icon={icon}
|
||||||
@ -510,6 +510,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
title={`${capitalizeString(label)} — ${
|
title={`${capitalizeString(label)} — ${
|
||||||
capitalizeString(value)[0]
|
capitalizeString(value)[0]
|
||||||
}, ${index + 1}`}
|
}, ${index + 1}`}
|
||||||
|
aria-label={capitalizeString(label)}
|
||||||
|
aria-keyshortcuts={`${label[0]} ${index + 1}`}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
this.setState({ elementType: value });
|
this.setState({ elementType: value });
|
||||||
elements = clearSelection(elements);
|
elements = clearSelection(elements);
|
||||||
@ -517,7 +519,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
|
value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}}
|
}}
|
||||||
></ToolIcon>
|
></ToolButton>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{this.renderShapeLock()}
|
{this.renderShapeLock()}
|
||||||
@ -610,6 +612,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
<div className="App-menu App-menu_top">
|
<div className="App-menu App-menu_top">
|
||||||
<Stack.Col gap={4} align="end">
|
<Stack.Col gap={4} align="end">
|
||||||
<div className="App-right-menu">
|
<div className="App-right-menu">
|
||||||
|
<h2 className="visually-hidden">Canvas actions</h2>
|
||||||
<Island padding={4}>{this.renderCanvasActions()}</Island>
|
<Island padding={4}>{this.renderCanvasActions()}</Island>
|
||||||
</div>
|
</div>
|
||||||
<div className="App-right-menu">
|
<div className="App-right-menu">
|
||||||
@ -618,6 +621,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
<Stack.Col gap={4} align="start">
|
<Stack.Col gap={4} align="start">
|
||||||
<Island padding={1}>
|
<Island padding={1}>
|
||||||
|
<h2 className="visually-hidden">Shapes</h2>
|
||||||
<Stack.Row gap={1}>{this.renderShapesSwitcher()}</Stack.Row>
|
<Stack.Row gap={1}>{this.renderShapesSwitcher()}</Stack.Row>
|
||||||
</Island>
|
</Island>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
|
@ -33,24 +33,52 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
h5 {
|
h3,
|
||||||
|
legend,
|
||||||
|
.control-label {
|
||||||
margin-top: 0.333rem;
|
margin-top: 0.333rem;
|
||||||
margin-bottom: 0.333em;
|
margin-bottom: 0.333rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--text-color-primary);
|
color: var(--text-color-primary);
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5:first-child {
|
.control-label input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3:first-child,
|
||||||
|
legend:first-child,
|
||||||
|
.control-label:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.buttonList {
|
.buttonList {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
button {
|
label {
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.333rem;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +93,8 @@ input:focus {
|
|||||||
box-shadow: 0 0 0 2px #a5d8ff;
|
box-shadow: 0 0 0 2px #a5d8ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button,
|
||||||
|
.buttonList label {
|
||||||
background-color: #e9ecef;
|
background-color: #e9ecef;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -92,7 +121,8 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active,
|
||||||
|
.buttonList label.active {
|
||||||
background-color: #ced4da;
|
background-color: #ced4da;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #ced4da;
|
background-color: #ced4da;
|
||||||
@ -216,3 +246,13 @@ button {
|
|||||||
background-color: #ced4da;
|
background-color: #ced4da;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visually-hidden {
|
||||||
|
position: absolute !important;
|
||||||
|
height: 1px;
|
||||||
|
width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
|
||||||
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
|
white-space: nowrap; /* added line */
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user