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)}; 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 { &:disabled {
color: ${props.theme.textTertiary}; color: ${props.theme.textTertiary};
} }
@ -77,6 +84,12 @@ const RealButton = styled.button`
&:hover { &:hover {
background: ${darken(0.05, props.theme.danger)}; 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 => ( const EditorTooltip = ({ children, ...props }) => (
<Tooltip offset="0, 16" delay={150} {...props} /> <Tooltip offset="0, 16" delay={150} {...props}>
<span>{children}</span>
</Tooltip>
); );
export default withTheme( 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 * as React from 'react';
import styled, { withTheme } from 'styled-components'; import styled, { withTheme } from 'styled-components';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { lighten } from 'polished';
type Props = { type Props = {
theme: Object, theme: Object,
@ -23,6 +24,13 @@ const StyledNavLink = styled(NavLink)`
border-bottom: 3px solid ${props => props.theme.divider}; border-bottom: 3px solid ${props => props.theme.divider};
padding-bottom: 5px; padding-bottom: 5px;
} }
&:focus {
outline: none;
border-bottom: 3px solid
${props => lighten(0.4, props.theme.buttonBackground)};
padding-bottom: 5px;
}
`; `;
function Tab(props: Props) { function Tab(props: Props) {

View File

@ -13,10 +13,6 @@ type Props = {
}; };
class Tooltip extends React.Component<Props> { class Tooltip extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() { render() {
const { shortcut, tooltip, delay = 50, className, ...rest } = this.props; 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 UiStore from 'stores/UiStore';
import DocumentsStore from 'stores/DocumentsStore'; import DocumentsStore from 'stores/DocumentsStore';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import NudeButton from 'components/NudeButton';
type Props = { type Props = {
label?: React.Node,
position?: 'left' | 'right' | 'center', position?: 'left' | 'right' | 'center',
ui: UiStore, ui: UiStore,
documents: DocumentsStore, documents: DocumentsStore,
@ -89,7 +89,7 @@ class CollectionMenu extends React.Component<Props> {
}; };
render() { render() {
const { collection, label, position, onOpen, onClose } = this.props; const { collection, position, onOpen, onClose } = this.props;
return ( return (
<React.Fragment> <React.Fragment>
@ -110,7 +110,11 @@ class CollectionMenu extends React.Component<Props> {
/> />
</Modal> </Modal>
<DropdownMenu <DropdownMenu
label={label || <MoreIcon />} label={
<NudeButton>
<MoreIcon />
</NudeButton>
}
onOpen={onOpen} onOpen={onOpen}
onClose={onClose} onClose={onClose}
position={position} position={position}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,12 +2,12 @@
import * as React from 'react'; import * as React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import breakpoint from 'styled-components-breakpoint'; import breakpoint from 'styled-components-breakpoint';
import { lighten } from 'polished';
import { observable } from 'mobx'; import { observable } from 'mobx';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { KeyboardIcon } from 'outline-icons'; import { KeyboardIcon } from 'outline-icons';
import Modal from 'components/Modal'; import Modal from 'components/Modal';
import Tooltip from 'components/Tooltip'; import Tooltip from 'components/Tooltip';
import NudeButton from 'components/NudeButton';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts'; import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
type Props = {}; type Props = {};
@ -49,31 +49,12 @@ class KeyboardShortcutsButton extends React.Component<Props> {
} }
} }
const Button = styled.button` const Button = styled(NudeButton)`
display: none; display: none;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
margin: 24px; 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')` ${breakpoint('tablet')`
display: block; display: block;

View File

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