mirror of https://github.com/vector-im/riot-web
Add disabled button state to rich text editor (#9930)
* add disabled css state * conditionally apply disabled css state * hides disabled tooltipspull/28217/head
parent
b47588fc5c
commit
8a2e386531
|
@ -50,6 +50,12 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_FormattingButtons_disabled {
|
||||||
|
.mx_FormattingButtons_Icon {
|
||||||
|
color: $quinary-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_FormattingButtons_Icon {
|
.mx_FormattingButtons_Icon {
|
||||||
--size: 16px;
|
--size: 16px;
|
||||||
height: var(--size);
|
height: var(--size);
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { MouseEventHandler, ReactNode } from "react";
|
import React, { MouseEventHandler, ReactNode } from "react";
|
||||||
import { FormattingFunctions, AllActionStates } from "@matrix-org/matrix-wysiwyg";
|
import { FormattingFunctions, AllActionStates, ActionState } from "@matrix-org/matrix-wysiwyg";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { Icon as BoldIcon } from "../../../../../../res/img/element-icons/room/composer/bold.svg";
|
import { Icon as BoldIcon } from "../../../../../../res/img/element-icons/room/composer/bold.svg";
|
||||||
|
@ -53,21 +53,23 @@ function Tooltip({ label, keyCombo }: TooltipProps): JSX.Element {
|
||||||
|
|
||||||
interface ButtonProps extends TooltipProps {
|
interface ButtonProps extends TooltipProps {
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
isActive: boolean;
|
actionState: ActionState;
|
||||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Button({ label, keyCombo, onClick, isActive, icon }: ButtonProps): JSX.Element {
|
function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
element="button"
|
element="button"
|
||||||
onClick={onClick as (e: ButtonEvent) => void}
|
onClick={onClick as (e: ButtonEvent) => void}
|
||||||
title={label}
|
title={label}
|
||||||
className={classNames("mx_FormattingButtons_Button", {
|
className={classNames("mx_FormattingButtons_Button", {
|
||||||
mx_FormattingButtons_active: isActive,
|
mx_FormattingButtons_active: actionState === "reversed",
|
||||||
mx_FormattingButtons_Button_hover: !isActive,
|
mx_FormattingButtons_Button_hover: actionState === "enabled",
|
||||||
|
mx_FormattingButtons_disabled: actionState === "disabled",
|
||||||
})}
|
})}
|
||||||
tooltip={keyCombo && <Tooltip label={label} keyCombo={keyCombo} />}
|
tooltip={keyCombo && <Tooltip label={label} keyCombo={keyCombo} />}
|
||||||
|
forceHide={actionState === "disabled"}
|
||||||
alignment={Alignment.Top}
|
alignment={Alignment.Top}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
|
@ -85,53 +87,53 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
|
||||||
return (
|
return (
|
||||||
<div className="mx_FormattingButtons">
|
<div className="mx_FormattingButtons">
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.bold === "reversed"}
|
actionState={actionStates.bold}
|
||||||
label={_td("Bold")}
|
label={_td("Bold")}
|
||||||
keyCombo={{ ctrlOrCmdKey: true, key: "b" }}
|
keyCombo={{ ctrlOrCmdKey: true, key: "b" }}
|
||||||
onClick={() => composer.bold()}
|
onClick={() => composer.bold()}
|
||||||
icon={<BoldIcon className="mx_FormattingButtons_Icon" />}
|
icon={<BoldIcon className="mx_FormattingButtons_Icon" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.italic === "reversed"}
|
actionState={actionStates.italic}
|
||||||
label={_td("Italic")}
|
label={_td("Italic")}
|
||||||
keyCombo={{ ctrlOrCmdKey: true, key: "i" }}
|
keyCombo={{ ctrlOrCmdKey: true, key: "i" }}
|
||||||
onClick={() => composer.italic()}
|
onClick={() => composer.italic()}
|
||||||
icon={<ItalicIcon className="mx_FormattingButtons_Icon" />}
|
icon={<ItalicIcon className="mx_FormattingButtons_Icon" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.underline === "reversed"}
|
actionState={actionStates.underline}
|
||||||
label={_td("Underline")}
|
label={_td("Underline")}
|
||||||
keyCombo={{ ctrlOrCmdKey: true, key: "u" }}
|
keyCombo={{ ctrlOrCmdKey: true, key: "u" }}
|
||||||
onClick={() => composer.underline()}
|
onClick={() => composer.underline()}
|
||||||
icon={<UnderlineIcon className="mx_FormattingButtons_Icon" />}
|
icon={<UnderlineIcon className="mx_FormattingButtons_Icon" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.strikeThrough === "reversed"}
|
actionState={actionStates.strikeThrough}
|
||||||
label={_td("Strikethrough")}
|
label={_td("Strikethrough")}
|
||||||
onClick={() => composer.strikeThrough()}
|
onClick={() => composer.strikeThrough()}
|
||||||
icon={<StrikeThroughIcon className="mx_FormattingButtons_Icon" />}
|
icon={<StrikeThroughIcon className="mx_FormattingButtons_Icon" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.unorderedList === "reversed"}
|
actionState={actionStates.unorderedList}
|
||||||
label={_td("Bulleted list")}
|
label={_td("Bulleted list")}
|
||||||
onClick={() => composer.unorderedList()}
|
onClick={() => composer.unorderedList()}
|
||||||
icon={<BulletedListIcon className="mx_FormattingButtons_Icon" />}
|
icon={<BulletedListIcon className="mx_FormattingButtons_Icon" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.orderedList === "reversed"}
|
actionState={actionStates.orderedList}
|
||||||
label={_td("Numbered list")}
|
label={_td("Numbered list")}
|
||||||
onClick={() => composer.orderedList()}
|
onClick={() => composer.orderedList()}
|
||||||
icon={<NumberedListIcon className="mx_FormattingButtons_Icon" />}
|
icon={<NumberedListIcon className="mx_FormattingButtons_Icon" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.inlineCode === "reversed"}
|
actionState={actionStates.inlineCode}
|
||||||
label={_td("Code")}
|
label={_td("Code")}
|
||||||
keyCombo={{ ctrlOrCmdKey: true, key: "e" }}
|
keyCombo={{ ctrlOrCmdKey: true, key: "e" }}
|
||||||
onClick={() => composer.inlineCode()}
|
onClick={() => composer.inlineCode()}
|
||||||
icon={<InlineCodeIcon className="mx_FormattingButtons_Icon" />}
|
icon={<InlineCodeIcon className="mx_FormattingButtons_Icon" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
isActive={actionStates.link === "reversed"}
|
actionState={actionStates.link}
|
||||||
label={_td("Link")}
|
label={_td("Link")}
|
||||||
onClick={() => openLinkModal(composer, composerContext, actionStates.link === "reversed")}
|
onClick={() => openLinkModal(composer, composerContext, actionStates.link === "reversed")}
|
||||||
icon={<LinkIcon className="mx_FormattingButtons_Icon" />}
|
icon={<LinkIcon className="mx_FormattingButtons_Icon" />}
|
||||||
|
|
|
@ -62,6 +62,7 @@ const renderComponent = (props = {}) => {
|
||||||
const classes = {
|
const classes = {
|
||||||
active: "mx_FormattingButtons_active",
|
active: "mx_FormattingButtons_active",
|
||||||
hover: "mx_FormattingButtons_Button_hover",
|
hover: "mx_FormattingButtons_Button_hover",
|
||||||
|
disabled: "mx_FormattingButtons_disabled",
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("FormattingButtons", () => {
|
describe("FormattingButtons", () => {
|
||||||
|
@ -87,6 +88,16 @@ describe("FormattingButtons", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Each button should have disabled class when disabled", () => {
|
||||||
|
const disabledActionStates = createActionStates("disabled");
|
||||||
|
renderComponent({ actionStates: disabledActionStates });
|
||||||
|
|
||||||
|
Object.values(testCases).forEach((testCase) => {
|
||||||
|
const { label } = testCase;
|
||||||
|
expect(screen.getByLabelText(label)).toHaveClass(classes.disabled);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("Should call wysiwyg function on button click", async () => {
|
it("Should call wysiwyg function on button click", async () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
|
||||||
|
@ -98,14 +109,26 @@ describe("FormattingButtons", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Each button should display the tooltip on mouse over", async () => {
|
it("Each button should display the tooltip on mouse over when not disabled", async () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
|
||||||
for (const testCase of Object.values(testCases)) {
|
for (const testCase of Object.values(testCases)) {
|
||||||
const { label } = testCase;
|
const { label } = testCase;
|
||||||
|
|
||||||
await userEvent.hover(screen.getByLabelText(label));
|
await userEvent.hover(screen.getByLabelText(label));
|
||||||
expect(await screen.findByText(label)).toBeTruthy();
|
expect(screen.getByText(label)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Each button should not display the tooltip on mouse over when disabled", async () => {
|
||||||
|
const disabledActionStates = createActionStates("disabled");
|
||||||
|
renderComponent({ actionStates: disabledActionStates });
|
||||||
|
|
||||||
|
for (const testCase of Object.values(testCases)) {
|
||||||
|
const { label } = testCase;
|
||||||
|
|
||||||
|
await userEvent.hover(screen.getByLabelText(label));
|
||||||
|
expect(screen.queryByText(label)).not.toBeInTheDocument();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue