Prepare for switching AccessibleButton and derivatives to forwardRef (#12072)
* Improve AccessibleButton props & docs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve AccessibleTooltipButton props docs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Simplify roving tab index hook usage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Ditch RefObject type casts Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Convert AccessibleTooltipButton to a Functional Component Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
2212fbadd0
commit
bf61d93bf4
13 changed files with 140 additions and 103 deletions
|
@ -26,7 +26,10 @@ import {
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import getEntryComponentForLoginType, { IStageComponent } from "../views/auth/InteractiveAuthEntryComponents";
|
import getEntryComponentForLoginType, {
|
||||||
|
ContinueKind,
|
||||||
|
IStageComponent,
|
||||||
|
} from "../views/auth/InteractiveAuthEntryComponents";
|
||||||
import Spinner from "../views/elements/Spinner";
|
import Spinner from "../views/elements/Spinner";
|
||||||
|
|
||||||
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
||||||
|
@ -59,7 +62,7 @@ export interface InteractiveAuthProps<T> {
|
||||||
continueIsManaged?: boolean;
|
continueIsManaged?: boolean;
|
||||||
// continueText and continueKind are passed straight through to the AuthEntryComponent.
|
// continueText and continueKind are passed straight through to the AuthEntryComponent.
|
||||||
continueText?: string;
|
continueText?: string;
|
||||||
continueKind?: string;
|
continueKind?: ContinueKind;
|
||||||
// callback
|
// callback
|
||||||
makeRequest(auth: IAuthDict | null): Promise<T>;
|
makeRequest(auth: IAuthDict | null): Promise<T>;
|
||||||
// callback called when the auth process has finished,
|
// callback called when the auth process has finished,
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import { EventType, RoomType, JoinRule, Preset, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
import { EventType, RoomType, JoinRule, Preset, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import React, { RefObject, useCallback, useContext, useRef, useState } from "react";
|
import React, { useCallback, useContext, useRef, useState } from "react";
|
||||||
|
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import createRoom, { IOpts } from "../../createRoom";
|
import createRoom, { IOpts } from "../../createRoom";
|
||||||
|
@ -499,7 +499,7 @@ const SpaceSetupPrivateInvite: React.FC<{
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const numFields = 3;
|
const numFields = 3;
|
||||||
const fieldRefs = [useRef(), useRef(), useRef()] as RefObject<Field>[];
|
const fieldRefs = [useRef<Field>(null), useRef<Field>(null), useRef<Field>(null)];
|
||||||
const [emailAddresses, setEmailAddress] = useStateArray(numFields, "");
|
const [emailAddresses, setEmailAddress] = useStateArray(numFields, "");
|
||||||
const fields = new Array(numFields).fill(0).map((x, i) => {
|
const fields = new Array(numFields).fill(0).map((x, i) => {
|
||||||
const name = "emailAddress" + i;
|
const name = "emailAddress" + i;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { _t } from "../../../languageHandler";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { LocalisedPolicy, Policies } from "../../../Terms";
|
import { LocalisedPolicy, Policies } from "../../../Terms";
|
||||||
import { AuthHeaderModifier } from "../../structures/auth/header/AuthHeaderModifier";
|
import { AuthHeaderModifier } from "../../structures/auth/header/AuthHeaderModifier";
|
||||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
import AccessibleButton, { AccessibleButtonKind, ButtonEvent } from "../elements/AccessibleButton";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import Spinner from "../elements/Spinner";
|
import Spinner from "../elements/Spinner";
|
||||||
|
@ -780,9 +780,12 @@ export class RegistrationTokenAuthEntry extends React.Component<IAuthEntryProps,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subset of AccessibleButtonKind which can be specified for the continue button
|
||||||
|
export type ContinueKind = Extract<AccessibleButtonKind, "primary" | "danger">;
|
||||||
|
|
||||||
interface ISSOAuthEntryProps extends IAuthEntryProps {
|
interface ISSOAuthEntryProps extends IAuthEntryProps {
|
||||||
continueText?: string;
|
continueText?: string;
|
||||||
continueKind?: string;
|
continueKind?: ContinueKind;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,7 +869,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
|
||||||
const cancelButton = (
|
const cancelButton = (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this.props.onCancel ?? null}
|
onClick={this.props.onCancel ?? null}
|
||||||
kind={this.props.continueKind ? this.props.continueKind + "_outline" : "primary_outline"}
|
kind={this.props.continueKind ? `${this.props.continueKind}_outline` : "primary_outline"}
|
||||||
>
|
>
|
||||||
{_t("action|cancel")}
|
{_t("action|cancel")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -985,7 +988,7 @@ export interface IStageComponentProps extends IAuthEntryProps {
|
||||||
inputs?: IInputs;
|
inputs?: IInputs;
|
||||||
stageState?: IStageStatus;
|
stageState?: IStageStatus;
|
||||||
continueText?: string;
|
continueText?: string;
|
||||||
continueKind?: string;
|
continueKind?: ContinueKind;
|
||||||
setEmailSid?(sid: string): void;
|
setEmailSid?(sid: string): void;
|
||||||
onCancel?(): void;
|
onCancel?(): void;
|
||||||
requestEmailToken?(): Promise<void>;
|
requestEmailToken?(): Promise<void>;
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactElement, ReactNode, RefObject, useContext, useMemo, useRef, useState } from "react";
|
import React, { ReactElement, ReactNode, useContext, useMemo, useRef, useState } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room, EventType } from "matrix-js-sdk/src/matrix";
|
import { Room, EventType } from "matrix-js-sdk/src/matrix";
|
||||||
import { sleep } from "matrix-js-sdk/src/utils";
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
|
@ -144,7 +144,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
[cli, msc3946ProcessDynamicPredecessor],
|
[cli, msc3946ProcessDynamicPredecessor],
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollRef = useRef() as RefObject<AutoHideScrollbar<"div">>;
|
const scrollRef = useRef<AutoHideScrollbar<"div">>(null);
|
||||||
const [scrollState, setScrollState] = useState<IScrollState>({
|
const [scrollState, setScrollState] = useState<IScrollState>({
|
||||||
// these are estimates which update as soon as it mounts
|
// these are estimates which update as soon as it mounts
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
|
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
|
||||||
import { DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
import { ContinueKind, DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
@ -34,7 +34,7 @@ type DialogAesthetics = Partial<{
|
||||||
[x: number]: {
|
[x: number]: {
|
||||||
body: string;
|
body: string;
|
||||||
continueText?: string;
|
continueText?: string;
|
||||||
continueKind?: string;
|
continueKind?: ContinueKind;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
@ -53,7 +53,7 @@ interface IState {
|
||||||
// next to the InteractiveAuth component.
|
// next to the InteractiveAuth component.
|
||||||
bodyText?: string;
|
bodyText?: string;
|
||||||
continueText?: string;
|
continueText?: string;
|
||||||
continueKind?: string;
|
continueKind?: ContinueKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DeactivateAccountDialog extends React.Component<IProps, IState> {
|
export default class DeactivateAccountDialog extends React.Component<IProps, IState> {
|
||||||
|
@ -98,7 +98,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
|
||||||
const aesthetics = DEACTIVATE_AESTHETICS[stage];
|
const aesthetics = DEACTIVATE_AESTHETICS[stage];
|
||||||
let bodyText: string | undefined;
|
let bodyText: string | undefined;
|
||||||
let continueText: string | undefined;
|
let continueText: string | undefined;
|
||||||
let continueKind: string | undefined;
|
let continueKind: ContinueKind | undefined;
|
||||||
if (aesthetics) {
|
if (aesthetics) {
|
||||||
const phaseAesthetics = aesthetics[phase];
|
const phaseAesthetics = aesthetics[phase];
|
||||||
if (phaseAesthetics) {
|
if (phaseAesthetics) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import InteractiveAuth, {
|
||||||
InteractiveAuthCallback,
|
InteractiveAuthCallback,
|
||||||
InteractiveAuthProps,
|
InteractiveAuthProps,
|
||||||
} from "../../structures/InteractiveAuth";
|
} from "../../structures/InteractiveAuth";
|
||||||
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
import { ContinueKind, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
|
|
||||||
type DialogAesthetics = Partial<{
|
type DialogAesthetics = Partial<{
|
||||||
|
@ -36,7 +36,7 @@ type DialogAesthetics = Partial<{
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
continueText: string;
|
continueText: string;
|
||||||
continueKind: string;
|
continueKind: ContinueKind;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
@ -146,7 +146,7 @@ export default class InteractiveAuthDialog<T> extends React.Component<Interactiv
|
||||||
let title = this.state.authError ? "Error" : this.props.title || _t("common|authentication");
|
let title = this.state.authError ? "Error" : this.props.title || _t("common|authentication");
|
||||||
let body = this.state.authError ? null : this.props.body;
|
let body = this.state.authError ? null : this.props.body;
|
||||||
let continueText: string | undefined;
|
let continueText: string | undefined;
|
||||||
let continueKind: string | undefined;
|
let continueKind: ContinueKind | undefined;
|
||||||
const dialogAesthetics = this.props.aestheticsForStagePhases || this.getDefaultDialogAesthetics();
|
const dialogAesthetics = this.props.aestheticsForStagePhases || this.getDefaultDialogAesthetics();
|
||||||
if (!this.state.authError && dialogAesthetics) {
|
if (!this.state.authError && dialogAesthetics) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -33,7 +33,7 @@ import {
|
||||||
|
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import { _t, getUserLanguage } from "../../../languageHandler";
|
import { _t, getUserLanguage } from "../../../languageHandler";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton, { AccessibleButtonKind } from "../elements/AccessibleButton";
|
||||||
import { StopGapWidgetDriver } from "../../../stores/widgets/StopGapWidgetDriver";
|
import { StopGapWidgetDriver } from "../../../stores/widgets/StopGapWidgetDriver";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
||||||
|
@ -158,7 +158,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
||||||
.slice(0, MAX_BUTTONS)
|
.slice(0, MAX_BUTTONS)
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((def) => {
|
.map((def) => {
|
||||||
let kind = "secondary";
|
let kind: AccessibleButtonKind = "secondary";
|
||||||
switch (def.kind) {
|
switch (def.kind) {
|
||||||
case ModalButtonKind.Primary:
|
case ModalButtonKind.Primary:
|
||||||
kind = "primary";
|
kind = "primary";
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { HTMLAttributes, InputHTMLAttributes, ReactNode } from "react";
|
import React, { HTMLAttributes, InputHTMLAttributes } from "react";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
|
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
|
@ -22,7 +22,10 @@ import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
|
|
||||||
export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element> | React.FormEvent<Element>;
|
export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element> | React.FormEvent<Element>;
|
||||||
|
|
||||||
type AccessibleButtonKind =
|
/**
|
||||||
|
* The kind of button, similar to how Bootstrap works.
|
||||||
|
*/
|
||||||
|
export type AccessibleButtonKind =
|
||||||
| "primary"
|
| "primary"
|
||||||
| "primary_outline"
|
| "primary_outline"
|
||||||
| "primary_sm"
|
| "primary_sm"
|
||||||
|
@ -58,25 +61,31 @@ type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<
|
||||||
Omit<InputHTMLAttributes<Element>, "onClick">;
|
Omit<InputHTMLAttributes<Element>, "onClick">;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* children: React's magic prop. Represents all children given to the element.
|
* Type of props accepted by {@link AccessibleButton}.
|
||||||
* element: (optional) The base element type. "div" by default.
|
*
|
||||||
* onClick: (required) Event handler for button activation. Should be
|
* Extends props accepted by the underlying element specified using the `element` prop.
|
||||||
* implemented exactly like a normal onClick handler.
|
|
||||||
*/
|
*/
|
||||||
type IProps<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> & {
|
type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> & {
|
||||||
inputRef?: React.Ref<Element>;
|
inputRef?: React.Ref<Element>;
|
||||||
|
/**
|
||||||
|
* The base element type. "div" by default.
|
||||||
|
*/
|
||||||
element?: T;
|
element?: T;
|
||||||
children?: ReactNode;
|
/**
|
||||||
// The kind of button, similar to how Bootstrap works.
|
* The kind of button, similar to how Bootstrap works.
|
||||||
// See available classes for AccessibleButton for options.
|
*/
|
||||||
kind?: AccessibleButtonKind | string;
|
kind?: AccessibleButtonKind;
|
||||||
// The ARIA role
|
/**
|
||||||
role?: string;
|
* Whether the button should be disabled.
|
||||||
// The tabIndex
|
*/
|
||||||
tabIndex?: number;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
className?: string;
|
/**
|
||||||
|
* Whether the button should trigger on mousedown event instead of on click event. Defaults to false (click event).
|
||||||
|
*/
|
||||||
triggerOnMouseDown?: boolean;
|
triggerOnMouseDown?: boolean;
|
||||||
|
/**
|
||||||
|
* Event handler for button activation. Should be implemented exactly like a normal `onClick` handler.
|
||||||
|
*/
|
||||||
onClick: ((e: ButtonEvent) => void | Promise<void>) | null;
|
onClick: ((e: ButtonEvent) => void | Promise<void>) | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,7 +113,7 @@ export default function AccessibleButton<T extends keyof JSX.IntrinsicElements>(
|
||||||
onKeyUp,
|
onKeyUp,
|
||||||
triggerOnMouseDown,
|
triggerOnMouseDown,
|
||||||
...restProps
|
...restProps
|
||||||
}: IProps<T>): JSX.Element {
|
}: Props<T>): JSX.Element {
|
||||||
const newProps: IAccessibleButtonProps = restProps;
|
const newProps: IAccessibleButtonProps = restProps;
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
newProps["aria-disabled"] = true;
|
newProps["aria-disabled"] = true;
|
||||||
|
|
|
@ -15,85 +15,105 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { SyntheticEvent, FocusEvent } from "react";
|
import React, { SyntheticEvent, FocusEvent, useEffect, useState } from "react";
|
||||||
|
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import Tooltip, { Alignment } from "./Tooltip";
|
import Tooltip, { Alignment } from "./Tooltip";
|
||||||
|
|
||||||
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
|
/**
|
||||||
|
* Type of props accepted by {@link AccessibleTooltipButton}.
|
||||||
|
*
|
||||||
|
* Extends that of {@link AccessibleButton}.
|
||||||
|
*/
|
||||||
|
interface Props extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
|
/**
|
||||||
|
* Title to show in the tooltip and use as aria-label
|
||||||
|
*/
|
||||||
title?: string;
|
title?: string;
|
||||||
|
/**
|
||||||
|
* Tooltip node to show in the tooltip, takes precedence over `title`
|
||||||
|
*/
|
||||||
tooltip?: React.ReactNode;
|
tooltip?: React.ReactNode;
|
||||||
|
/**
|
||||||
|
* Trigger label to render
|
||||||
|
*/
|
||||||
label?: string;
|
label?: string;
|
||||||
|
/**
|
||||||
|
* Classname to apply to the tooltip
|
||||||
|
*/
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
|
/**
|
||||||
|
* Force the tooltip to be hidden
|
||||||
|
*/
|
||||||
forceHide?: boolean;
|
forceHide?: boolean;
|
||||||
|
/**
|
||||||
|
* Alignment to render the tooltip with
|
||||||
|
*/
|
||||||
alignment?: Alignment;
|
alignment?: Alignment;
|
||||||
|
/**
|
||||||
|
* Function to call when the children are hovered over
|
||||||
|
*/
|
||||||
onHover?: (hovering: boolean) => void;
|
onHover?: (hovering: boolean) => void;
|
||||||
|
/**
|
||||||
|
* Function to call when the tooltip goes from shown to hidden.
|
||||||
|
*/
|
||||||
onHideTooltip?(ev: SyntheticEvent): void;
|
onHideTooltip?(ev: SyntheticEvent): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
function AccessibleTooltipButton({
|
||||||
hover: boolean;
|
title,
|
||||||
}
|
tooltip,
|
||||||
|
children,
|
||||||
|
forceHide,
|
||||||
|
alignment,
|
||||||
|
onHideTooltip,
|
||||||
|
tooltipClassName,
|
||||||
|
...props
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const [hover, setHover] = useState(false);
|
||||||
|
|
||||||
export default class AccessibleTooltipButton extends React.PureComponent<IProps, IState> {
|
useEffect(() => {
|
||||||
public constructor(props: IProps) {
|
// If forceHide is set then force hover to off to hide the tooltip
|
||||||
super(props);
|
if (forceHide && hover) {
|
||||||
this.state = {
|
setHover(false);
|
||||||
hover: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
|
||||||
if (!prevProps.forceHide && this.props.forceHide && this.state.hover) {
|
|
||||||
this.setState({
|
|
||||||
hover: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}, [forceHide, hover]);
|
||||||
|
|
||||||
private showTooltip = (): void => {
|
const showTooltip = (): void => {
|
||||||
if (this.props.onHover) this.props.onHover(true);
|
props.onHover?.(true);
|
||||||
if (this.props.forceHide) return;
|
if (forceHide) return;
|
||||||
this.setState({
|
setHover(true);
|
||||||
hover: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private hideTooltip = (ev: SyntheticEvent): void => {
|
const hideTooltip = (ev: SyntheticEvent): void => {
|
||||||
if (this.props.onHover) this.props.onHover(false);
|
props.onHover?.(false);
|
||||||
this.setState({
|
setHover(false);
|
||||||
hover: false,
|
onHideTooltip?.(ev);
|
||||||
});
|
|
||||||
this.props.onHideTooltip?.(ev);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onFocus = (ev: FocusEvent): void => {
|
const onFocus = (ev: FocusEvent): void => {
|
||||||
// We only show the tooltip if focus arrived here from some other
|
// We only show the tooltip if focus arrived here from some other
|
||||||
// element, to avoid leaving tooltips hanging around when a modal closes
|
// element, to avoid leaving tooltips hanging around when a modal closes
|
||||||
if (ev.relatedTarget) this.showTooltip();
|
if (ev.relatedTarget) showTooltip();
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
const tip = hover && (title || tooltip) && (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
<Tooltip tooltipClassName={tooltipClassName} label={tooltip || title} alignment={alignment} />
|
||||||
const { title, tooltip, children, tooltipClassName, forceHide, alignment, onHideTooltip, ...props } =
|
);
|
||||||
this.props;
|
return (
|
||||||
|
<AccessibleButton
|
||||||
const tip = this.state.hover && (title || tooltip) && (
|
{...props}
|
||||||
<Tooltip tooltipClassName={tooltipClassName} label={tooltip || title} alignment={alignment} />
|
onMouseOver={showTooltip}
|
||||||
);
|
onMouseLeave={hideTooltip}
|
||||||
return (
|
onFocus={onFocus}
|
||||||
<AccessibleButton
|
onBlur={hideTooltip}
|
||||||
{...props}
|
aria-label={title || props["aria-label"]}
|
||||||
onMouseOver={this.showTooltip || props.onMouseOver}
|
>
|
||||||
onMouseLeave={this.hideTooltip || props.onMouseLeave}
|
{children}
|
||||||
onFocus={this.onFocus || props.onFocus}
|
{props.label}
|
||||||
onBlur={this.hideTooltip || props.onBlur}
|
{(tooltip || title) && tip}
|
||||||
aria-label={title || props["aria-label"]}
|
</AccessibleButton>
|
||||||
>
|
);
|
||||||
{children}
|
|
||||||
{this.props.label}
|
|
||||||
{(tooltip || title) && tip}
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AccessibleTooltipButton;
|
||||||
|
|
|
@ -28,13 +28,14 @@ import {
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import type { ButtonEvent } from "../elements/AccessibleButton";
|
import { AccessibleButtonKind, ButtonEvent } from "../elements/AccessibleButton";
|
||||||
import MemberAvatar from "../avatars/MemberAvatar";
|
import MemberAvatar from "../avatars/MemberAvatar";
|
||||||
import { LiveContentSummary, LiveContentType } from "../rooms/LiveContentSummary";
|
import { LiveContentSummary, LiveContentType } from "../rooms/LiveContentSummary";
|
||||||
import FacePile from "../elements/FacePile";
|
import FacePile from "../elements/FacePile";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { CallDuration, SessionDuration } from "../voip/CallDuration";
|
import { CallDuration, SessionDuration } from "../voip/CallDuration";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
import { ContinueKind } from "../auth/InteractiveAuthEntryComponents";
|
||||||
|
|
||||||
const MAX_FACES = 8;
|
const MAX_FACES = 8;
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ interface ActiveCallEventProps {
|
||||||
call: ElementCall | null;
|
call: ElementCall | null;
|
||||||
participatingMembers: RoomMember[];
|
participatingMembers: RoomMember[];
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
buttonKind: string;
|
buttonKind: AccessibleButtonKind;
|
||||||
buttonDisabledTooltip?: string;
|
buttonDisabledTooltip?: string;
|
||||||
onButtonClick: ((ev: ButtonEvent) => void) | null;
|
onButtonClick: ((ev: ButtonEvent) => void) | null;
|
||||||
}
|
}
|
||||||
|
@ -125,7 +126,9 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
|
||||||
[call],
|
[call],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [buttonText, buttonKind, onButtonClick] = useMemo(() => {
|
const [buttonText, buttonKind, onButtonClick] = useMemo<
|
||||||
|
[string, ContinueKind, null | ((ev: ButtonEvent) => void)]
|
||||||
|
>(() => {
|
||||||
switch (connectionState) {
|
switch (connectionState) {
|
||||||
case ConnectionState.Disconnected:
|
case ConnectionState.Disconnected:
|
||||||
return [_t("action|join"), "primary", connect];
|
return [_t("action|join"), "primary", connect];
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropsWithChildren, useRef } from "react";
|
import React, { PropsWithChildren } from "react";
|
||||||
import { User } from "matrix-js-sdk/src/matrix";
|
import { User } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import ReadReceiptMarker, { IReadReceiptInfo } from "./ReadReceiptMarker";
|
import ReadReceiptMarker, { IReadReceiptInfo } from "./ReadReceiptMarker";
|
||||||
|
@ -284,8 +284,7 @@ interface ISectionHeaderProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SectionHeader({ className, children }: PropsWithChildren<ISectionHeaderProps>): JSX.Element {
|
function SectionHeader({ className, children }: PropsWithChildren<ISectionHeaderProps>): JSX.Element {
|
||||||
const ref = useRef<HTMLHeadingElement>(null);
|
const [onFocus, , ref] = useRovingTabIndex<HTMLHeadingElement>();
|
||||||
const [onFocus] = useRovingTabIndex(ref);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h3 className={className} role="menuitem" onFocus={onFocus} tabIndex={-1} ref={ref}>
|
<h3 className={className} role="menuitem" onFocus={onFocus} tabIndex={-1} ref={ref}>
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef, RefObject } from "react";
|
import React, { createRef } from "react";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ interface IReadReceiptMarkerStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ReadReceiptMarker extends React.PureComponent<IProps, IState> {
|
export default class ReadReceiptMarker extends React.PureComponent<IProps, IState> {
|
||||||
private avatar: React.RefObject<HTMLDivElement | HTMLImageElement | HTMLSpanElement> = createRef();
|
private avatar = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -199,7 +199,7 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
if (this.state.suppressDisplay) {
|
if (this.state.suppressDisplay) {
|
||||||
return <div ref={this.avatar as RefObject<HTMLDivElement>} />;
|
return <div ref={this.avatar} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton, { AccessibleButtonKind } from "../elements/AccessibleButton";
|
||||||
import Spinner from "../elements/Spinner";
|
import Spinner from "../elements/Spinner";
|
||||||
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
||||||
import { UserFriendlyError, _t, _td } from "../../../languageHandler";
|
import { UserFriendlyError, _t, _td } from "../../../languageHandler";
|
||||||
|
@ -45,7 +45,7 @@ interface IProps {
|
||||||
onError: (error: Error) => void;
|
onError: (error: Error) => void;
|
||||||
rowClassName?: string;
|
rowClassName?: string;
|
||||||
buttonClassName?: string;
|
buttonClassName?: string;
|
||||||
buttonKind?: string;
|
buttonKind?: AccessibleButtonKind;
|
||||||
buttonLabel?: string;
|
buttonLabel?: string;
|
||||||
confirm?: boolean;
|
confirm?: boolean;
|
||||||
// Whether to autoFocus the new password input
|
// Whether to autoFocus the new password input
|
||||||
|
|
Loading…
Reference in a new issue