Convert ContextMenu to TypeScript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
4b27a67e33
commit
58718dab37
3 changed files with 148 additions and 114 deletions
|
@ -17,3 +17,4 @@ limitations under the License.
|
||||||
// Based on https://stackoverflow.com/a/53229857/3532235
|
// Based on https://stackoverflow.com/a/53229857/3532235
|
||||||
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never};
|
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never};
|
||||||
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
|
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
|
||||||
|
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
*
|
||||||
Copyright 2018 New Vector Ltd
|
* Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
Copyright 2019 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.
|
||||||
|
* /
|
||||||
|
*/
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
import React, {CSSProperties, useRef, useState} from "react";
|
||||||
you may not use this file except in compliance with the License.
|
import ReactDOM from "react-dom";
|
||||||
You may obtain a copy of the License at
|
import classNames from "classnames";
|
||||||
|
|
||||||
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, {useRef, useState} from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import {Key} from "../../Keyboard";
|
import {Key} from "../../Keyboard";
|
||||||
import * as sdk from "../../index";
|
import AccessibleButton, { IAccessibleButtonProps } from "../views/elements/AccessibleButton";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import {Writeable} from "../../@types/common";
|
||||||
|
|
||||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
|
@ -30,8 +30,8 @@ import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
|
|
||||||
const ContextualMenuContainerId = "mx_ContextualMenu_Container";
|
const ContextualMenuContainerId = "mx_ContextualMenu_Container";
|
||||||
|
|
||||||
function getOrCreateContainer() {
|
function getOrCreateContainer(): HTMLDivElement {
|
||||||
let container = document.getElementById(ContextualMenuContainerId);
|
let container = document.getElementById(ContextualMenuContainerId) as HTMLDivElement;
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
container = document.createElement("div");
|
container = document.createElement("div");
|
||||||
|
@ -43,50 +43,70 @@ function getOrCreateContainer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]);
|
const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]);
|
||||||
|
|
||||||
|
interface IPosition {
|
||||||
|
top?: number;
|
||||||
|
bottom?: number;
|
||||||
|
left?: number;
|
||||||
|
right?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChevronFace {
|
||||||
|
Top = "top",
|
||||||
|
Bottom = "bottom",
|
||||||
|
Left = "left",
|
||||||
|
Right = "right",
|
||||||
|
None = "none",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps extends IPosition {
|
||||||
|
menuWidth?: number;
|
||||||
|
menuHeight?: number;
|
||||||
|
|
||||||
|
chevronOffset?: number;
|
||||||
|
chevronFace?: ChevronFace;
|
||||||
|
|
||||||
|
menuPaddingTop?: number;
|
||||||
|
menuPaddingBottom?: number;
|
||||||
|
menuPaddingLeft?: number;
|
||||||
|
menuPaddingRight?: number;
|
||||||
|
|
||||||
|
zIndex?: number;
|
||||||
|
|
||||||
|
// If true, insert an invisible screen-sized element behind the menu that when clicked will close it.
|
||||||
|
hasBackground?: boolean;
|
||||||
|
// whether this context menu should be focus managed. If false it must handle itself
|
||||||
|
managed?: boolean;
|
||||||
|
|
||||||
|
// Function to be called on menu close
|
||||||
|
onFinished();
|
||||||
|
// on resize callback
|
||||||
|
windowResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
contextMenuElem: HTMLDivElement;
|
||||||
|
}
|
||||||
|
|
||||||
// Generic ContextMenu Portal wrapper
|
// Generic ContextMenu Portal wrapper
|
||||||
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
||||||
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
||||||
export class ContextMenu extends React.Component {
|
export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
static propTypes = {
|
private initialFocus: HTMLElement;
|
||||||
top: PropTypes.number,
|
|
||||||
bottom: PropTypes.number,
|
|
||||||
left: PropTypes.number,
|
|
||||||
right: PropTypes.number,
|
|
||||||
menuWidth: PropTypes.number,
|
|
||||||
menuHeight: PropTypes.number,
|
|
||||||
chevronOffset: PropTypes.number,
|
|
||||||
chevronFace: PropTypes.string, // top, bottom, left, right or none
|
|
||||||
// Function to be called on menu close
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
menuPaddingTop: PropTypes.number,
|
|
||||||
menuPaddingRight: PropTypes.number,
|
|
||||||
menuPaddingBottom: PropTypes.number,
|
|
||||||
menuPaddingLeft: PropTypes.number,
|
|
||||||
zIndex: PropTypes.number,
|
|
||||||
|
|
||||||
// If true, insert an invisible screen-sized element behind the
|
|
||||||
// menu that when clicked will close it.
|
|
||||||
hasBackground: PropTypes.bool,
|
|
||||||
|
|
||||||
// on resize callback
|
|
||||||
windowResize: PropTypes.func,
|
|
||||||
|
|
||||||
managed: PropTypes.bool, // whether this context menu should be focus managed. If false it must handle itself
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
hasBackground: true,
|
hasBackground: true,
|
||||||
managed: true,
|
managed: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor(props, context) {
|
||||||
super();
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
contextMenuElem: null,
|
contextMenuElem: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// persist what had focus when we got initialized so we can return it after
|
// persist what had focus when we got initialized so we can return it after
|
||||||
this.initialFocus = document.activeElement;
|
this.initialFocus = document.activeElement as HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -232,9 +252,8 @@ export class ContextMenu extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderMenu(hasBackground=this.props.hasBackground) {
|
renderMenu(hasBackground = this.props.hasBackground) {
|
||||||
const position = {};
|
const position: Partial<Writeable<DOMRect>> = {};
|
||||||
let chevronFace = null;
|
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
if (props.top) {
|
if (props.top) {
|
||||||
|
@ -243,23 +262,24 @@ export class ContextMenu extends React.Component {
|
||||||
position.bottom = props.bottom;
|
position.bottom = props.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let chevronFace: IProps["chevronFace"];
|
||||||
if (props.left) {
|
if (props.left) {
|
||||||
position.left = props.left;
|
position.left = props.left;
|
||||||
chevronFace = 'left';
|
chevronFace = ChevronFace.Left;
|
||||||
} else {
|
} else {
|
||||||
position.right = props.right;
|
position.right = props.right;
|
||||||
chevronFace = 'right';
|
chevronFace = ChevronFace.Right;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextMenuRect = this.state.contextMenuElem ? this.state.contextMenuElem.getBoundingClientRect() : null;
|
const contextMenuRect = this.state.contextMenuElem ? this.state.contextMenuElem.getBoundingClientRect() : null;
|
||||||
|
|
||||||
const chevronOffset = {};
|
const chevronOffset: CSSProperties = {};
|
||||||
if (props.chevronFace) {
|
if (props.chevronFace) {
|
||||||
chevronFace = props.chevronFace;
|
chevronFace = props.chevronFace;
|
||||||
}
|
}
|
||||||
const hasChevron = chevronFace && chevronFace !== "none";
|
const hasChevron = chevronFace && chevronFace !== ChevronFace.None;
|
||||||
|
|
||||||
if (chevronFace === 'top' || chevronFace === 'bottom') {
|
if (chevronFace === ChevronFace.Top || chevronFace === ChevronFace.Bottom) {
|
||||||
chevronOffset.left = props.chevronOffset;
|
chevronOffset.left = props.chevronOffset;
|
||||||
} else if (position.top !== undefined) {
|
} else if (position.top !== undefined) {
|
||||||
const target = position.top;
|
const target = position.top;
|
||||||
|
@ -289,13 +309,13 @@ export class ContextMenu extends React.Component {
|
||||||
'mx_ContextualMenu_right': !hasChevron && position.right,
|
'mx_ContextualMenu_right': !hasChevron && position.right,
|
||||||
'mx_ContextualMenu_top': !hasChevron && position.top,
|
'mx_ContextualMenu_top': !hasChevron && position.top,
|
||||||
'mx_ContextualMenu_bottom': !hasChevron && position.bottom,
|
'mx_ContextualMenu_bottom': !hasChevron && position.bottom,
|
||||||
'mx_ContextualMenu_withChevron_left': chevronFace === 'left',
|
'mx_ContextualMenu_withChevron_left': chevronFace === ChevronFace.Left,
|
||||||
'mx_ContextualMenu_withChevron_right': chevronFace === 'right',
|
'mx_ContextualMenu_withChevron_right': chevronFace === ChevronFace.Right,
|
||||||
'mx_ContextualMenu_withChevron_top': chevronFace === 'top',
|
'mx_ContextualMenu_withChevron_top': chevronFace === ChevronFace.Top,
|
||||||
'mx_ContextualMenu_withChevron_bottom': chevronFace === 'bottom',
|
'mx_ContextualMenu_withChevron_bottom': chevronFace === ChevronFace.Bottom,
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuStyle = {};
|
const menuStyle: CSSProperties = {};
|
||||||
if (props.menuWidth) {
|
if (props.menuWidth) {
|
||||||
menuStyle.width = props.menuWidth;
|
menuStyle.width = props.menuWidth;
|
||||||
}
|
}
|
||||||
|
@ -326,13 +346,28 @@ export class ContextMenu extends React.Component {
|
||||||
let background;
|
let background;
|
||||||
if (hasBackground) {
|
if (hasBackground) {
|
||||||
background = (
|
background = (
|
||||||
<div className="mx_ContextualMenu_background" style={wrapperStyle} onClick={props.onFinished} onContextMenu={this.onContextMenu} />
|
<div
|
||||||
|
className="mx_ContextualMenu_background"
|
||||||
|
style={wrapperStyle}
|
||||||
|
onClick={props.onFinished}
|
||||||
|
onContextMenu={this.onContextMenu}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown} onContextMenu={this.onContextMenuPreventBubbling}>
|
<div
|
||||||
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} role={this.props.managed ? "menu" : undefined}>
|
className="mx_ContextualMenu_wrapper"
|
||||||
|
style={{...position, ...wrapperStyle}}
|
||||||
|
onKeyDown={this._onKeyDown}
|
||||||
|
onContextMenu={this.onContextMenuPreventBubbling}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={menuClasses}
|
||||||
|
style={menuStyle}
|
||||||
|
ref={this.collectContextMenuRect}
|
||||||
|
role={this.props.managed ? "menu" : undefined}
|
||||||
|
>
|
||||||
{ chevron }
|
{ chevron }
|
||||||
{ props.children }
|
{ props.children }
|
||||||
</div>
|
</div>
|
||||||
|
@ -341,14 +376,19 @@ export class ContextMenu extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render(): React.ReactChild {
|
||||||
return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer());
|
return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IContextMenuButtonProps extends IAccessibleButtonProps {
|
||||||
|
label?: string;
|
||||||
|
// whether or not the context menu is currently open
|
||||||
|
isExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
||||||
export const ContextMenuButton = ({ label, isExpanded, children, onClick, onContextMenu, ...props }) => {
|
export const ContextMenuButton: React.FC<IContextMenuButtonProps> = ({ label, isExpanded, children, onClick, onContextMenu, ...props }) => {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -363,77 +403,70 @@ export const ContextMenuButton = ({ label, isExpanded, children, onClick, onCont
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ContextMenuButton.propTypes = {
|
|
||||||
...AccessibleButton.propTypes,
|
interface IMenuItemProps extends IAccessibleButtonProps {
|
||||||
label: PropTypes.string,
|
label?: string;
|
||||||
isExpanded: PropTypes.bool.isRequired, // whether or not the context menu is currently open
|
className?: string;
|
||||||
};
|
onClick();
|
||||||
|
}
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitem
|
// Semantic component for representing a role=menuitem
|
||||||
export const MenuItem = ({children, label, ...props}) => {
|
export const MenuItem: React.FC<IMenuItemProps> = ({children, label, ...props}) => {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={label}>
|
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={label}>
|
||||||
{ children }
|
{ children }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
MenuItem.propTypes = {
|
|
||||||
...AccessibleButton.propTypes,
|
interface IMenuGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
label: PropTypes.string, // optional
|
label: string;
|
||||||
className: PropTypes.string, // optional
|
className?: string;
|
||||||
onClick: PropTypes.func.isRequired,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Semantic component for representing a role=group for grouping menu radios/checkboxes
|
// Semantic component for representing a role=group for grouping menu radios/checkboxes
|
||||||
export const MenuGroup = ({children, label, ...props}) => {
|
export const MenuGroup: React.FC<IMenuGroupProps> = ({children, label, ...props}) => {
|
||||||
return <div {...props} role="group" aria-label={label}>
|
return <div {...props} role="group" aria-label={label}>
|
||||||
{ children }
|
{ children }
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
MenuGroup.propTypes = {
|
|
||||||
label: PropTypes.string.isRequired,
|
interface IMenuItemCheckboxProps extends IAccessibleButtonProps {
|
||||||
className: PropTypes.string, // optional
|
label?: string;
|
||||||
};
|
active: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitemcheckbox
|
// Semantic component for representing a role=menuitemcheckbox
|
||||||
export const MenuItemCheckbox = ({children, label, active=false, disabled=false, ...props}) => {
|
export const MenuItemCheckbox: React.FC<IMenuItemCheckboxProps> = ({children, label, active = false, disabled = false, ...props}) => {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} role="menuitemcheckbox" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
|
<AccessibleButton {...props} role="menuitemcheckbox" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
|
||||||
{ children }
|
{ children }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
MenuItemCheckbox.propTypes = {
|
|
||||||
...AccessibleButton.propTypes,
|
interface IMenuItemRadioProps extends IAccessibleButtonProps {
|
||||||
label: PropTypes.string, // optional
|
label?: string;
|
||||||
active: PropTypes.bool.isRequired,
|
active: boolean;
|
||||||
disabled: PropTypes.bool, // optional
|
disabled?: boolean;
|
||||||
className: PropTypes.string, // optional
|
className?: string;
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick();
|
||||||
};
|
}
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitemradio
|
// Semantic component for representing a role=menuitemradio
|
||||||
export const MenuItemRadio = ({children, label, active=false, disabled=false, ...props}) => {
|
export const MenuItemRadio: React.FC<IMenuItemRadioProps> = ({children, label, active = false, disabled = false, ...props}) => {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} role="menuitemradio" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
|
<AccessibleButton {...props} role="menuitemradio" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
|
||||||
{ children }
|
{ children }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
MenuItemRadio.propTypes = {
|
|
||||||
...AccessibleButton.propTypes,
|
|
||||||
label: PropTypes.string, // optional
|
|
||||||
active: PropTypes.bool.isRequired,
|
|
||||||
disabled: PropTypes.bool, // optional
|
|
||||||
className: PropTypes.string, // optional
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Placement method for <ContextMenu /> to position context menu to right of elementRect with chevronOffset
|
// Placement method for <ContextMenu /> to position context menu to right of elementRect with chevronOffset
|
||||||
export const toRightOf = (elementRect, chevronOffset=12) => {
|
export const toRightOf = (elementRect: DOMRect, chevronOffset = 12) => {
|
||||||
const left = elementRect.right + window.pageXOffset + 3;
|
const left = elementRect.right + window.pageXOffset + 3;
|
||||||
let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
||||||
top -= chevronOffset + 8; // where 8 is half the height of the chevron
|
top -= chevronOffset + 8; // where 8 is half the height of the chevron
|
||||||
|
@ -441,8 +474,8 @@ export const toRightOf = (elementRect, chevronOffset=12) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect
|
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect
|
||||||
export const aboveLeftOf = (elementRect, chevronFace="none") => {
|
export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None) => {
|
||||||
const menuOptions = { chevronFace };
|
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
|
||||||
|
|
||||||
const buttonRight = elementRect.right + window.pageXOffset;
|
const buttonRight = elementRect.right + window.pageXOffset;
|
||||||
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
|
@ -42,7 +42,7 @@ interface IProps extends React.InputHTMLAttributes<Element> {
|
||||||
onClick?(e?: ButtonEvent): void;
|
onClick?(e?: ButtonEvent): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> {
|
export interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> {
|
||||||
ref?: React.Ref<Element>;
|
ref?: React.Ref<Element>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue