Improve AccessibleButton & related types (#12075)

* Fix wrong type enum usage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Use improved type definition for forwardRef which enables Generic props

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve AccessibleButton & related Props types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove useless comment

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
pull/28788/head^2
Michael Telatynski 2023-12-20 14:42:31 +00:00 committed by GitHub
parent e26d3e9b68
commit af31965866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 109 additions and 79 deletions

24
src/@types/react.d.ts vendored Normal file
View File

@ -0,0 +1,24 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { PropsWithChildren } from "react";
declare module "react" {
// Fix forwardRef types for Generic components - https://stackoverflow.com/a/58473012
function forwardRef<T, P = {}>(
render: (props: PropsWithChildren<P>, ref: React.ForwardedRef<T>) => React.ReactElement | null,
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}

View File

@ -16,25 +16,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
label?: string;
// whether or not the context menu is currently open
// whether the context menu is currently open
isExpanded: boolean;
}
};
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton: React.FC<IProps> = ({
export const ContextMenuButton = <T extends keyof JSX.IntrinsicElements>({
label,
isExpanded,
children,
onClick,
onContextMenu,
...props
}) => {
}: Props<T>): JSX.Element => {
return (
<AccessibleButton
{...props}

View File

@ -16,23 +16,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
// whether or not the context menu is currently open
type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleTooltipButton<T>> & {
// whether the context menu is currently open
isExpanded: boolean;
}
};
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuTooltipButton: React.FC<IProps> = ({
export const ContextMenuTooltipButton = <T extends keyof JSX.IntrinsicElements>({
isExpanded,
children,
onClick,
onContextMenu,
...props
}) => {
}: Props<T>): JSX.Element => {
return (
<AccessibleTooltipButton
{...props}

View File

@ -14,25 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";
interface IProps extends Omit<React.ComponentProps<typeof AccessibleButton>, "inputRef" | "tabIndex"> {
type Props<T extends keyof JSX.IntrinsicElements> = Omit<
ComponentProps<typeof AccessibleButton<T>>,
"inputRef" | "tabIndex"
> & {
inputRef?: Ref;
focusOnMouseOver?: boolean;
}
};
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton: React.FC<IProps> = ({
export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({
inputRef,
onFocus,
onMouseOver,
focusOnMouseOver,
...props
}) => {
}: Props<T>): JSX.Element => {
const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
return (
<AccessibleButton

View File

@ -14,19 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";
type ATBProps = React.ComponentProps<typeof AccessibleTooltipButton>;
interface IProps extends Omit<ATBProps, "inputRef" | "tabIndex"> {
type Props<T extends keyof JSX.IntrinsicElements> = Omit<
ComponentProps<typeof AccessibleTooltipButton<T>>,
"tabIndex"
> & {
inputRef?: Ref;
}
};
// Wrapper to allow use of useRovingTabIndex for simple AccessibleTooltipButtons outside of React Functional Components.
export const RovingAccessibleTooltipButton: React.FC<IProps> = ({ inputRef, onFocus, ...props }) => {
export const RovingAccessibleTooltipButton = <T extends keyof JSX.IntrinsicElements>({
inputRef,
onFocus,
...props
}: Props<T>): JSX.Element => {
const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
return (
<AccessibleTooltipButton

View File

@ -14,28 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactNode } from "react";
import React, { ComponentProps, ReactNode } from "react";
import classNames from "classnames";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { _t } from "../../../languageHandler";
import { Playback, PlaybackState } from "../../../audio/Playback";
// omitted props are handled by render function
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick" | "disabled"> {
type Props = Omit<ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick" | "disabled" | "element"> & {
// Playback instance to manipulate. Cannot change during the component lifecycle.
playback: Playback;
// The playback phase to render. Able to change during the component lifecycle.
playbackPhase: PlaybackState;
}
};
/**
* Displays a play/pause button (activating the play/pause function of the recorder)
* to be displayed in reference to a recording.
*/
export default class PlayPauseButton extends React.PureComponent<IProps> {
public constructor(props: IProps) {
export default class PlayPauseButton extends React.PureComponent<Props> {
public constructor(props: Props) {
super(props);
}

View File

@ -89,7 +89,10 @@ type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> &
onClick: ((e: ButtonEvent) => void | Promise<void>) | null;
};
export interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> {
/**
* Type of the props passed to the element that is rendered by AccessibleButton.
*/
interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
ref?: React.Ref<Element>;
}
@ -114,7 +117,7 @@ export default function AccessibleButton<T extends keyof JSX.IntrinsicElements>(
triggerOnMouseDown,
...restProps
}: Props<T>): JSX.Element {
const newProps: IAccessibleButtonProps = restProps;
const newProps: RenderedElementProps = restProps;
if (disabled) {
newProps["aria-disabled"] = true;
newProps["disabled"] = true;

View File

@ -25,7 +25,7 @@ import Tooltip, { Alignment } from "./Tooltip";
*
* Extends that of {@link AccessibleButton}.
*/
interface Props extends React.ComponentProps<typeof AccessibleButton> {
type Props<T extends keyof JSX.IntrinsicElements> = React.ComponentProps<typeof AccessibleButton<T>> & {
/**
* Title to show in the tooltip and use as aria-label
*/
@ -58,9 +58,9 @@ interface Props extends React.ComponentProps<typeof AccessibleButton> {
* Function to call when the tooltip goes from shown to hidden.
*/
onHideTooltip?(ev: SyntheticEvent): void;
}
};
function AccessibleTooltipButton({
function AccessibleTooltipButton<T extends keyof JSX.IntrinsicElements>({
title,
tooltip,
children,
@ -69,7 +69,7 @@ function AccessibleTooltipButton({
onHideTooltip,
tooltipClassName,
...props
}: Props): JSX.Element {
}: Props<T>): JSX.Element {
const [hover, setHover] = useState(false);
useEffect(() => {

View File

@ -14,19 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import InfoDialog from "../dialogs/InfoDialog";
import AccessibleButton, { IAccessibleButtonProps } from "./AccessibleButton";
import AccessibleButton from "./AccessibleButton";
export interface LearnMoreProps extends IAccessibleButtonProps {
type Props = Omit<ComponentProps<typeof AccessibleButton>, "kind" | "onClick" | "className"> & {
title: string;
description: string | React.ReactNode;
}
};
const LearnMore: React.FC<LearnMoreProps> = ({ title, description, ...rest }) => {
const LearnMore: React.FC<Props> = ({ title, description, ...rest }) => {
const onClick = (): void => {
Modal.createDialog(InfoDialog, {
title,

View File

@ -35,7 +35,6 @@ import FacePile from "../elements/FacePile";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { CallDuration, SessionDuration } from "../voip/CallDuration";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { ContinueKind } from "../auth/InteractiveAuthEntryComponents";
const MAX_FACES = 8;
@ -127,7 +126,7 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
);
const [buttonText, buttonKind, onButtonClick] = useMemo<
[string, ContinueKind, null | ((ev: ButtonEvent) => void)]
[string, AccessibleButtonKind, null | ((ev: ButtonEvent) => void)]
>(() => {
switch (connectionState) {
case ConnectionState.Disconnected:

View File

@ -73,11 +73,7 @@ export default function ExtraTile({
);
if (isMinimized) nameContainer = null;
let Button = RovingAccessibleButton;
if (isMinimized) {
Button = RovingAccessibleTooltipButton;
}
const Button = isMinimized ? RovingAccessibleTooltipButton : RovingAccessibleButton;
return (
<Button
className={classes}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ForwardedRef, forwardRef } from "react";
import React, { ForwardedRef, forwardRef, FunctionComponent } from "react";
import { FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg";
import { logger } from "matrix-js-sdk/src/logger";
@ -120,6 +120,6 @@ const WysiwygAutocomplete = forwardRef(
},
);
WysiwygAutocomplete.displayName = "WysiwygAutocomplete";
(WysiwygAutocomplete as FunctionComponent).displayName = "WysiwygAutocomplete";
export { WysiwygAutocomplete };

View File

@ -15,18 +15,25 @@ limitations under the License.
*/
import classNames from "classnames";
import React from "react";
import React, { ComponentProps } from "react";
import { Icon as CaretIcon } from "../../../../../res/img/feather-customised/dropdown-arrow.svg";
import { _t } from "../../../../languageHandler";
import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton";
interface Props extends React.ComponentProps<typeof AccessibleTooltipButton> {
type Props<T extends keyof JSX.IntrinsicElements> = Omit<
ComponentProps<typeof AccessibleTooltipButton<T>>,
"aria-label" | "title" | "kind" | "className" | "onClick"
> & {
isExpanded: boolean;
onClick: () => void;
}
};
export const DeviceExpandDetailsButton: React.FC<Props> = ({ isExpanded, onClick, ...rest }) => {
export const DeviceExpandDetailsButton = <T extends keyof JSX.IntrinsicElements>({
isExpanded,
onClick,
...rest
}: Props<T>): JSX.Element => {
const label = isExpanded ? _t("settings|sessions|hide_details") : _t("settings|sessions|show_details");
return (
<AccessibleTooltipButton

View File

@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
import { _t } from "../../../../languageHandler";
import LearnMore, { LearnMoreProps } from "../../elements/LearnMore";
import LearnMore from "../../elements/LearnMore";
import { DeviceSecurityVariation } from "./types";
interface Props extends Omit<LearnMoreProps, "title" | "description"> {
type Props = Omit<ComponentProps<typeof LearnMore>, "title" | "description"> & {
variation: DeviceSecurityVariation;
}
};
const securityCardContent: Record<
DeviceSecurityVariation,

View File

@ -48,7 +48,10 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick" | "size"> {
type ButtonProps<T extends keyof JSX.IntrinsicElements> = Omit<
ComponentProps<typeof AccessibleTooltipButton<T>>,
"title" | "onClick" | "size"
> & {
space?: Room;
spaceKey?: SpaceKey;
className?: string;
@ -61,9 +64,9 @@ interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButto
innerRef?: RefObject<HTMLElement>;
ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
onClick?(ev?: ButtonEvent): void;
}
};
export const SpaceButton: React.FC<IButtonProps> = ({
export const SpaceButton = <T extends keyof JSX.IntrinsicElements>({
space,
spaceKey: _spaceKey,
className,
@ -77,7 +80,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
innerRef,
ContextMenuComponent,
...props
}) => {
}: ButtonProps<T>): JSX.Element => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>(innerRef);
const [onFocus, isActive] = useRovingTabIndex(handle);
const tabIndex = isActive ? 0 : -1;

View File

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { createRef, useState } from "react";
import React, { ComponentProps, createRef, useState } from "react";
import classNames from "classnames";
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
@ -42,14 +42,13 @@ const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the butt
const CONTROLS_HIDE_DELAY = 2000;
interface IButtonProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "title"> {
type ButtonProps = Omit<ComponentProps<typeof AccessibleTooltipButton>, "title" | "element"> & {
state: boolean;
className: string;
onLabel?: string;
offLabel?: string;
}
};
const LegacyCallViewToggleButton: React.FC<IButtonProps> = ({
const LegacyCallViewToggleButton: React.FC<ButtonProps> = ({
children,
state: isOn,
className,
@ -74,7 +73,7 @@ const LegacyCallViewToggleButton: React.FC<IButtonProps> = ({
);
};
interface IDropdownButtonProps extends IButtonProps {
interface IDropdownButtonProps extends ButtonProps {
deviceKinds: MediaDeviceKindEnum[];
}

View File

@ -14,15 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {
ComponentClass,
createContext,
forwardRef,
PropsWithoutRef,
ForwardRefExoticComponent,
useContext,
RefAttributes,
} from "react";
import React, { ComponentClass, createContext, forwardRef, useContext } from "react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
// This context is available to components under LoggedInView,
@ -42,10 +34,9 @@ export function useMatrixClientContext(): MatrixClient {
const matrixHOC = <ComposedComponentProps extends {}>(
ComposedComponent: ComponentClass<ComposedComponentProps>,
): ForwardRefExoticComponent<
PropsWithoutRef<Omit<ComposedComponentProps, "mxClient">> &
RefAttributes<InstanceType<ComponentClass<ComposedComponentProps>>>
> => {
): ((
props: Omit<ComposedComponentProps, "mxClient"> & React.RefAttributes<InstanceType<typeof ComposedComponent>>,
) => React.ReactElement | null) => {
type ComposedComponentInstance = InstanceType<typeof ComposedComponent>;
// eslint-disable-next-line react-hooks/rules-of-hooks