mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Consolidate, simplify and improve copied tooltips (#7799)
This commit is contained in:
parent
226eed2a7f
commit
76fb2abae1
6 changed files with 31 additions and 50 deletions
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue