Move all the ContextMenu semantic helper (ARIA) components out to separate modules
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
afac330143
commit
8c2286a044
8 changed files with 346 additions and 184 deletions
51
src/accessibility/context_menu/ContextMenuButton.tsx
Normal file
51
src/accessibility/context_menu/ContextMenuButton.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AccessibleButton, {IProps as IAccessibleButtonProps} from "../../components/views/elements/AccessibleButton";
|
||||||
|
|
||||||
|
interface IProps 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 />
|
||||||
|
export const ContextMenuButton: React.FC<IProps> = ({
|
||||||
|
label,
|
||||||
|
isExpanded,
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
onContextMenu,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<AccessibleButton
|
||||||
|
{...props}
|
||||||
|
onClick={onClick}
|
||||||
|
onContextMenu={onContextMenu || onClick}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
|
aria-haspopup={true}
|
||||||
|
aria-expanded={isExpanded}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
};
|
31
src/accessibility/context_menu/MenuGroup.tsx
Normal file
31
src/accessibility/context_menu/MenuGroup.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface IProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
label: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic component for representing a role=group for grouping menu radios/checkboxes
|
||||||
|
export const MenuGroup: React.FC<IProps> = ({children, label, ...props}) => {
|
||||||
|
return <div {...props} role="group" aria-label={label}>
|
||||||
|
{ children }
|
||||||
|
</div>;
|
||||||
|
};
|
36
src/accessibility/context_menu/MenuItem.tsx
Normal file
36
src/accessibility/context_menu/MenuItem.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AccessibleButton, {ButtonEvent, IProps as IAccessibleButtonProps} from "../../components/views/elements/AccessibleButton";
|
||||||
|
|
||||||
|
interface IProps extends IAccessibleButtonProps {
|
||||||
|
label?: string;
|
||||||
|
className?: string;
|
||||||
|
onClick(ev: ButtonEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic component for representing a role=menuitem
|
||||||
|
export const MenuItem: React.FC<IProps> = ({children, label, ...props}) => {
|
||||||
|
return (
|
||||||
|
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={label}>
|
||||||
|
{ children }
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
};
|
45
src/accessibility/context_menu/MenuItemCheckbox.tsx
Normal file
45
src/accessibility/context_menu/MenuItemCheckbox.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AccessibleButton, {ButtonEvent, IProps as IAccessibleButtonProps} from "../../components/views/elements/AccessibleButton";
|
||||||
|
|
||||||
|
interface IProps extends IAccessibleButtonProps {
|
||||||
|
label?: string;
|
||||||
|
active: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onClick(ev: ButtonEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic component for representing a role=menuitemcheckbox
|
||||||
|
export const MenuItemCheckbox: React.FC<IProps> = ({children, label, active, disabled, ...props}) => {
|
||||||
|
return (
|
||||||
|
<AccessibleButton
|
||||||
|
{...props}
|
||||||
|
role="menuitemcheckbox"
|
||||||
|
aria-checked={active}
|
||||||
|
aria-disabled={disabled}
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-label={label}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
};
|
45
src/accessibility/context_menu/MenuItemRadio.tsx
Normal file
45
src/accessibility/context_menu/MenuItemRadio.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AccessibleButton, {ButtonEvent, IProps as IAccessibleButtonProps} from "../../components/views/elements/AccessibleButton";
|
||||||
|
|
||||||
|
interface IProps extends IAccessibleButtonProps {
|
||||||
|
label?: string;
|
||||||
|
active: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onClick(ev: ButtonEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic component for representing a role=menuitemradio
|
||||||
|
export const MenuItemRadio: React.FC<IProps> = ({children, label, active, disabled, ...props}) => {
|
||||||
|
return (
|
||||||
|
<AccessibleButton
|
||||||
|
{...props}
|
||||||
|
role="menuitemradio"
|
||||||
|
aria-checked={active}
|
||||||
|
aria-disabled={disabled}
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-label={label}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
};
|
64
src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
Normal file
64
src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import {Key} from "../../Keyboard";
|
||||||
|
import StyledCheckbox from "../../components/views/elements/StyledCheckbox";
|
||||||
|
|
||||||
|
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
|
||||||
|
label?: string;
|
||||||
|
onChange();
|
||||||
|
onClose(): void; // gets called after onChange on Key.ENTER
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic component for representing a styled role=menuitemcheckbox
|
||||||
|
export const StyledMenuItemCheckbox: React.FC<IProps> = ({children, label, onChange, onClose, ...props}) => {
|
||||||
|
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === Key.ENTER || e.key === Key.SPACE) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onChange();
|
||||||
|
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||||
|
if (e.key === Key.ENTER) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onKeyUp = (e: React.KeyboardEvent) => {
|
||||||
|
// prevent the input default handler as we handle it on keydown to match
|
||||||
|
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
|
||||||
|
if (e.key === Key.SPACE || e.key === Key.ENTER) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<StyledCheckbox
|
||||||
|
{...props}
|
||||||
|
role="menuitemcheckbox"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-label={label}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onKeyUp={onKeyUp}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</StyledCheckbox>
|
||||||
|
);
|
||||||
|
};
|
65
src/accessibility/context_menu/StyledMenuItemRadio.tsx
Normal file
65
src/accessibility/context_menu/StyledMenuItemRadio.tsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import {Key} from "../../Keyboard";
|
||||||
|
import StyledRadioButton from "../../components/views/elements/StyledRadioButton";
|
||||||
|
|
||||||
|
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
|
||||||
|
label?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
onChange(): void;
|
||||||
|
onClose(): void; // gets called after onChange on Key.ENTER
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic component for representing a styled role=menuitemradio
|
||||||
|
export const StyledMenuItemRadio: React.FC<IProps> = ({children, label, onChange, onClose, ...props}) => {
|
||||||
|
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === Key.ENTER || e.key === Key.SPACE) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onChange();
|
||||||
|
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||||
|
if (e.key === Key.ENTER) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onKeyUp = (e: React.KeyboardEvent) => {
|
||||||
|
// prevent the input default handler as we handle it on keydown to match
|
||||||
|
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
|
||||||
|
if (e.key === Key.SPACE || e.key === Key.ENTER) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<StyledRadioButton
|
||||||
|
{...props}
|
||||||
|
role="menuitemradio"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-label={label}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onKeyUp={onKeyUp}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</StyledRadioButton>
|
||||||
|
);
|
||||||
|
};
|
|
@ -21,10 +21,7 @@ import ReactDOM from "react-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import {Key} from "../../Keyboard";
|
import {Key} from "../../Keyboard";
|
||||||
import AccessibleButton, { IProps as IAccessibleButtonProps, ButtonEvent } from "../views/elements/AccessibleButton";
|
|
||||||
import {Writeable} from "../../@types/common";
|
import {Writeable} from "../../@types/common";
|
||||||
import StyledCheckbox from "../views/elements/StyledCheckbox";
|
|
||||||
import StyledRadioButton from "../views/elements/StyledRadioButton";
|
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -390,187 +387,6 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 />
|
|
||||||
export const ContextMenuButton: React.FC<IContextMenuButtonProps> = ({ label, isExpanded, children, onClick, onContextMenu, ...props }) => {
|
|
||||||
return (
|
|
||||||
<AccessibleButton
|
|
||||||
{...props}
|
|
||||||
onClick={onClick}
|
|
||||||
onContextMenu={onContextMenu || onClick}
|
|
||||||
title={label}
|
|
||||||
aria-label={label}
|
|
||||||
aria-haspopup={true}
|
|
||||||
aria-expanded={isExpanded}
|
|
||||||
>
|
|
||||||
{ children }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IMenuItemProps extends IAccessibleButtonProps {
|
|
||||||
label?: string;
|
|
||||||
className?: string;
|
|
||||||
onClick(ev: ButtonEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitem
|
|
||||||
export const MenuItem: React.FC<IMenuItemProps> = ({children, label, ...props}) => {
|
|
||||||
return (
|
|
||||||
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={label}>
|
|
||||||
{ children }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IMenuGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
||||||
label: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semantic component for representing a role=group for grouping menu radios/checkboxes
|
|
||||||
export const MenuGroup: React.FC<IMenuGroupProps> = ({children, label, ...props}) => {
|
|
||||||
return <div {...props} role="group" aria-label={label}>
|
|
||||||
{ children }
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IMenuItemCheckboxProps extends IAccessibleButtonProps {
|
|
||||||
label?: string;
|
|
||||||
active: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
className?: string;
|
|
||||||
onClick(ev: ButtonEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitemcheckbox
|
|
||||||
export const MenuItemCheckbox: React.FC<IMenuItemCheckboxProps> = ({children, label, active = false, disabled = false, ...props}) => {
|
|
||||||
return (
|
|
||||||
<AccessibleButton {...props} role="menuitemcheckbox" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
|
|
||||||
{ children }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IStyledMenuItemCheckboxProps extends IAccessibleButtonProps {
|
|
||||||
label?: string;
|
|
||||||
active: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
className?: string;
|
|
||||||
onChange();
|
|
||||||
onClose(): void; // gets called after onChange on Key.ENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semantic component for representing a styled role=menuitemcheckbox
|
|
||||||
export const StyledMenuItemCheckbox: React.FC<IStyledMenuItemCheckboxProps> = ({children, label, onChange, onClose, checked, disabled=false, ...props}) => {
|
|
||||||
const onKeyDown = (e) => {
|
|
||||||
if (e.key === Key.ENTER || e.key === Key.SPACE) {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
onChange();
|
|
||||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
|
||||||
if (e.key === Key.ENTER) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const onKeyUp = (e) => {
|
|
||||||
// prevent the input default handler as we handle it on keydown to match
|
|
||||||
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
|
|
||||||
if (e.key === Key.SPACE || e.key === Key.ENTER) {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<StyledCheckbox
|
|
||||||
{...props}
|
|
||||||
role="menuitemcheckbox"
|
|
||||||
aria-checked={checked}
|
|
||||||
checked={checked}
|
|
||||||
aria-disabled={disabled}
|
|
||||||
tabIndex={-1}
|
|
||||||
aria-label={label}
|
|
||||||
onChange={onChange}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
onKeyUp={onKeyUp}
|
|
||||||
>
|
|
||||||
{ children }
|
|
||||||
</StyledCheckbox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IMenuItemRadioProps extends IAccessibleButtonProps {
|
|
||||||
label?: string;
|
|
||||||
active: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
className?: string;
|
|
||||||
onClick(ev: ButtonEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitemradio
|
|
||||||
export const MenuItemRadio: React.FC<IMenuItemRadioProps> = ({children, label, active = false, disabled = false, ...props}) => {
|
|
||||||
return (
|
|
||||||
<AccessibleButton {...props} role="menuitemradio" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
|
|
||||||
{ children }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
interface IStyledMenuItemRadioProps extends IAccessibleButtonProps {
|
|
||||||
label?: string;
|
|
||||||
active: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
className?: string;
|
|
||||||
onChange();
|
|
||||||
onClose(): void; // gets called after onChange on Key.ENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
// Semantic component for representing a styled role=menuitemradio
|
|
||||||
export const StyledMenuItemRadio: React.FC<IStyledMenuItemRadioProps> = ({children, label, onChange, onClose, checked=false, disabled=false, ...props}) => {
|
|
||||||
const onKeyDown = (e) => {
|
|
||||||
if (e.key === Key.ENTER || e.key === Key.SPACE) {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
onChange();
|
|
||||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
|
||||||
if (e.key === Key.ENTER) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const onKeyUp = (e) => {
|
|
||||||
// prevent the input default handler as we handle it on keydown to match
|
|
||||||
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
|
|
||||||
if (e.key === Key.SPACE || e.key === Key.ENTER) {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<StyledRadioButton
|
|
||||||
{...props}
|
|
||||||
role="menuitemradio"
|
|
||||||
aria-checked={checked}
|
|
||||||
checked={checked}
|
|
||||||
aria-disabled={disabled}
|
|
||||||
tabIndex={-1}
|
|
||||||
aria-label={label}
|
|
||||||
onChange={onChange}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
onKeyUp={onKeyUp}
|
|
||||||
>
|
|
||||||
{ children }
|
|
||||||
</StyledRadioButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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: DOMRect, chevronOffset = 12) => {
|
export const toRightOf = (elementRect: DOMRect, chevronOffset = 12) => {
|
||||||
const left = elementRect.right + window.pageXOffset + 3;
|
const left = elementRect.right + window.pageXOffset + 3;
|
||||||
|
@ -639,3 +455,12 @@ export function createMenu(ElementClass, props) {
|
||||||
|
|
||||||
return {close: onFinished};
|
return {close: onFinished};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// re-export the semantic helper components for simplicity
|
||||||
|
export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton";
|
||||||
|
export {MenuGroup} from "../../accessibility/context_menu/MenuGroup";
|
||||||
|
export {MenuItem} from "../../accessibility/context_menu/MenuItem";
|
||||||
|
export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox";
|
||||||
|
export {MenuItemRadio} from "../../accessibility/context_menu/MenuItemRadio";
|
||||||
|
export {StyledMenuItemCheckbox} from "../../accessibility/context_menu/StyledMenuItemCheckbox";
|
||||||
|
export {StyledMenuItemRadio} from "../../accessibility/context_menu/StyledMenuItemRadio";
|
||||||
|
|
Loading…
Reference in a new issue