Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyboard a11y improvements #1274

Merged
merged 13 commits into from
Nov 15, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const MesaTooltip = ({
enterDelay={showDelay}
className={(className ?? '') + (corner ? ` ${corner}` : '')}
style={finalStyle}
tabIndex={0}
>
{children}
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,12 @@ const PopoverButton = forwardRef<PopoverButtonHandle, PopoverButtonProps>(
);

const onCloseHandler = useCallback(() => {
setTimeout(() => {
anchorEl?.focus(); // return focus to button
});
setAnchorEl(null);
onClose && onClose();
}, [onClose]);
}, [anchorEl, onClose]);

// Expose the `close()` method to external components via ref
useImperativeHandle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default function SwissArmyButton({
styleSpec[styleState].color ?? 'transparent'
} !important`,
borderRadius: styleSpec[styleState].border?.radius ?? 5,
outlineStyle: styleSpec[styleState].border?.style ?? 'none',
outlineStyle: styleSpec[styleState].border?.style,
outlineColor: styleSpec[styleState].border?.color,
outlineWidth: styleSpec[styleState].border?.width,
outlineOffset: styleSpec[styleState].border?.width
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,7 @@ function CheckboxTree<T>(props: CheckboxTreeProps<T>) {
},
'.arrow-container': {
height: '1em',
'outline-offset': '-1px',
},
'.arrow-icon': {
fill: '#aaa',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,33 +161,28 @@ export default function CheckboxTreeNode<T>({
{isLeafNode ? null : isActiveSearch ? (
// this retains the space of the expansion toggle icons for easier formatting
<div className="active-search-buffer"></div>
) : isExpanded ? (
<div className="arrow-container">
<ArrowDown
className="arrow-icon"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
toggleExpansion(node);
}}
onKeyDown={(e) =>
e.key === 'Enter' ? toggleExpansion(node) : null
}
/>
</div>
) : (
<div className="arrow-container">
<ArrowRight
className="arrow-icon"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
<div
className="arrow-container"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
toggleExpansion(node);
}}
onKeyDown={(e) => {
const toggleKeys = isExpanded
? ['Enter', 'ArrowLeft']
: ['Enter', 'ArrowRight'];
if (toggleKeys.includes(e.key)) {
toggleExpansion(node);
}}
onKeyDown={(e) =>
e.key === 'Enter' ? toggleExpansion(node) : null
}
/>
}}
>
{isExpanded ? (
<ArrowDown className="arrow-icon" />
) : (
<ArrowRight className="arrow-icon" />
)}
</div>
)}
{!isSelectable || (!isMultiPick && !isLeafNode) ? (
Expand Down
16 changes: 14 additions & 2 deletions packages/libs/coreui/src/components/theming/UIThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { ThemeProvider } from '@emotion/react';
import { css, Global, ThemeProvider } from '@emotion/react';
import { useCoreUIFonts } from '../../hooks';

import { UITheme } from './types';
Expand All @@ -14,5 +14,17 @@ export default function UIThemeProvider({
children,
}: UIThemeProviderProps) {
useCoreUIFonts();
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
return (
<ThemeProvider theme={theme}>
<Global
styles={css`
*:focus {
outline: 2px solid
${theme.palette.primary.hue[theme.palette.primary.level]};
}
`}
/>
{children}
</ThemeProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
padding: 5px;
background: none;
border: none;
outline-offset: -5px;
}

.RemoveColumnButton {
Expand Down
3 changes: 2 additions & 1 deletion packages/libs/wdk-client/src/Views/Strategy/StepBoxes.css
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,8 @@ button.StepBoxes--EditButton:hover {
background: yellow;
}

.StrategyPanel--Panel:hover button.StepBoxes--EditButton {
.StrategyPanel--Panel:hover button.StepBoxes--EditButton,
.StrategyPanel--Panel:focus-within button.StepBoxes--EditButton {
display: inline;
}

Expand Down
56 changes: 11 additions & 45 deletions packages/libs/web-common/src/App/UserMenu/UserMenu.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';

import './UserMenu.scss';

Expand All @@ -7,87 +8,56 @@ import { IconAlt as Icon } from '@veupathdb/wdk-client/lib/Components';
class UserMenu extends React.Component {
constructor(props) {
super(props);
this.state = { isEntered: false, isHovered: false };
this.renderMenu = this.renderMenu.bind(this);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
}

onMouseEnter(event) {
this.setState({ isEntered: true, isHovered: true });
}

onMouseLeave(event) {
this.setState({ isEntered: false });

setTimeout(() => {
if (!this.state.isEntered) {
this.setState({ isHovered: false });
}
}, 500);
}

renderMenu() {
const { user, actions, webAppUrl } = this.props;
const { showLoginForm } = actions;
const { isHovered } = this.state;
const { properties } = user.properties ? user : { properties: null };
const { firstName, lastName } = properties
? properties
: { firstName: '', lastName: '' };
const { user } = this.props;
const items = user.isGuest
? [
{
icon: 'sign-in',
text: 'Login',
onClick: () => actions.showLoginForm(window.location.href),
route: '/user/login',
},
{
icon: 'user-plus',
text: 'Register',
href: webAppUrl + '/app/user/registration',
route: '/user/registration',
target: '_blank',
},
]
: [
{
icon: 'vcard',
text: 'My Profile',
href: webAppUrl + '/app/user/profile',
route: '/user/profile',
},
{
icon: 'power-off',
text: 'Log Out',
onClick: () => actions.showLogoutWarning(window.location.href),
route: '/user/logout',
},
];

return (
<div className={'UserMenu-Pane' + (!isHovered ? ' inert' : '')}>
<div className="UserMenu-Pane">
{items.map((item, key) => {
const { onClick, href, target } = item;
const { route, target } = item;
const className = 'UserMenu-Pane-Item';

let props = {
className,
onClick: onClick ? onClick : () => null,
};
if (href) props = Object.assign({}, props, { href, target });
const Element = href ? 'a' : 'div';

return (
<Element key={key} {...props}>
<Link key={key} className={className} to={route} target={target}>
<Icon fa={item.icon + ' UserMenu-Pane-Item-Icon'} />
{item.text}
</Element>
</Link>
);
})}
</div>
);
}

render() {
const { onMouseEnter, onMouseLeave } = this;
const { user } = this.props;
if (!user) return null;

Expand All @@ -96,11 +66,7 @@ class UserMenu extends React.Component {
const Menu = this.renderMenu;

return (
<div
className="box UserMenu"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<div className="box UserMenu">
<Icon className="UserMenu-Icon" fa={iconClass} />
<span className="UserMenu-Title">
{typeof isGuest === 'undefined'
Expand Down
18 changes: 13 additions & 5 deletions packages/libs/web-common/src/App/UserMenu/UserMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,22 @@ $white: #e0e0e0;
padding: 5px;
font-style: italic;
}

&:focus-within,
&:hover {
.UserMenu-Pane {
opacity: 1;
pointer-events: all;
transition: none;
}
}
}

.UserMenu-Pane {
transition: opacity 500ms 500ms;
opacity: 0;
pointer-events: none;

right: 0;
top: 100%;
color: black;
Expand All @@ -33,17 +46,12 @@ $white: #e0e0e0;
min-width: 150px;
position: absolute;
border-radius: 10px;
transition: all 0.2s;
line-height: 1em;
background-color: $white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
.fa {
color: $red;
}
&.inert {
opacity: 0;
pointer-events: none;
}
&::after {
top: -3px;
width: 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@
border: 0.2em solid #00304c;
}
}

&:focus,
&__selected:focus {
outline: none;
text-decoration: underline;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
white-space: nowrap;
width: 18em;

&:hover,
&:focus {
background-color: #396aa4;
background-image: none;
}

&Text {
margin: 0 2em;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,15 @@ type ExpansionBarProps = {

function ExpansionBar(props: ExpansionBarProps) {
return (
<div className={cx('--ExpansionBar')} onClick={props.onClick}>
<button
type="button"
className={cx('--ExpansionBar')}
onClick={props.onClick}
>
{props.arrow}
<span className={cx('--ExpansionBarText')}>{props.message}</span>
{props.arrow}
</div>
</button>
);
}

Expand Down
Loading
Loading