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

Use Compound Tooltips in StatelessNotificationBadge, VerifyEmailModal, CheckEmail #12084

Merged
merged 23 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
788e98d
Switch StatelessNotificationBadge to using Compound Tooltips
t3chguy Dec 21, 2023
60e1f56
Fix test
t3chguy Dec 21, 2023
d398563
Migrate CheckEmail & VerifyEmailModal to Compound tooltips
t3chguy Dec 22, 2023
84ed72d
Fix test
t3chguy Dec 22, 2023
cc0c7d9
Merge branch 'develop' into t3chguy/cr/157-11
t3chguy Dec 22, 2023
4d116f5
Fix CSS stacking contexts for Dialogs & PersistedElement
t3chguy Dec 29, 2023
53e30e2
Switch to PersistedElement sharing a CSS stacking context for z-index…
t3chguy Dec 29, 2023
c9783de
Fix Widget PIP overlay being under the widget and dragging being broken
t3chguy Dec 29, 2023
1261e9e
Merge branch 'develop' into t3chguy/fix/26805
t3chguy Dec 29, 2023
0a9a97f
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into…
t3chguy Jan 2, 2024
02177e9
Fix border-radius on widget pip
t3chguy Jan 2, 2024
76d5d19
Fix majority of tests
t3chguy Jan 2, 2024
7ca123a
Fix jest retryTimes applying outside of CI
t3chguy Jan 2, 2024
fbdcd96
Fix remaining tests
t3chguy Jan 2, 2024
977216c
Fix React unique key warnings
t3chguy Jan 2, 2024
8ad9790
Merge branch 'develop' into t3chguy/fix/26805
t3chguy Jan 2, 2024
1678e4b
Fix sticker picker
t3chguy Jan 2, 2024
d0cf422
Merge remote-tracking branch 'origin/t3chguy/fix/26805' into t3chguy/…
t3chguy Jan 2, 2024
80baf34
id not class
t3chguy Jan 2, 2024
caefcf4
Fix widget pip button colour in light theme
t3chguy Jan 4, 2024
7920072
Merge branch 't3chguy/fix/26805' of github.com:matrix-org/matrix-reac…
t3chguy Jan 5, 2024
ff5f89e
Merge remote-tracking branch 'origin/t3chguy/cr/157-11' into t3chguy/…
t3chguy Jan 5, 2024
e5addae
Merge branch 'develop' into t3chguy/cr/157-11
t3chguy Jan 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions res/css/views/rooms/_NotificationBadge.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,3 @@ limitations under the License.
}
}
}

.mx_NotificationBadge_tooltip {
display: inline-block;
position: relative;
top: -25px;
left: 6px;
}
26 changes: 8 additions & 18 deletions src/components/structures/auth/forgot-password/CheckEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

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

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

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

const onResendClickFn = async (): Promise<void> => {
Expand All @@ -67,21 +66,12 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
<input onClick={onSubmitForm} type="button" className="mx_Login_submit" value={_t("action|next")} />
<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<AccessibleButton
className="mx_AuthBody_resend-button"
kind="link"
onClick={onResendClickFn}
aria-describedby={tooltipVisible ? tooltipId : undefined}
>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
<Tooltip
id={tooltipId}
label={_t("auth|check_email_resend_tooltip")}
alignment={Alignment.Top}
visible={tooltipVisible}
/>
</AccessibleButton>
<Tooltip label={_t("auth|check_email_resend_tooltip")} side="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
</AccessibleButton>
</Tooltip>
</div>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

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

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

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

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

<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<AccessibleButton
className="mx_AuthBody_resend-button"
kind="link"
onClick={onResendClickFn}
aria-describedby={tooltipVisible ? tooltipId : undefined}
>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
<Tooltip
id={tooltipId}
label={_t("auth|check_email_resend_tooltip")}
alignment={Alignment.Top}
visible={tooltipVisible}
/>
</AccessibleButton>
<Tooltip label={_t("auth|check_email_resend_tooltip")} side="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}
</AccessibleButton>
</Tooltip>
{errorText && <ErrorMessage message={errorText} />}
</div>

Expand Down
46 changes: 14 additions & 32 deletions src/components/views/rooms/NotificationBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

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

import SettingsStore from "../../../settings/SettingsStore";
import { XOR } from "../../../@types/common";
import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
import Tooltip from "../elements/Tooltip";
import { _t } from "../../../languageHandler";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { StatelessNotificationBadge } from "./NotificationBadge/StatelessNotificationBadge";
Expand Down Expand Up @@ -48,8 +48,7 @@ interface IClickableProps extends IProps, React.InputHTMLAttributes<Element> {
}

interface IState {
showCounts: boolean; // whether or not to show counts. Independent of props.forceCount
showTooltip: boolean;
showCounts: boolean; // whether to show counts. Independent of props.forceCount
}

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

this.state = {
showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId),
showTooltip: false,
};

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

private onMouseOver = (e: MouseEvent): void => {
e.stopPropagation();
this.setState({
showTooltip: true,
});
};

private onMouseLeave = (): void => {
this.setState({
showTooltip: false,
});
};

public render(): ReactNode {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { notification, showUnsentTooltip, forceCount, onClick, tabIndex } = this.props;
Expand All @@ -119,31 +104,28 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
if (!notification.hasUnreadCount) return null; // Can't render a badge
}

let label: string | undefined;
let tooltip: JSX.Element | undefined;
if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) {
label = _t("notifications|message_didnt_send");
tooltip = <Tooltip className="mx_NotificationBadge_tooltip" label={label} />;
}

const commonProps: React.ComponentProps<typeof StatelessNotificationBadge> = {
label,
symbol: notification.symbol,
count: notification.count,
color: notification.color,
knocked: notification.knocked,
onMouseOver: this.onMouseOver,
onMouseLeave: this.onMouseLeave,
};

let badge: JSX.Element;
if (onClick) {
badge = <StatelessNotificationBadge {...commonProps} onClick={onClick} tabIndex={tabIndex} />;
} else {
badge = <StatelessNotificationBadge {...commonProps} />;
}

if (showUnsentTooltip && notification.color === NotificationColor.Unsent) {
return (
<StatelessNotificationBadge {...commonProps} onClick={onClick} tabIndex={tabIndex}>
{tooltip}
</StatelessNotificationBadge>
<Tooltip label={_t("notifications|message_didnt_send")} side="right">
{badge}
</Tooltip>
);
}

return <StatelessNotificationBadge {...commonProps}>{tooltip}</StatelessNotificationBadge>;
return badge;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { MouseEvent, ReactNode } from "react";
import React, { forwardRef } from "react";
import classNames from "classnames";

import { formatCount } from "../../../../utils/FormattingUtils";
Expand All @@ -28,10 +28,6 @@ interface Props {
count: number;
color: NotificationColor;
knocked?: boolean;
onMouseOver?: (ev: MouseEvent) => void;
onMouseLeave?: (ev: MouseEvent) => void;
children?: ReactNode;
label?: string;
}

interface ClickableProps extends Props {
Expand All @@ -42,57 +38,46 @@ interface ClickableProps extends Props {
tabIndex?: number;
}

export function StatelessNotificationBadge({
symbol,
count,
color,
knocked,
...props
}: XOR<Props, ClickableProps>): JSX.Element {
const hideBold = useSettingValue("feature_hidebold");
export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props, ClickableProps>>(
({ symbol, count, color, knocked, ...props }, ref) => {
const hideBold = useSettingValue("feature_hidebold");

// Don't show a badge if we don't need to
if ((color === NotificationColor.None || (hideBold && color == NotificationColor.Bold)) && !knocked) {
return <></>;
}
// Don't show a badge if we don't need to
if ((color === NotificationColor.None || (hideBold && color == NotificationColor.Bold)) && !knocked) {
return <></>;
}

const hasUnreadCount = color >= NotificationColor.Grey && (!!count || !!symbol);
const hasUnreadCount = color >= NotificationColor.Grey && (!!count || !!symbol);

const isEmptyBadge = symbol === null && count === 0;
const isEmptyBadge = symbol === null && count === 0;

if (symbol === null && count > 0) {
symbol = formatCount(count);
}
if (symbol === null && count > 0) {
symbol = formatCount(count);
}

const classes = classNames({
mx_NotificationBadge: true,
mx_NotificationBadge_visible: isEmptyBadge || knocked ? true : hasUnreadCount,
mx_NotificationBadge_highlighted: color >= NotificationColor.Red,
mx_NotificationBadge_dot: isEmptyBadge && !knocked,
mx_NotificationBadge_knocked: knocked,
mx_NotificationBadge_2char: symbol && symbol.length > 0 && symbol.length < 3,
mx_NotificationBadge_3char: symbol && symbol.length > 2,
});
const classes = classNames({
mx_NotificationBadge: true,
mx_NotificationBadge_visible: isEmptyBadge || knocked ? true : hasUnreadCount,
mx_NotificationBadge_highlighted: color >= NotificationColor.Red,
mx_NotificationBadge_dot: isEmptyBadge && !knocked,
mx_NotificationBadge_knocked: knocked,
mx_NotificationBadge_2char: symbol && symbol.length > 0 && symbol.length < 3,
mx_NotificationBadge_3char: symbol && symbol.length > 2,
});

if (props.onClick) {
return (
<AccessibleButton {...props} className={classes} onClick={props.onClick} ref={ref}>
<span className="mx_NotificationBadge_count">{symbol}</span>
{props.children}
</AccessibleButton>
);
}

if (props.onClick) {
return (
<AccessibleButton
aria-label={props.label}
{...props}
className={classes}
onClick={props.onClick}
onMouseOver={props.onMouseOver}
onMouseLeave={props.onMouseLeave}
>
<div className={classes} ref={ref}>
<span className="mx_NotificationBadge_count">{symbol}</span>
{props.children}
</AccessibleButton>
</div>
);
}

return (
<div className={classes}>
<span className="mx_NotificationBadge_count">{symbol}</span>
</div>
);
}
},
);
4 changes: 3 additions & 1 deletion test/components/structures/auth/ForgotPassword-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ describe("<ForgotPassword>", () => {
expect.any(String),
2, // second send attempt
);
expect(screen.getByText("Verification link email resent!")).toBeInTheDocument();
expect(
screen.getByRole("tooltip", { name: "Verification link email resent!" }),
).toBeInTheDocument();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,12 @@ describe("NotificationBadge", () => {
it("lets you click it", () => {
const cb = jest.fn();

const { container } = render(
<StatelessNotificationBadge
symbol=""
color={NotificationColor.Red}
count={5}
onClick={cb}
onMouseOver={cb}
onMouseLeave={cb}
/>,
const { getByRole } = render(
<StatelessNotificationBadge symbol="" color={NotificationColor.Red} count={5} onClick={cb} />,
);

fireEvent.click(container.firstChild!);
fireEvent.click(getByRole("button")!);
expect(cb).toHaveBeenCalledTimes(1);

fireEvent.mouseEnter(container.firstChild!);
expect(cb).toHaveBeenCalledTimes(2);

fireEvent.mouseLeave(container.firstChild!);
expect(cb).toHaveBeenCalledTimes(3);
});

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