Consolidate, simplify and improve copied tooltips (#7799)

This commit is contained in:
Michael Telatynski 2022-02-14 23:54:46 +00:00 committed by GitHub
parent 226eed2a7f
commit 76fb2abae1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 31 additions and 50 deletions

View file

@ -161,10 +161,6 @@ limitations under the License.
flex-direction: column;
}
.mx_CreateSecretStorageDialog_recoveryKeyCopyButtonText {
overflow-y: hidden;
}
.mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton {
flex-grow: 1;
white-space: nowrap;

View file

@ -740,20 +740,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
onClick={this.onCopyClick}
disabled={this.state.phase === Phase.Storing}
>
<span
className="mx_CreateSecretStorageDialog_recoveryKeyCopyButtonText"
style={{ height: this.state.copied ? '0' : 'auto' }}
aria-hidden={this.state.copied}
>
{ _t("Copy") }
</span>
<span
className="mx_CreateSecretStorageDialog_recoveryKeyCopyButtonText"
style={{ height: this.state.copied ? 'auto' : '0' }}
aria-hidden={!this.state.copied}
>
{ _t("Copied!") }
</span>
{ this.state.copied ? _t("Copied!") : _t("Copy") }
</AccessibleButton>
</div>
</div>

View file

@ -25,6 +25,8 @@ interface IProps {
@replaceableComponent("views.context_menus.GenericTextContextMenu")
export default class GenericTextContextMenu extends React.Component<IProps> {
public render(): JSX.Element {
return <div>{ this.props.message }</div>;
return <div className="mx_Tooltip mx_Tooltip_visible" style={{ display: "block" }}>
{ this.props.message }
</div>;
}
}

View file

@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, { SyntheticEvent } from 'react';
import AccessibleButton from "./AccessibleButton";
import Tooltip, { Alignment } from './Tooltip';
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
title: string;
tooltip?: React.ReactNode;
label?: React.ReactNode;
@ -29,6 +29,7 @@ interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
forceHide?: boolean;
yOffset?: number;
alignment?: Alignment;
onHideTooltip?(ev: SyntheticEvent): void;
}
interface IState {
@ -36,15 +37,15 @@ interface IState {
}
@replaceableComponent("views.elements.AccessibleTooltipButton")
export default class AccessibleTooltipButton extends React.PureComponent<ITooltipProps, IState> {
constructor(props: ITooltipProps) {
export default class AccessibleTooltipButton extends React.PureComponent<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
hover: false,
};
}
componentDidUpdate(prevProps: Readonly<ITooltipProps>) {
componentDidUpdate(prevProps: Readonly<IProps>) {
if (!prevProps.forceHide && this.props.forceHide && this.state.hover) {
this.setState({
hover: false,
@ -52,22 +53,24 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
}
}
showTooltip = () => {
private showTooltip = () => {
if (this.props.forceHide) return;
this.setState({
hover: true,
});
};
hideTooltip = () => {
private hideTooltip = (ev: SyntheticEvent) => {
this.setState({
hover: false,
});
this.props.onHideTooltip?.(ev);
};
render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, tooltip, children, tooltipClassName, forceHide, yOffset, alignment, ...props } = this.props;
const { title, tooltip, children, tooltipClassName, forceHide, yOffset, alignment, onHideTooltip,
...props } = this.props;
const tip = this.state.hover && <Tooltip
tooltipClassName={tooltipClassName}

View file

@ -15,12 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useEffect, useRef } from "react";
import React, { useState } from "react";
import { _t } from "../../../languageHandler";
import { copyPlaintext } from "../../../utils/strings";
import { toRightOf, createMenu } from "../../structures/ContextMenu";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
import { ButtonEvent } from "./AccessibleButton";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
@ -30,32 +28,27 @@ interface IProps {
}
const CopyableText: React.FC<IProps> = ({ children, getTextToCopy }) => {
const closeCopiedTooltip = useRef<() => void>();
const divRef = useRef<HTMLDivElement>();
useEffect(() => () => {
if (closeCopiedTooltip.current) closeCopiedTooltip.current();
}, [closeCopiedTooltip]);
const [tooltip, setTooltip] = useState<string | undefined>(undefined);
const onCopyClickInternal = async (e: ButtonEvent) => {
e.preventDefault();
const target = e.target as HTMLDivElement; // copy target before we go async and React throws it away
const successful = await copyPlaintext(getTextToCopy());
const buttonRect = target.getBoundingClientRect();
const { close } = createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
closeCopiedTooltip.current = target.onmouseleave = close;
setTooltip(successful ? _t('Copied!') : _t('Failed to copy'));
};
return <div className="mx_CopyableText" ref={divRef}>
const onHideTooltip = () => {
if (tooltip) {
setTooltip(undefined);
}
};
return <div className="mx_CopyableText">
{ children }
<AccessibleTooltipButton
title={_t("Copy")}
title={tooltip ?? _t("Copy")}
onClick={onCopyClickInternal}
className="mx_CopyableText_copyButton"
onHideTooltip={onHideTooltip}
/>
</div>;
};

View file

@ -26,7 +26,7 @@ import Modal from '../../../Modal';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import * as ContextMenu from '../../structures/ContextMenu';
import { toRightOf } from '../../structures/ContextMenu';
import { ChevronFace, toRightOf } from '../../structures/ContextMenu';
import SettingsStore from "../../../settings/SettingsStore";
import ReplyChain from "../elements/ReplyChain";
import { pillifyLinks, unmountPills } from '../../../utils/pillify';
@ -177,8 +177,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const button = document.createElement("span");
button.className = "mx_EventTile_button mx_EventTile_copyButton ";
// Check if expansion button exists. If so
// we put the copy button to the bottom
// Check if expansion button exists. If so we put the copy button to the bottom
const expansionButtonExists = div.getElementsByClassName("mx_EventTile_button");
if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
@ -188,7 +187,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const buttonRect = button.getBoundingClientRect();
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
...toRightOf(buttonRect, 0),
chevronFace: ChevronFace.None,
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
button.onmouseleave = close;