Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ module.exports = {
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
"Use UIStore to access window dimensions instead.",
),
...buildRestrictedPropertiesOptions(
["React.forwardRef", "*.forwardRef", "forwardRef"],
"Use ref props instead.",
),
...buildRestrictedPropertiesOptions(
["*.mxcUrlToHttp", "*.getHttpUriForMxc"],
"Use Media helper instead to centralise access for customisation.",
Expand All @@ -55,6 +59,11 @@ module.exports = {
"error",
{
paths: [
{
name: "react",
importNames: ["forwardRef"],
message: "Use ref props instead.",
},
{
name: "@testing-library/react",
message: "Please use jest-matrix-react instead",
Expand Down
9 changes: 1 addition & 8 deletions src/@types/react.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import { type PropsWithChildren } from "react";

import type React from "react";
import { type ComponentType } from "react";

declare module "react" {
// Fix forwardRef types for Generic components - https://stackoverflow.com/a/58473012
function forwardRef<T, P extends object>(
render: (props: PropsWithChildren<P>, ref: React.ForwardedRef<T>) => React.ReactElement | null,
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;

// Fix lazy types - https://stackoverflow.com/a/71017028
function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;

Expand Down
10 changes: 6 additions & 4 deletions src/accessibility/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import React, { forwardRef } from "react";
import React, { type Ref, type JSX } from "react";

import { RovingTabIndexProvider } from "./RovingTabIndex";
import { getKeyBindingsManager } from "../KeyBindingsManager";
import { KeyBindingAction } from "./KeyboardShortcuts";

interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {}
interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {
ref?: Ref<HTMLDivElement>;
}

// This component implements the Toolbar design pattern from the WAI-ARIA Authoring Practices guidelines.
// https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar
// All buttons passed in children must use RovingTabIndex to set `onFocus`, `isActive`, `ref`
const Toolbar = forwardRef<HTMLDivElement, IProps>(({ children, ...props }, ref) => {
const Toolbar = ({ children, ref, ...props }: IProps): JSX.Element => {
const onKeyDown = (ev: React.KeyboardEvent): void => {
const target = ev.target as HTMLElement;
// Don't interfere with input default keydown behaviour
Expand Down Expand Up @@ -55,6 +57,6 @@ const Toolbar = forwardRef<HTMLDivElement, IProps>(({ children, ...props }, ref)
)}
</RovingTabIndexProvider>
);
});
};

export default Toolbar;
18 changes: 12 additions & 6 deletions src/accessibility/context_menu/ContextMenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,27 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import React, { forwardRef, type Ref } from "react";
import React, { type Ref, type JSX } from "react";

import AccessibleButton, { type ButtonProps } from "../../components/views/elements/AccessibleButton";

type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
label?: string;
// whether the context menu is currently open
isExpanded: boolean;
ref?: Ref<HTMLElementTagNameMap[T]>;
};

// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
{ label, isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
) {
export const ContextMenuButton = function <T extends keyof HTMLElementTagNameMap>({
label,
isExpanded,
children,
onClick,
onContextMenu,
ref,
...props
}: Props<T>): JSX.Element {
return (
<AccessibleButton
{...props}
Expand All @@ -36,4 +42,4 @@ export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElemen
{children}
</AccessibleButton>
);
});
};
16 changes: 10 additions & 6 deletions src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import React, { forwardRef, type Ref } from "react";
import React, { type JSX } from "react";

import AccessibleButton, { type ButtonProps } from "../../components/views/elements/AccessibleButton";

Expand All @@ -18,10 +18,14 @@ type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
};

// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
{ isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
) {
export const ContextMenuTooltipButton = function <T extends keyof HTMLElementTagNameMap>({
isExpanded,
children,
onClick,
onContextMenu,
ref,
...props
}: Props<T>): JSX.Element {
return (
<AccessibleButton
{...props}
Expand All @@ -35,4 +39,4 @@ export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTM
{children}
</AccessibleButton>
);
});
};
31 changes: 21 additions & 10 deletions src/autocomplete/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import React, { forwardRef } from "react";
import React, { type Ref, type JSX } from "react";
import classNames from "classnames";

/* These were earlier stateless functional components but had to be converted
Expand All @@ -16,14 +16,24 @@ presumably wrap them in a <div> before rendering but I think this is the better
*/

interface ITextualCompletionProps {
title?: string;
subtitle?: string;
description?: string;
className?: string;
"title"?: string;
"subtitle"?: string;
"description"?: string;
"className"?: string;
"aria-selected"?: boolean;
"ref"?: Ref<HTMLDivElement>;
}

export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props, ref) => {
const { title, subtitle, description, className, "aria-selected": ariaSelectedAttribute, ...restProps } = props;
export const TextualCompletion = (props: ITextualCompletionProps): JSX.Element => {
const {
title,
subtitle,
description,
className,
"aria-selected": ariaSelectedAttribute,
ref,
...restProps
} = props;
return (
<div
{...restProps}
Expand All @@ -37,20 +47,21 @@ export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props
<span className="mx_Autocomplete_Completion_description">{description}</span>
</div>
);
});
};

interface IPillCompletionProps extends ITextualCompletionProps {
children?: React.ReactNode;
}

export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref) => {
export const PillCompletion = (props: IPillCompletionProps): JSX.Element => {
const {
title,
subtitle,
description,
className,
children,
"aria-selected": ariaSelectedAttribute,
ref,
...restProps
} = props;
return (
Expand All @@ -67,4 +78,4 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
<span className="mx_Autocomplete_Completion_description">{description}</span>
</div>
);
});
};
2 changes: 1 addition & 1 deletion src/autocomplete/UserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export default class UserProvider extends AutocompleteProvider {
suffix: selection.beginning && range!.start === 0 ? ": " : " ",
href: makeUserPermalink(user.userId),
component: (
<PillCompletion title={displayName} description={description}>
<PillCompletion title={displayName} description={description ?? undefined}>
<MemberAvatar member={user} size="24px" />
</PillCompletion>
),
Expand Down
4 changes: 2 additions & 2 deletions src/components/structures/MatrixClientContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import React, { type PropsWithChildren, useEffect, useState } from "react";
import React, { type PropsWithChildren, useEffect, useState, type JSX } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
import { logger } from "matrix-js-sdk/src/logger";
Expand Down Expand Up @@ -85,7 +85,7 @@ interface Props {
* A React component which exposes a {@link MatrixClientContext} and a {@link LocalDeviceVerificationStateContext}
* to its children.
*/
export function MatrixClientContextProvider(props: PropsWithChildren<Props>): React.JSX.Element {
export function MatrixClientContextProvider(props: PropsWithChildren<Props>): JSX.Element {
const verificationState = useLocalVerificationState(props.client);
return (
<MatrixClientContext.Provider value={props.client}>
Expand Down
Loading
Loading