Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 061c269

Browse files
authored
Use Compound Tooltips in StatelessNotificationBadge, VerifyEmailModal, CheckEmail (#12084)
* Switch StatelessNotificationBadge to using Compound Tooltips Signed-off-by: Michael Telatynski <[email protected]> * Fix test Signed-off-by: Michael Telatynski <[email protected]> * Migrate CheckEmail & VerifyEmailModal to Compound tooltips Signed-off-by: Michael Telatynski <[email protected]> * Fix test Signed-off-by: Michael Telatynski <[email protected]> * Fix CSS stacking contexts for Dialogs & PersistedElement Signed-off-by: Michael Telatynski <[email protected]> * Switch to PersistedElement sharing a CSS stacking context for z-index to continue functioning Signed-off-by: Michael Telatynski <[email protected]> * Fix Widget PIP overlay being under the widget and dragging being broken Signed-off-by: Michael Telatynski <[email protected]> * Fix border-radius on widget pip Signed-off-by: Michael Telatynski <[email protected]> * Fix majority of tests Signed-off-by: Michael Telatynski <[email protected]> * Fix jest retryTimes applying outside of CI Signed-off-by: Michael Telatynski <[email protected]> * Fix remaining tests Signed-off-by: Michael Telatynski <[email protected]> * Fix React unique key warnings Signed-off-by: Michael Telatynski <[email protected]> * Fix sticker picker Signed-off-by: Michael Telatynski <[email protected]> * id not class Signed-off-by: Michael Telatynski <[email protected]> * Fix widget pip button colour in light theme Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent 82840a1 commit 061c269

File tree

7 files changed

+71
-142
lines changed

7 files changed

+71
-142
lines changed

res/css/views/rooms/_NotificationBadge.pcss

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,3 @@ limitations under the License.
7575
}
7676
}
7777
}
78-
79-
.mx_NotificationBadge_tooltip {
80-
display: inline-block;
81-
position: relative;
82-
top: -25px;
83-
left: 6px;
84-
}

src/components/structures/auth/forgot-password/CheckEmail.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { ReactNode, useRef } from "react";
17+
import React, { ReactNode } from "react";
18+
import { Tooltip } from "@vector-im/compound-web";
1819

1920
import AccessibleButton from "../../../views/elements/AccessibleButton";
2021
import { Icon as EMailPromptIcon } from "../../../../../res/img/element-icons/email-prompt.svg";
2122
import { Icon as RetryIcon } from "../../../../../res/img/compound/retry-16px.svg";
2223
import { _t } from "../../../../languageHandler";
23-
import Tooltip, { Alignment } from "../../../views/elements/Tooltip";
2424
import { useTimeoutToggle } from "../../../../hooks/useTimeoutToggle";
2525
import { ErrorMessage } from "../../ErrorMessage";
2626

@@ -42,7 +42,6 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
4242
onSubmitForm,
4343
onResendClick,
4444
}) => {
45-
const tooltipId = useRef(`mx_CheckEmail_${Math.random()}`).current;
4645
const { toggle: toggleTooltipVisible, value: tooltipVisible } = useTimeoutToggle(false, 2500);
4746

4847
const onResendClickFn = async (): Promise<void> => {
@@ -67,21 +66,12 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
6766
<input onClick={onSubmitForm} type="button" className="mx_Login_submit" value={_t("action|next")} />
6867
<div className="mx_AuthBody_did-not-receive">
6968
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
70-
<AccessibleButton
71-
className="mx_AuthBody_resend-button"
72-
kind="link"
73-
onClick={onResendClickFn}
74-
aria-describedby={tooltipVisible ? tooltipId : undefined}
75-
>
76-
<RetryIcon className="mx_Icon mx_Icon_16" />
77-
{_t("action|resend")}
78-
<Tooltip
79-
id={tooltipId}
80-
label={_t("auth|check_email_resend_tooltip")}
81-
alignment={Alignment.Top}
82-
visible={tooltipVisible}
83-
/>
84-
</AccessibleButton>
69+
<Tooltip label={_t("auth|check_email_resend_tooltip")} side="top" open={tooltipVisible}>
70+
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
71+
<RetryIcon className="mx_Icon mx_Icon_16" />
72+
{_t("action|resend")}
73+
</AccessibleButton>
74+
</Tooltip>
8575
</div>
8676
</>
8777
);

src/components/structures/auth/forgot-password/VerifyEmailModal.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { ReactNode, useRef } from "react";
17+
import React, { ReactNode } from "react";
18+
import { Tooltip } from "@vector-im/compound-web";
1819

1920
import { _t } from "../../../../languageHandler";
2021
import AccessibleButton from "../../../views/elements/AccessibleButton";
2122
import { Icon as RetryIcon } from "../../../../../res/img/compound/retry-16px.svg";
2223
import { Icon as EmailPromptIcon } from "../../../../../res/img/element-icons/email-prompt.svg";
23-
import Tooltip, { Alignment } from "../../../views/elements/Tooltip";
2424
import { useTimeoutToggle } from "../../../../hooks/useTimeoutToggle";
2525
import { ErrorMessage } from "../../ErrorMessage";
2626

@@ -40,7 +40,6 @@ export const VerifyEmailModal: React.FC<Props> = ({
4040
onReEnterEmailClick,
4141
onResendClick,
4242
}) => {
43-
const tooltipId = useRef(`mx_VerifyEmailModal_${Math.random()}`).current;
4443
const { toggle: toggleTooltipVisible, value: tooltipVisible } = useTimeoutToggle(false, 2500);
4544

4645
const onResendClickFn = async (): Promise<void> => {
@@ -66,21 +65,12 @@ export const VerifyEmailModal: React.FC<Props> = ({
6665

6766
<div className="mx_AuthBody_did-not-receive">
6867
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
69-
<AccessibleButton
70-
className="mx_AuthBody_resend-button"
71-
kind="link"
72-
onClick={onResendClickFn}
73-
aria-describedby={tooltipVisible ? tooltipId : undefined}
74-
>
75-
<RetryIcon className="mx_Icon mx_Icon_16" />
76-
{_t("action|resend")}
77-
<Tooltip
78-
id={tooltipId}
79-
label={_t("auth|check_email_resend_tooltip")}
80-
alignment={Alignment.Top}
81-
visible={tooltipVisible}
82-
/>
83-
</AccessibleButton>
68+
<Tooltip label={_t("auth|check_email_resend_tooltip")} side="top" open={tooltipVisible}>
69+
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
70+
<RetryIcon className="mx_Icon mx_Icon_16" />
71+
{_t("action|resend")}
72+
</AccessibleButton>
73+
</Tooltip>
8474
{errorText && <ErrorMessage message={errorText} />}
8575
</div>
8676

src/components/views/rooms/NotificationBadge.tsx

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { MouseEvent, ReactNode } from "react";
17+
import React, { ReactNode } from "react";
18+
import { Tooltip } from "@vector-im/compound-web";
1819

1920
import SettingsStore from "../../../settings/SettingsStore";
2021
import { XOR } from "../../../@types/common";
2122
import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
22-
import Tooltip from "../elements/Tooltip";
2323
import { _t } from "../../../languageHandler";
2424
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
2525
import { StatelessNotificationBadge } from "./NotificationBadge/StatelessNotificationBadge";
@@ -48,8 +48,7 @@ interface IClickableProps extends IProps, React.InputHTMLAttributes<Element> {
4848
}
4949

5050
interface IState {
51-
showCounts: boolean; // whether or not to show counts. Independent of props.forceCount
52-
showTooltip: boolean;
51+
showCounts: boolean; // whether to show counts. Independent of props.forceCount
5352
}
5453

5554
export default class NotificationBadge extends React.PureComponent<XOR<IProps, IClickableProps>, IState> {
@@ -61,7 +60,6 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
6160

6261
this.state = {
6362
showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId),
64-
showTooltip: false,
6563
};
6664

6765
this.countWatcherRef = SettingsStore.watchSetting(
@@ -97,19 +95,6 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
9795
this.forceUpdate(); // notification state changed - update
9896
};
9997

100-
private onMouseOver = (e: MouseEvent): void => {
101-
e.stopPropagation();
102-
this.setState({
103-
showTooltip: true,
104-
});
105-
};
106-
107-
private onMouseLeave = (): void => {
108-
this.setState({
109-
showTooltip: false,
110-
});
111-
};
112-
11398
public render(): ReactNode {
11499
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
115100
const { notification, showUnsentTooltip, forceCount, onClick, tabIndex } = this.props;
@@ -119,31 +104,28 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
119104
if (!notification.hasUnreadCount) return null; // Can't render a badge
120105
}
121106

122-
let label: string | undefined;
123-
let tooltip: JSX.Element | undefined;
124-
if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) {
125-
label = _t("notifications|message_didnt_send");
126-
tooltip = <Tooltip className="mx_NotificationBadge_tooltip" label={label} />;
127-
}
128-
129107
const commonProps: React.ComponentProps<typeof StatelessNotificationBadge> = {
130-
label,
131108
symbol: notification.symbol,
132109
count: notification.count,
133110
color: notification.color,
134111
knocked: notification.knocked,
135-
onMouseOver: this.onMouseOver,
136-
onMouseLeave: this.onMouseLeave,
137112
};
138113

114+
let badge: JSX.Element;
139115
if (onClick) {
116+
badge = <StatelessNotificationBadge {...commonProps} onClick={onClick} tabIndex={tabIndex} />;
117+
} else {
118+
badge = <StatelessNotificationBadge {...commonProps} />;
119+
}
120+
121+
if (showUnsentTooltip && notification.color === NotificationColor.Unsent) {
140122
return (
141-
<StatelessNotificationBadge {...commonProps} onClick={onClick} tabIndex={tabIndex}>
142-
{tooltip}
143-
</StatelessNotificationBadge>
123+
<Tooltip label={_t("notifications|message_didnt_send")} side="right">
124+
{badge}
125+
</Tooltip>
144126
);
145127
}
146128

147-
return <StatelessNotificationBadge {...commonProps}>{tooltip}</StatelessNotificationBadge>;
129+
return badge;
148130
}
149131
}

src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { MouseEvent, ReactNode } from "react";
17+
import React, { forwardRef } from "react";
1818
import classNames from "classnames";
1919

2020
import { formatCount } from "../../../../utils/FormattingUtils";
@@ -28,10 +28,6 @@ interface Props {
2828
count: number;
2929
color: NotificationColor;
3030
knocked?: boolean;
31-
onMouseOver?: (ev: MouseEvent) => void;
32-
onMouseLeave?: (ev: MouseEvent) => void;
33-
children?: ReactNode;
34-
label?: string;
3531
}
3632

3733
interface ClickableProps extends Props {
@@ -42,57 +38,46 @@ interface ClickableProps extends Props {
4238
tabIndex?: number;
4339
}
4440

45-
export function StatelessNotificationBadge({
46-
symbol,
47-
count,
48-
color,
49-
knocked,
50-
...props
51-
}: XOR<Props, ClickableProps>): JSX.Element {
52-
const hideBold = useSettingValue("feature_hidebold");
41+
export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props, ClickableProps>>(
42+
({ symbol, count, color, knocked, ...props }, ref) => {
43+
const hideBold = useSettingValue("feature_hidebold");
5344

54-
// Don't show a badge if we don't need to
55-
if ((color === NotificationColor.None || (hideBold && color == NotificationColor.Bold)) && !knocked) {
56-
return <></>;
57-
}
45+
// Don't show a badge if we don't need to
46+
if ((color === NotificationColor.None || (hideBold && color == NotificationColor.Bold)) && !knocked) {
47+
return <></>;
48+
}
5849

59-
const hasUnreadCount = color >= NotificationColor.Grey && (!!count || !!symbol);
50+
const hasUnreadCount = color >= NotificationColor.Grey && (!!count || !!symbol);
6051

61-
const isEmptyBadge = symbol === null && count === 0;
52+
const isEmptyBadge = symbol === null && count === 0;
6253

63-
if (symbol === null && count > 0) {
64-
symbol = formatCount(count);
65-
}
54+
if (symbol === null && count > 0) {
55+
symbol = formatCount(count);
56+
}
6657

67-
const classes = classNames({
68-
mx_NotificationBadge: true,
69-
mx_NotificationBadge_visible: isEmptyBadge || knocked ? true : hasUnreadCount,
70-
mx_NotificationBadge_highlighted: color >= NotificationColor.Red,
71-
mx_NotificationBadge_dot: isEmptyBadge && !knocked,
72-
mx_NotificationBadge_knocked: knocked,
73-
mx_NotificationBadge_2char: symbol && symbol.length > 0 && symbol.length < 3,
74-
mx_NotificationBadge_3char: symbol && symbol.length > 2,
75-
});
58+
const classes = classNames({
59+
mx_NotificationBadge: true,
60+
mx_NotificationBadge_visible: isEmptyBadge || knocked ? true : hasUnreadCount,
61+
mx_NotificationBadge_highlighted: color >= NotificationColor.Red,
62+
mx_NotificationBadge_dot: isEmptyBadge && !knocked,
63+
mx_NotificationBadge_knocked: knocked,
64+
mx_NotificationBadge_2char: symbol && symbol.length > 0 && symbol.length < 3,
65+
mx_NotificationBadge_3char: symbol && symbol.length > 2,
66+
});
67+
68+
if (props.onClick) {
69+
return (
70+
<AccessibleButton {...props} className={classes} onClick={props.onClick} ref={ref}>
71+
<span className="mx_NotificationBadge_count">{symbol}</span>
72+
{props.children}
73+
</AccessibleButton>
74+
);
75+
}
7676

77-
if (props.onClick) {
7877
return (
79-
<AccessibleButton
80-
aria-label={props.label}
81-
{...props}
82-
className={classes}
83-
onClick={props.onClick}
84-
onMouseOver={props.onMouseOver}
85-
onMouseLeave={props.onMouseLeave}
86-
>
78+
<div className={classes} ref={ref}>
8779
<span className="mx_NotificationBadge_count">{symbol}</span>
88-
{props.children}
89-
</AccessibleButton>
80+
</div>
9081
);
91-
}
92-
93-
return (
94-
<div className={classes}>
95-
<span className="mx_NotificationBadge_count">{symbol}</span>
96-
</div>
97-
);
98-
}
82+
},
83+
);

test/components/structures/auth/ForgotPassword-test.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,9 @@ describe("<ForgotPassword>", () => {
235235
expect.any(String),
236236
2, // second send attempt
237237
);
238-
expect(screen.getByText("Verification link email resent!")).toBeInTheDocument();
238+
expect(
239+
screen.getByRole("tooltip", { name: "Verification link email resent!" }),
240+
).toBeInTheDocument();
239241
});
240242
});
241243

test/components/views/rooms/NotificationBadge/NotificationBadge-test.tsx

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,12 @@ describe("NotificationBadge", () => {
2626
it("lets you click it", () => {
2727
const cb = jest.fn();
2828

29-
const { container } = render(
30-
<StatelessNotificationBadge
31-
symbol=""
32-
color={NotificationColor.Red}
33-
count={5}
34-
onClick={cb}
35-
onMouseOver={cb}
36-
onMouseLeave={cb}
37-
/>,
29+
const { getByRole } = render(
30+
<StatelessNotificationBadge symbol="" color={NotificationColor.Red} count={5} onClick={cb} />,
3831
);
3932

40-
fireEvent.click(container.firstChild!);
33+
fireEvent.click(getByRole("button")!);
4134
expect(cb).toHaveBeenCalledTimes(1);
42-
43-
fireEvent.mouseEnter(container.firstChild!);
44-
expect(cb).toHaveBeenCalledTimes(2);
45-
46-
fireEvent.mouseLeave(container.firstChild!);
47-
expect(cb).toHaveBeenCalledTimes(3);
4835
});
4936

5037
it("hides the bold icon when the settings is set", () => {

0 commit comments

Comments
 (0)