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;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CreateSecretStorageDialog_recoveryKeyCopyButtonText {
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton {
|
.mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -740,20 +740,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
onClick={this.onCopyClick}
|
onClick={this.onCopyClick}
|
||||||
disabled={this.state.phase === Phase.Storing}
|
disabled={this.state.phase === Phase.Storing}
|
||||||
>
|
>
|
||||||
<span
|
{ this.state.copied ? _t("Copied!") : _t("Copy") }
|
||||||
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>
|
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,8 @@ interface IProps {
|
||||||
@replaceableComponent("views.context_menus.GenericTextContextMenu")
|
@replaceableComponent("views.context_menus.GenericTextContextMenu")
|
||||||
export default class GenericTextContextMenu extends React.Component<IProps> {
|
export default class GenericTextContextMenu extends React.Component<IProps> {
|
||||||
public render(): JSX.Element {
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { SyntheticEvent } from 'react';
|
||||||
|
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import Tooltip, { Alignment } from './Tooltip';
|
import Tooltip, { Alignment } from './Tooltip';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
title: string;
|
title: string;
|
||||||
tooltip?: React.ReactNode;
|
tooltip?: React.ReactNode;
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
|
@ -29,6 +29,7 @@ interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
forceHide?: boolean;
|
forceHide?: boolean;
|
||||||
yOffset?: number;
|
yOffset?: number;
|
||||||
alignment?: Alignment;
|
alignment?: Alignment;
|
||||||
|
onHideTooltip?(ev: SyntheticEvent): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -36,15 +37,15 @@ interface IState {
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.elements.AccessibleTooltipButton")
|
@replaceableComponent("views.elements.AccessibleTooltipButton")
|
||||||
export default class AccessibleTooltipButton extends React.PureComponent<ITooltipProps, IState> {
|
export default class AccessibleTooltipButton extends React.PureComponent<IProps, IState> {
|
||||||
constructor(props: ITooltipProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
hover: false,
|
hover: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<ITooltipProps>) {
|
componentDidUpdate(prevProps: Readonly<IProps>) {
|
||||||
if (!prevProps.forceHide && this.props.forceHide && this.state.hover) {
|
if (!prevProps.forceHide && this.props.forceHide && this.state.hover) {
|
||||||
this.setState({
|
this.setState({
|
||||||
hover: false,
|
hover: false,
|
||||||
|
@ -52,22 +53,24 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showTooltip = () => {
|
private showTooltip = () => {
|
||||||
if (this.props.forceHide) return;
|
if (this.props.forceHide) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
hover: true,
|
hover: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
hideTooltip = () => {
|
private hideTooltip = (ev: SyntheticEvent) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
hover: false,
|
hover: false,
|
||||||
});
|
});
|
||||||
|
this.props.onHideTooltip?.(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// 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
|
const tip = this.state.hover && <Tooltip
|
||||||
tooltipClassName={tooltipClassName}
|
tooltipClassName={tooltipClassName}
|
||||||
|
|
|
@ -15,12 +15,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { copyPlaintext } from "../../../utils/strings";
|
import { copyPlaintext } from "../../../utils/strings";
|
||||||
import { toRightOf, createMenu } from "../../structures/ContextMenu";
|
|
||||||
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
|
|
||||||
import { ButtonEvent } from "./AccessibleButton";
|
import { ButtonEvent } from "./AccessibleButton";
|
||||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||||
|
|
||||||
|
@ -30,32 +28,27 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const CopyableText: React.FC<IProps> = ({ children, getTextToCopy }) => {
|
const CopyableText: React.FC<IProps> = ({ children, getTextToCopy }) => {
|
||||||
const closeCopiedTooltip = useRef<() => void>();
|
const [tooltip, setTooltip] = useState<string | undefined>(undefined);
|
||||||
const divRef = useRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
useEffect(() => () => {
|
|
||||||
if (closeCopiedTooltip.current) closeCopiedTooltip.current();
|
|
||||||
}, [closeCopiedTooltip]);
|
|
||||||
|
|
||||||
const onCopyClickInternal = async (e: ButtonEvent) => {
|
const onCopyClickInternal = async (e: ButtonEvent) => {
|
||||||
e.preventDefault();
|
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 successful = await copyPlaintext(getTextToCopy());
|
||||||
const buttonRect = target.getBoundingClientRect();
|
setTooltip(successful ? _t('Copied!') : _t('Failed to copy'));
|
||||||
const { close } = createMenu(GenericTextContextMenu, {
|
|
||||||
...toRightOf(buttonRect, 2),
|
|
||||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
|
||||||
});
|
|
||||||
closeCopiedTooltip.current = target.onmouseleave = close;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="mx_CopyableText" ref={divRef}>
|
const onHideTooltip = () => {
|
||||||
|
if (tooltip) {
|
||||||
|
setTooltip(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className="mx_CopyableText">
|
||||||
{ children }
|
{ children }
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
title={_t("Copy")}
|
title={tooltip ?? _t("Copy")}
|
||||||
onClick={onCopyClickInternal}
|
onClick={onCopyClickInternal}
|
||||||
className="mx_CopyableText_copyButton"
|
className="mx_CopyableText_copyButton"
|
||||||
|
onHideTooltip={onHideTooltip}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,7 @@ import Modal from '../../../Modal';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as ContextMenu from '../../structures/ContextMenu';
|
import * as ContextMenu from '../../structures/ContextMenu';
|
||||||
import { toRightOf } from '../../structures/ContextMenu';
|
import { ChevronFace, toRightOf } from '../../structures/ContextMenu';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import ReplyChain from "../elements/ReplyChain";
|
import ReplyChain from "../elements/ReplyChain";
|
||||||
import { pillifyLinks, unmountPills } from '../../../utils/pillify';
|
import { pillifyLinks, unmountPills } from '../../../utils/pillify';
|
||||||
|
@ -177,8 +177,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||||
const button = document.createElement("span");
|
const button = document.createElement("span");
|
||||||
button.className = "mx_EventTile_button mx_EventTile_copyButton ";
|
button.className = "mx_EventTile_button mx_EventTile_copyButton ";
|
||||||
|
|
||||||
// Check if expansion button exists. If so
|
// Check if expansion button exists. If so we put the copy button to the bottom
|
||||||
// we put the copy button to the bottom
|
|
||||||
const expansionButtonExists = div.getElementsByClassName("mx_EventTile_button");
|
const expansionButtonExists = div.getElementsByClassName("mx_EventTile_button");
|
||||||
if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
|
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 buttonRect = button.getBoundingClientRect();
|
||||||
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
|
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
|
||||||
...toRightOf(buttonRect, 2),
|
...toRightOf(buttonRect, 0),
|
||||||
|
chevronFace: ChevronFace.None,
|
||||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||||
});
|
});
|
||||||
button.onmouseleave = close;
|
button.onmouseleave = close;
|
||||||
|
|
Loading…
Reference in a new issue