Skip to content

Commit

Permalink
Merge branch 'develop' into dbkr/initialcryptosetupstore
Browse files Browse the repository at this point in the history
  • Loading branch information
dbkr authored Dec 9, 2024
2 parents ce19437 + 0130443 commit c07883e
Show file tree
Hide file tree
Showing 24 changed files with 83 additions and 111 deletions.
13 changes: 6 additions & 7 deletions src/accessibility/context_menu/ContextMenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { ComponentProps, forwardRef, Ref } from "react";
import React, { forwardRef, Ref } from "react";

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

type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
label?: string;
// whether the context menu is currently open
isExpanded: boolean;
};

// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
{ label, isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElement>,
export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
{ label, isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
) {
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-label={label}
Expand Down
13 changes: 6 additions & 7 deletions src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { ComponentProps, forwardRef, Ref } from "react";
import React, { forwardRef, Ref } from "react";

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

type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
// whether the context menu is currently open
isExpanded: boolean;
};

// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
{ isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElement>,
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>(
{ isExpanded, children, onClick, onContextMenu, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
) {
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-haspopup={true}
Expand Down
22 changes: 8 additions & 14 deletions src/accessibility/roving/RovingAccessibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,33 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { ComponentProps } from "react";
import React, { RefObject } from "react";

import AccessibleButton from "../../components/views/elements/AccessibleButton";
import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton";
import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";

type Props<T extends keyof JSX.IntrinsicElements> = Omit<
ComponentProps<typeof AccessibleButton<T>>,
"inputRef" | "tabIndex"
> & {
inputRef?: Ref;
type Props<T extends keyof HTMLElementTagNameMap> = Omit<ButtonProps<T>, "tabIndex"> & {
inputRef?: RefObject<HTMLElementTagNameMap[T]>;
focusOnMouseOver?: boolean;
};

// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({
export const RovingAccessibleButton = <T extends keyof HTMLElementTagNameMap>({
inputRef,
onFocus,
onMouseOver,
focusOnMouseOver,
element,
...props
}: Props<T>): JSX.Element => {
const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
const [onFocusInternal, isActive, ref] = useRovingTabIndex<HTMLElementTagNameMap[T]>(inputRef);
return (
<AccessibleButton
{...props}
element={element as keyof JSX.IntrinsicElements}
onFocus={(event: React.FocusEvent) => {
onFocus={(event: React.FocusEvent<never, never>) => {
onFocusInternal();
onFocus?.(event);
}}
onMouseOver={(event: React.MouseEvent) => {
onMouseOver={(event: React.MouseEvent<never, never>) => {
if (focusOnMouseOver) onFocusInternal();
onMouseOver?.(event);
}}
Expand Down
7 changes: 1 addition & 6 deletions src/components/structures/auth/SoftLogout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
value={this.state.password}
disabled={this.state.busy}
/>
<AccessibleButton
onClick={this.onPasswordLogin}
kind="primary"
type="submit"
disabled={this.state.busy}
>
<AccessibleButton onClick={this.onPasswordLogin} kind="primary" disabled={this.state.busy}>
{_t("action|sign_in")}
</AccessibleButton>
<AccessibleButton onClick={this.onForgotPassword} kind="link">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn

export class FallbackAuthEntry<T = {}> extends React.Component<IAuthEntryProps & T> {
protected popupWindow: Window | null;
protected fallbackButton = createRef<HTMLButtonElement>();
protected fallbackButton = createRef<HTMLDivElement>();

public constructor(props: IAuthEntryProps & T) {
super(props);
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/dialogs/devtools/SettingExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
<code>{i}</code>
</AccessibleButton>
<AccessibleButton
alt={_t("devtools|edit_setting")}
title={_t("devtools|edit_setting")}
onClick={() => onEdit(i)}
className="mx_DevTools_SettingsExplorer_edit"
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/dialogs/spotlight/SpotlightDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
<span>{filterToLabel(filter)}</span>
<AccessibleButton
tabIndex={-1}
alt={_t("spotlight_dialog|remove_filter", {
title={_t("spotlight_dialog|remove_filter", {
filter: filterToLabel(filter),
})}
className="mx_SpotlightDialog_filter--close"
Expand Down
7 changes: 3 additions & 4 deletions src/components/views/dialogs/spotlight/TooltipOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
import { Ref } from "../../../../accessibility/roving/types";

type TooltipOptionProps<T extends keyof JSX.IntrinsicElements> = ButtonProps<T> & {
type TooltipOptionProps<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & {
className?: string;
endAdornment?: ReactNode;
inputRef?: Ref;
};

export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
export const TooltipOption = <T extends keyof HTMLElementTagNameMap>({
inputRef,
className,
element,
...props
}: TooltipOptionProps<T>): JSX.Element => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
Expand All @@ -34,7 +34,6 @@ export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
tabIndex={-1}
aria-selected={isActive}
role="option"
element={element as keyof JSX.IntrinsicElements}
/>
);
};
2 changes: 1 addition & 1 deletion src/components/views/directory/NetworkDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
adornment: (
<AccessibleButton
className="mx_NetworkDropdown_removeServer"
alt={_t("spotlight|public_rooms|network_dropdown_remove_server_adornment", { roomServer })}
title={_t("spotlight|public_rooms|network_dropdown_remove_server_adornment", { roomServer })}
onClick={() => setUserDefinedServers(without(userDefinedServers, roomServer))}
/>
),
Expand Down
63 changes: 34 additions & 29 deletions src/components/views/elements/AccessibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

import React, { ComponentProps, forwardRef, FunctionComponent, HTMLAttributes, InputHTMLAttributes, Ref } from "react";
import React, {
ComponentProps,
ComponentPropsWithoutRef,
forwardRef,
FunctionComponent,
ReactElement,
KeyboardEvent,
Ref,
} from "react";
import classnames from "classnames";
import { Tooltip } from "@vector-im/compound-web";

Expand Down Expand Up @@ -38,20 +46,8 @@ export type AccessibleButtonKind =
| "icon_primary"
| "icon_primary_outline";

/**
* This type construct allows us to specifically pass those props down to the element we’re creating that the element
* actually supports.
*
* e.g., if element is set to "a", we’ll support href and target, if it’s set to "input", we support type.
*
* To remain compatible with existing code, we’ll continue to support InputHTMLAttributes<Element>
*/
type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> =
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<
Omit<JSX.IntrinsicElements[T], "ref" | "onClick" | "onMouseDown" | "onKeyUp" | "onKeyDown">
> &
Omit<InputHTMLAttributes<Element>, "onClick">;
type ElementType = keyof HTMLElementTagNameMap;
const defaultElement = "div";

type TooltipProps = ComponentProps<typeof Tooltip>;

Expand All @@ -60,7 +56,7 @@ type TooltipProps = ComponentProps<typeof Tooltip>;
*
* Extends props accepted by the underlying element specified using the `element` prop.
*/
type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> & {
type Props<T extends ElementType = "div"> = {
/**
* The base element type. "div" by default.
*/
Expand Down Expand Up @@ -105,14 +101,12 @@ type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> &
disableTooltip?: TooltipProps["disabled"];
};

export type ButtonProps<T extends keyof JSX.IntrinsicElements> = Props<T>;
export type ButtonProps<T extends ElementType> = Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>;

/**
* Type of the props passed to the element that is rendered by AccessibleButton.
*/
interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
ref?: React.Ref<Element>;
}
type RenderedElementProps<T extends ElementType> = React.InputHTMLAttributes<Element> & RefProp<T>;

/**
* AccessibleButton is a generic wrapper for any element that should be treated
Expand All @@ -124,9 +118,9 @@ interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
* @param {Object} props react element properties
* @returns {Object} rendered react
*/
const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
const AccessibleButton = forwardRef(function <T extends ElementType = typeof defaultElement>(
{
element = "div" as T,
element,
onClick,
children,
kind,
Expand All @@ -141,10 +135,10 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
onTooltipOpenChange,
disableTooltip,
...restProps
}: Props<T>,
ref: Ref<HTMLElement>,
}: ButtonProps<T>,
ref: Ref<HTMLElementTagNameMap[T]>,
): JSX.Element {
const newProps: RenderedElementProps = restProps;
const newProps = restProps as RenderedElementProps<T>;
newProps["aria-label"] = newProps["aria-label"] ?? title;
if (disabled) {
newProps["aria-disabled"] = true;
Expand All @@ -162,7 +156,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
// And divs which we report as role button to assistive technologies.
// Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here
newProps.onKeyDown = (e) => {
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);

switch (action) {
Expand All @@ -178,7 +172,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
onKeyDown?.(e);
}
};
newProps.onKeyUp = (e) => {
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);

switch (action) {
Expand Down Expand Up @@ -207,7 +201,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
});

// React.createElement expects InputHTMLAttributes
const button = React.createElement(element, newProps, children);
const button = React.createElement(element ?? defaultElement, newProps, children);

if (title) {
return (
Expand All @@ -233,4 +227,15 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
};
(AccessibleButton as FunctionComponent).displayName = "AccessibleButton";

export default AccessibleButton;
interface RefProp<T extends ElementType> {
ref?: Ref<HTMLElementTagNameMap[T]>;
}

interface ButtonComponent {
// With the explicit `element` prop
<C extends ElementType>(props: { element?: C } & ButtonProps<C> & RefProp<C>): ReactElement;
// Without the explicit `element` prop
(props: ButtonProps<"div"> & RefProp<"div">): ReactElement;
}

export default AccessibleButton as ButtonComponent;
7 changes: 1 addition & 6 deletions src/components/views/elements/EditableItemList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,7 @@ export default class EditableItemList<P = {}> extends React.PureComponent<IProps
onChange={this.onNewItemChanged}
list={this.props.suggestionsListId}
/>
<AccessibleButton
onClick={this.onItemAdded}
kind="primary"
type="submit"
disabled={!this.props.newItem}
>
<AccessibleButton onClick={this.onItemAdded} kind="primary" disabled={!this.props.newItem}>
{_t("action|add")}
</AccessibleButton>
</form>
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/emojipicker/Emoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Emoji extends React.PureComponent<IProps> {
return (
<RovingAccessibleButton
id={this.props.id}
onClick={(ev) => onClick(ev, emoji)}
onClick={(ev: ButtonEvent) => onClick(ev, emoji)}
onMouseEnter={() => onMouseEnter(emoji)}
onMouseLeave={() => onMouseLeave(emoji)}
className="mx_EmojiPicker_item_wrapper"
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MPollEndBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...pro
const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);

if (!pollStartEvent) {
const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent, cli);
const pollEndFallbackMessage = M_TEXT.findIn<string>(mxEvent.getContent()) || textForEvent(mxEvent, cli);
return (
<>
<PollIcon className="mx_MPollEndBody_icon" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MessageActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
<RovingAccessibleButton
className="mx_MessageActionBar_iconButton"
title={isPinned ? _t("action|unpin") : _t("action|pin")}
onClick={(e) => this.onPinClick(e, isPinned)}
onClick={(e: ButtonEvent) => this.onPinClick(e, isPinned)}
onContextMenu={(e: ButtonEvent) => this.onPinClick(e, isPinned)}
key="pin"
placement="left"
Expand Down
1 change: 0 additions & 1 deletion src/components/views/settings/SetIdServer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,6 @@ export default class SetIdServer extends React.Component<IProps, IState> {
forceValidity={this.state.error ? false : undefined}
/>
<AccessibleButton
type="submit"
kind="primary_sm"
onClick={this.checkIdServer}
disabled={!this.idServerChangeEnabled()}
Expand Down
Loading

0 comments on commit c07883e

Please sign in to comment.