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

Commit

Permalink
Improve AccessibleButton & related types (#12075)
Browse files Browse the repository at this point in the history
* Fix wrong type enum usage

Signed-off-by: Michael Telatynski <[email protected]>

* Use improved type definition for forwardRef which enables Generic props

Signed-off-by: Michael Telatynski <[email protected]>

* Improve AccessibleButton & related Props types

Signed-off-by: Michael Telatynski <[email protected]>

* Remove useless comment

Signed-off-by: Michael Telatynski <[email protected]>

---------

Signed-off-by: Michael Telatynski <[email protected]>
  • Loading branch information
t3chguy authored Dec 20, 2023
1 parent e26d3e9 commit af31965
Show file tree
Hide file tree
Showing 17 changed files with 109 additions and 79 deletions.
24 changes: 24 additions & 0 deletions src/@types/react.d.ts
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 6 additions & 6 deletions src/accessibility/context_menu/ContextMenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
12 changes: 6 additions & 6 deletions src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
13 changes: 8 additions & 5 deletions src/accessibility/roving/RovingAccessibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions src/accessibility/roving/RovingAccessibleTooltipButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions src/components/views/audio_messages/PlayPauseButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
7 changes: 5 additions & 2 deletions src/components/views/elements/AccessibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}

Expand All @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/components/views/elements/AccessibleTooltipButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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,
Expand All @@ -69,7 +69,7 @@ function AccessibleTooltipButton({
onHideTooltip,
tooltipClassName,
...props
}: Props): JSX.Element {
}: Props<T>): JSX.Element {
const [hover, setHover] = useState(false);

useEffect(() => {
Expand Down
10 changes: 5 additions & 5 deletions src/components/views/elements/LearnMore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions src/components/views/messages/CallEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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:
Expand Down
6 changes: 1 addition & 5 deletions src/components/views/rooms/ExtraTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
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, { 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";

Expand Down Expand Up @@ -120,6 +120,6 @@ const WysiwygAutocomplete = forwardRef(
},
);

WysiwygAutocomplete.displayName = "WysiwygAutocomplete";
(WysiwygAutocomplete as FunctionComponent).displayName = "WysiwygAutocomplete";

export { WysiwygAutocomplete };
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 7 additions & 4 deletions src/components/views/spaces/SpaceTreeLevel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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;
Expand Down
Loading

0 comments on commit af31965

Please sign in to comment.