fix: accessiblity improvements, focus states, real buttons
This commit is contained in:
@ -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;
|
||||||
|
}
|
||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
26
app/components/NudeButton.js
Normal file
26
app/components/NudeButton.js
Normal 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} />
|
||||||
|
));
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
>
|
>
|
||||||
|
@ -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}>
|
||||||
|
@ -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 </Permission>
|
<Permission as="span">Can edit </Permission>
|
||||||
{showRemove && (
|
{showRemove && (
|
||||||
<DropdownMenu label={<MoreIcon />}>
|
<DropdownMenu
|
||||||
|
label={
|
||||||
|
<NudeButton>
|
||||||
|
<MoreIcon />
|
||||||
|
</NudeButton>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuItem onClick={onRemove}>Remove</DropdownMenuItem>
|
<DropdownMenuItem onClick={onRemove}>Remove</DropdownMenuItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
Reference in New Issue
Block a user