fix: accessiblity improvements, focus states, real buttons

This commit is contained in:
Tom Moor 2019-08-30 00:27:40 -07:00
parent 140f009b4d
commit 6520a501e3
14 changed files with 123 additions and 52 deletions

View File

@ -65,6 +65,13 @@ const RealButton = styled.button`
border: 1px solid ${darken(0.15, props.theme.buttonNeutralBackground)};
}
&:focus {
transition-duration: 0.05s;
border: 1px solid ${lighten(0.4, props.theme.buttonBackground)};
box-shadow: ${lighten(0.4, props.theme.buttonBackground)} 0px 0px
0px 2px;
}
&:disabled {
color: ${props.theme.textTertiary};
}
@ -77,6 +84,12 @@ const RealButton = styled.button`
&:hover {
background: ${darken(0.05, props.theme.danger)};
}
&:focus {
transition-duration: 0.05s;
box-shadow: ${lighten(0.4, props.theme.danger)} 0px 0px
0px 3px;
}
`};
`;

View File

@ -272,8 +272,10 @@ const PrismStyles = createGlobalStyle`
}
`;
const EditorTooltip = props => (
<Tooltip offset="0, 16" delay={150} {...props} />
const EditorTooltip = ({ children, ...props }) => (
<Tooltip offset="0, 16" delay={150} {...props}>
<span>{children}</span>
</Tooltip>
);
export default withTheme(

View File

@ -0,0 +1,26 @@
// @flow
import * as React from 'react';
import styled from 'styled-components';
import { lighten } from 'polished';
const Button = styled.button`
width: 24px;
height: 24px;
background: none;
border-radius: 4px;
line-height: 0;
border: 0;
padding: 0;
&:focus {
transition-duration: 0.05s;
box-shadow: ${props => lighten(0.4, props.theme.buttonBackground)} 0px 0px
0px 3px;
outline: none;
}
`;
// $FlowFixMe - need to upgrade to get forwardRef
export default React.forwardRef((props, ref) => (
<Button {...props} ref={ref} />
));

View File

@ -2,6 +2,7 @@
import * as React from 'react';
import styled, { withTheme } from 'styled-components';
import { NavLink } from 'react-router-dom';
import { lighten } from 'polished';
type Props = {
theme: Object,
@ -23,6 +24,13 @@ const StyledNavLink = styled(NavLink)`
border-bottom: 3px solid ${props => props.theme.divider};
padding-bottom: 5px;
}
&:focus {
outline: none;
border-bottom: 3px solid
${props => lighten(0.4, props.theme.buttonBackground)};
padding-bottom: 5px;
}
`;
function Tab(props: Props) {

View File

@ -13,10 +13,6 @@ type Props = {
};
class Tooltip extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
const { shortcut, tooltip, delay = 50, className, ...rest } = this.props;

View File

@ -15,9 +15,9 @@ import Collection from 'models/Collection';
import UiStore from 'stores/UiStore';
import DocumentsStore from 'stores/DocumentsStore';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import NudeButton from 'components/NudeButton';
type Props = {
label?: React.Node,
position?: 'left' | 'right' | 'center',
ui: UiStore,
documents: DocumentsStore,
@ -89,7 +89,7 @@ class CollectionMenu extends React.Component<Props> {
};
render() {
const { collection, label, position, onOpen, onClose } = this.props;
const { collection, position, onOpen, onClose } = this.props;
return (
<React.Fragment>
@ -110,7 +110,11 @@ class CollectionMenu extends React.Component<Props> {
/>
</Modal>
<DropdownMenu
label={label || <MoreIcon />}
label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
onOpen={onOpen}
onClose={onClose}
position={position}

View File

@ -16,11 +16,11 @@ import {
newDocumentUrl,
} from 'utils/routeHelpers';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import NudeButton from 'components/NudeButton';
type Props = {
ui: UiStore,
auth: AuthStore,
label?: React.Node,
position?: 'left' | 'right' | 'center',
document: Document,
collections: CollectionStore,
@ -113,7 +113,6 @@ class DocumentMenu extends React.Component<Props> {
const {
document,
position,
label,
className,
showPrint,
showPin,
@ -125,7 +124,14 @@ class DocumentMenu extends React.Component<Props> {
if (document.isArchived) {
return (
<DropdownMenu label={label || <MoreIcon />} className={className}>
<DropdownMenu
label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
className={className}
>
<DropdownMenuItem onClick={this.handleRestore}>
Restore
</DropdownMenuItem>
@ -138,7 +144,11 @@ class DocumentMenu extends React.Component<Props> {
return (
<DropdownMenu
label={label || <MoreIcon />}
label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
className={className}
position={position}
onOpen={onOpen}

View File

@ -4,6 +4,7 @@ import { withRouter, type RouterHistory } from 'react-router-dom';
import { inject } from 'mobx-react';
import { MoreIcon } from 'outline-icons';
import NudeButton from 'components/NudeButton';
import CopyToClipboard from 'components/CopyToClipboard';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import { documentHistoryUrl } from 'utils/routeHelpers';
@ -12,7 +13,6 @@ import Document from 'models/Document';
import UiStore from 'stores/UiStore';
type Props = {
label?: React.Node,
onOpen?: () => void,
onClose: () => void,
history: RouterHistory,
@ -35,7 +35,7 @@ class RevisionMenu extends React.Component<Props> {
};
render() {
const { label, className, onOpen, onClose } = this.props;
const { className, onOpen, onClose } = this.props;
const url = `${window.location.origin}${documentHistoryUrl(
this.props.document,
this.props.revision.id
@ -43,7 +43,11 @@ class RevisionMenu extends React.Component<Props> {
return (
<DropdownMenu
label={label || <MoreIcon />}
label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
onOpen={onOpen}
onClose={onClose}
className={className}

View File

@ -5,6 +5,7 @@ import { inject, observer } from 'mobx-react';
import { observable } from 'mobx';
import { MoreIcon } from 'outline-icons';
import NudeButton from 'components/NudeButton';
import CopyToClipboard from 'components/CopyToClipboard';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import SharesStore from 'stores/SharesStore';
@ -12,7 +13,6 @@ import UiStore from 'stores/UiStore';
import Share from 'models/Share';
type Props = {
label?: React.Node,
onOpen?: () => void,
onClose: () => void,
shares: SharesStore,
@ -46,11 +46,15 @@ class ShareMenu extends React.Component<Props> {
render() {
if (this.redirectTo) return <Redirect to={this.redirectTo} push />;
const { share, label, onOpen, onClose } = this.props;
const { share, onOpen, onClose } = this.props;
return (
<DropdownMenu
label={label || <MoreIcon />}
label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
onOpen={onOpen}
onClose={onClose}
>

View File

@ -4,6 +4,7 @@ import { inject, observer } from 'mobx-react';
import { MoreIcon } from 'outline-icons';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import NudeButton from 'components/NudeButton';
import UsersStore from 'stores/UsersStore';
import User from 'models/User';
@ -61,7 +62,13 @@ class UserMenu extends React.Component<Props> {
const { user } = this.props;
return (
<DropdownMenu label={<MoreIcon />}>
<DropdownMenu
label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
>
{!user.isSuspended &&
(user.isAdmin ? (
<DropdownMenuItem onClick={this.handleDemote}>

View File

@ -8,6 +8,7 @@ import Flex from 'shared/components/Flex';
import ListItem from 'components/List/Item';
import User from 'models/User';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import NudeButton from 'components/NudeButton';
type Props = {
user: User,
@ -24,7 +25,13 @@ const MemberListItem = ({ user, onRemove, showRemove }: Props) => {
<Flex align="center">
<Permission as="span">Can edit&nbsp;</Permission>
{showRemove && (
<DropdownMenu label={<MoreIcon />}>
<DropdownMenu
label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
>
<DropdownMenuItem onClick={onRemove}>Remove</DropdownMenuItem>
</DropdownMenu>
)}

View File

@ -156,16 +156,22 @@ class Header extends React.Component<Props> {
{isEditing && (
<React.Fragment>
<Action>
<Button
onClick={this.handleSave}
title={`Save changes (${meta}+Enter)`}
disabled={savingIsDisabled}
isSaving={isSaving}
neutral={isDraft}
small
<Tooltip
tooltip="Save"
shortcut={`${meta}+enter`}
delay={500}
placement="bottom"
>
{isDraft ? 'Save Draft' : 'Done Editing'}
</Button>
<Button
onClick={this.handleSave}
disabled={savingIsDisabled}
isSaving={isSaving}
neutral={isDraft}
small
>
{isDraft ? 'Save Draft' : 'Done Editing'}
</Button>
</Tooltip>
</Action>
</React.Fragment>
)}

View File

@ -2,12 +2,12 @@
import * as React from 'react';
import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint';
import { lighten } from 'polished';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import { KeyboardIcon } from 'outline-icons';
import Modal from 'components/Modal';
import Tooltip from 'components/Tooltip';
import NudeButton from 'components/NudeButton';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
type Props = {};
@ -49,31 +49,12 @@ class KeyboardShortcutsButton extends React.Component<Props> {
}
}
const Button = styled.button`
const Button = styled(NudeButton)`
display: none;
position: fixed;
bottom: 0;
right: 0;
margin: 24px;
width: 24px;
height: 24px;
opacity: 0.8;
background: none;
border-radius: 4px;
line-height: 0;
border: 0;
padding: 0;
&:hover {
opacity: 1;
}
&:focus {
transition-duration: 0.05s;
box-shadow: ${props => lighten(0.4, props.theme.buttonBackground)} 0px 0px
0px 3px;
outline: none;
}
${breakpoint('tablet')`
display: block;

View File

@ -11,6 +11,7 @@ import Button from 'components/Button';
import Input from 'components/Input';
import HelpText from 'components/HelpText';
import Tooltip from 'components/Tooltip';
import NudeButton from 'components/NudeButton';
import UiStore from 'stores/UiStore';
import AuthStore from 'stores/AuthStore';
@ -124,7 +125,9 @@ class Invite extends React.Component<Props> {
{index !== 0 && (
<Remove>
<Tooltip tooltip="Remove invite" placement="top">
<CloseIcon onClick={() => this.handleRemove(index)} />
<NudeButton onClick={() => this.handleRemove(index)}>
<CloseIcon />
</NudeButton>
</Tooltip>
</Remove>
)}