Merge pull request #128 from joachimesque/accessible-checkboxes

[A11y] Better accessibility: checkboxes, buttons & links, visible/invisible content toggles
This commit is contained in:
Brian Lovin 2019-02-08 14:42:40 -08:00 committed by GitHub
commit 406d33dd5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 400 additions and 157 deletions

View file

@ -1,16 +1,10 @@
// @flow
// $FlowIssue
import React, { useState } from 'react';
import dynamic from 'next/dynamic';
import * as Styled from './style';
import { CopyLinkButton as StyledCopyLinkButton } from './style';
import Icon from '../Icon';
import type { ButtonProps } from './types';
const Clipboard = dynamic(() => import('react-clipboard.js'), {
ssr: false,
loading: () => null,
});
type CopyLinkProps = {
...$Exact<ButtonProps>,
text: string,
@ -26,20 +20,18 @@ export default function CopyLinkButton(props: CopyLinkProps) {
};
return (
<Clipboard
style={{ background: 'none' }}
<StyledCopyLinkButton
data-clipboard-text={text}
onSuccess={onClick}
component="a"
component="button"
data-cy="copy-link-button"
isClicked={isClicked}
aria-label="Copy the websites address to your clipboard."
type="button"
{...props}
>
<Styled.CopyLinkButton
data-cy="copy-link-button"
isClicked={isClicked}
{...props}
>
<Icon glyph="link" size={24} />
{isClicked ? 'Copied!' : children}
</Styled.CopyLinkButton>
</Clipboard>
<Icon glyph="link" size={24} />
<span>{isClicked ? 'Copied!' : children}</span>
</StyledCopyLinkButton>
);
}

View file

@ -3,6 +3,12 @@ import styled, { css } from 'styled-components';
import { hexa, tint } from '../globals';
import type { ButtonSize } from './types';
import { theme } from '../theme';
import dynamic from 'next/dynamic';
const Clipboard = dynamic(() => import('react-clipboard.js'), {
ssr: false,
loading: () => null,
});
const getPadding = (size: ButtonSize) => {
switch (size) {
@ -32,7 +38,7 @@ const getFontSize = (size: ButtonSize) => {
}
};
const base = css`
export const base = css`
-webkit-appearance: none;
display: flex;
flex: none;
@ -58,11 +64,15 @@ const base = css`
cursor: not-allowed;
}
&:hover {
&:hover, &:active, &:focus {
transition: all 0.2s ease-in-out;
box-shadow: ${props =>
props.disabled ? 'none' : `${theme.shadows.button}`};
}
&:active, &:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.5)};
}
`;
export const Button = styled.button`
@ -126,7 +136,7 @@ export const PrimaryButton = styled.button`
&:focus {
box-shadow: 0 0 0 1px ${props =>
props.theme.bg.default}, 0 0 0 3px ${props =>
hexa(props.theme.brand.alt, 0.16)};
hexa(props.theme.brand.alt, 0.5)};
}
`;
@ -137,15 +147,15 @@ export const GhostButton = styled.button`
background-color: transparent;
background-image: none;
&:hover {
background: ${props => tint(props.theme.bg.wash, -3)};
&:hover, &:active, &:focus {
background: ${props => tint(props.theme.bg.wash, -8)};
color: ${theme.text.default};
box-shadow: none;
}
&:focus {
&:active, &:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.08)};
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
}
`;
@ -220,12 +230,12 @@ export const ButtonSegmentRow = styled.div`
${PrimaryButton} {
&:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.brand.alt, 0.16)};
0 0 0 3px ${props => hexa(props.theme.brand.alt, 0.5)};
}
}
`;
export const FacebookButton = styled.button`
export const FacebookButton = styled.a`
${base}
border: 1px solid ${theme.social.facebook};
color: ${theme.bg.default};
@ -262,11 +272,11 @@ export const FacebookButton = styled.button`
&:focus {
box-shadow: 0 0 0 1px ${props =>
props.theme.bg.default}, 0 0 0 3px ${props =>
hexa(props.theme.social.facebook, 0.16)};
hexa(props.theme.social.facebook, 0.5)};
}
`;
export const TwitterButton = styled.button`
export const TwitterButton = styled.a`
${base}
border: 1px solid ${theme.social.twitter};
color: ${theme.bg.default};
@ -303,12 +313,13 @@ export const TwitterButton = styled.button`
&:focus {
box-shadow: 0 0 0 1px ${props =>
props.theme.bg.default}, 0 0 0 3px ${props =>
hexa(props.theme.social.twitter, 0.16)};
hexa(props.theme.social.twitter, 0.5)};
}
`;
export const CopyLinkButton = styled.button`
export const CopyLinkButton = styled(Clipboard)`
${base}
transition: all ${theme.animations.default};
border: 1px solid ${props =>
props.isClicked
? tint(props.theme.success.default, -10)
@ -356,8 +367,9 @@ export const CopyLinkButton = styled.button`
&:focus {
box-shadow: 0 0 0 1px ${props =>
props.theme.bg.default}, 0 0 0 3px ${props =>
props.isClicked
? hexa(props.theme.success.default, 0.16)
: props.theme.border.default};
props.isClicked
? hexa(props.theme.success.default, 0.5)
: props.theme.border.default};
}
`;

View file

@ -38,8 +38,8 @@ export const AppRow = ({ app }: Props) => {
};
return (
<AppRowContainer href={app.url} target="_blank" rel="noopener noreferrer">
<AppMeta>
<AppRowContainer>
<AppMeta href={app.url} target="_blank" rel="noopener noreferrer" title={`go to: ${app.name}`}>
{app.image && <AppIcon alt={app.name} src={app.image} />}
<AppName>{app.name}</AppName>
</AppMeta>
@ -49,4 +49,4 @@ export const AppRow = ({ app }: Props) => {
{app.offer && <Offer offer={app.offer} />}
</AppRowContainer>
);
};
};

View file

@ -1,23 +1,39 @@
// @flow
// $FlowIssue
import React, { useState } from 'react';
import { AppsContainer, SectionHeading, ExpandContainer } from './style';
import React, { useState, useRef } from 'react';
import { AppsContainer, SectionHeading, ExpandContainer, ExpandContent } from './style';
import { Button } from '../Button';
import type { ChecklistResource } from '../../types';
import { AppRow } from './App';
type Props = {
resource: ChecklistResource,
handleAppsExpand: Function,
};
export const Apps = ({ resource }: Props) => {
export const Apps = ({ resource, handleAppsExpand }: Props) => {
const [overflowExpanded, setOverflowExpanded] = useState(false);
const [contentHeight, setcontentHeight] = useState(2000);
const expandContentContainer = useRef(null);
function handleExpand() {
let expandContentHeight =
expandContentContainer.current
? expandContentContainer.current.scrollHeight
: contentHeight;
setcontentHeight(expandContentHeight)
setOverflowExpanded(!overflowExpanded)
handleAppsExpand(overflowExpanded ? -expandContentHeight : expandContentHeight);
}
if (!resource.apps) return null;
let appList = resource.apps;
let overflowAppList;
if (appList.length > 3) {
if (appList && appList.length > 3) {
overflowAppList = appList.slice(3, appList.length);
appList = appList.slice(0, 3);
}
@ -25,19 +41,34 @@ export const Apps = ({ resource }: Props) => {
return (
<AppsContainer overflowExpanded={overflowExpanded}>
<SectionHeading>Apps</SectionHeading>
{appList.map(app => (
{appList && appList.map(app => (
<AppRow key={app.name} app={app} />
))}
{overflowAppList && !overflowExpanded && (
<ExpandContainer onClick={() => setOverflowExpanded(true)}>
<Button>Show more choices</Button>
</ExpandContainer>
{overflowAppList && (
<React.Fragment>
<ExpandContent
id={`apps_${resource.id}`}
role="region"
tabindex="-1"
ref={expandContentContainer}
style={{ '--maxHeight': `${contentHeight}px` }}
aria-hidden={!overflowExpanded}
>
{overflowAppList.map(app => <AppRow key={app.name} app={app} />)}
</ExpandContent>
<ExpandContainer
onClick={handleExpand}
role="button"
aria-expanded={overflowExpanded}
aria-controls={`apps_${resource.id}`}
>
<Button type="button">Show {overflowExpanded ? "less" : "more"} choices</Button>
</ExpandContainer>
</React.Fragment>
)}
{overflowAppList &&
overflowExpanded &&
overflowAppList.map(app => <AppRow key={app.name} app={app} />)}
</AppsContainer>
);
};

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import { Title, Description, Uncollapse } from './style';
import { Title, Uncollapse } from './style';
import type { ChecklistResource } from '../../types';
type Props = {
@ -13,10 +13,14 @@ export const Heading = ({ resource, isCollapsed, handleCollapse }: Props) => (
<React.Fragment>
<Title>
{resource.title}
<Uncollapse onClick={handleCollapse}>
<Uncollapse
onClick={handleCollapse}
aria-controls={`content_${resource.id}`}
aria-expanded={!isCollapsed}
type="button"
>
{isCollapsed ? 'Show details' : 'Hide details'}
</Uncollapse>
</Title>
{!isCollapsed && <Description source={resource.description} />}
</React.Fragment>
);

View file

@ -10,10 +10,11 @@ import { Resources } from './Resources';
import {
Container,
CheckboxContainer,
Checkbox,
CardContent,
ResourceContent,
Divider,
Description,
Content,
} from './style';
type Props = {
@ -24,10 +25,18 @@ type State = {
isChecked: boolean,
isLoading: boolean,
isCollapsed: boolean,
contentHeight: number,
};
class ChecklistItem extends React.Component<Props, State> {
state = { isChecked: false, isLoading: true, isCollapsed: true };
state = { isChecked: false, isLoading: true, isCollapsed: true, contentHeight: 2000, };
contentContainer: { current: null | HTMLDivElement }
constructor(props: Props) {
super(props);
this.contentContainer = React.createRef();
}
componentDidMount() {
const { resource } = this.props;
@ -39,6 +48,14 @@ class ChecklistItem extends React.Component<Props, State> {
});
}
componentDidUpdate(prevProps: Props, prevState: State) {
if (prevState.isLoading && !this.state.isLoading && this.contentContainer.current) {
return this.setState({
contentHeight: this.contentContainer.current.scrollHeight,
})
}
}
handleSetChecked = () => {
const { isChecked } = this.state;
const { resource } = this.props;
@ -46,11 +63,19 @@ class ChecklistItem extends React.Component<Props, State> {
return this.setState({ isChecked: !isChecked, isCollapsed: !isChecked });
};
uncollapse = () =>
this.setState(state => ({ isCollapsed: !state.isCollapsed }));
uncollapse = () => {
this.setState(state => ({ isCollapsed: !state.isCollapsed }));
this.contentContainer.current && this.contentContainer.current.focus();
};
handleAppsExpand = (appsContainerHeight: number) => {
return this.contentContainer.current && this.setState({
contentHeight: this.contentContainer.current.scrollHeight + appsContainerHeight,
})
}
render() {
const { isChecked, isLoading, isCollapsed } = this.state;
const { isChecked, isLoading, isCollapsed, contentHeight } = this.state;
const { resource } = this.props;
if (isLoading) return <LoadingChecklistItem />;
@ -59,30 +84,60 @@ class ChecklistItem extends React.Component<Props, State> {
<Container>
<Card isChecked={isChecked}>
<CardContent isCollapsed={isCollapsed}>
<CheckboxContainer onClick={this.handleSetChecked}>
<Checkbox isChecked={isChecked} />
<CheckboxContainer>
<input
aria-expanded={!isCollapsed}
type="checkbox"
checked={isChecked}
id={`checkbox_${resource.id}`}
onChange={this.handleSetChecked}
aria-controls={`content_${resource.id}`}
/>
<label htmlFor={`checkbox_${resource.id}`}>
{resource.title}
</label>
</CheckboxContainer>
<ResourceContent isChecked={isChecked} isCollapsed={isCollapsed}>
<ResourceContent
isChecked={isChecked}
isCollapsed={isCollapsed}
>
<Heading
resource={resource}
isCollapsed={isCollapsed}
handleCollapse={this.uncollapse}
/>
{!isCollapsed && resource.apps && (
<React.Fragment>
<Divider />
<Apps resource={resource} />
</React.Fragment>
)}
<Content
aria-hidden={isCollapsed}
id={`content_${resource.id}`}
role="region"
tabindex="-1"
ref={this.contentContainer}
style={{ '--maxHeight': `${contentHeight}px` }}
>
<Description source={resource.description} />
{resource.apps && (
<React.Fragment>
<Divider />
<Apps
resource={resource}
handleAppsExpand={this.handleAppsExpand}
/>
</React.Fragment>
)}
{resource.resources && (
<React.Fragment>
<Divider />
<Resources resource={resource} />
</React.Fragment>
)}
</Content>
{!isCollapsed && resource.resources && (
<React.Fragment>
<Divider />
<Resources resource={resource} />
</React.Fragment>
)}
</ResourceContent>
</CardContent>
</Card>

View file

@ -2,7 +2,7 @@
import styled, { css } from 'styled-components';
import Markdown from 'react-markdown';
import { theme } from '../theme';
import { Shadows, tint } from '../globals';
import { Shadows, tint, hexa } from '../globals';
export const Container = styled.div`
margin-bottom: 24px;
@ -69,35 +69,29 @@ export const CheckboxContainer = styled.div`
margin-top: 4px;
width: 32px;
}
`;
export const Checkbox = styled.span`
width: 32px;
height: 32px;
border-radius: 4px;
border: 1px solid
${props => (props.isChecked ? theme.bg.default : theme.border.default)};
background: ${props =>
props.isChecked ? theme.success.default : theme.bg.wash};
cursor: pointer;
position: relative;
background-image: ${props =>
props.isChecked
? 'radial-gradient(circle at top right, #a913de, #6ac9ff)'
: 'none'};
box-shadow: ${props =>
props.isChecked ? 'inset 0 0 1px rgba(0,0,0,0.4)' : 'none'};
&:hover {
${props => !props.isChecked && Shadows.default};
background: ${theme.bg.default};
background-image: ${props =>
props.isChecked
? 'radial-gradient(circle at top right, #a913de, #6ac9ff)'
: 'none'};
input[type="checkbox"] {
position: absolute;
}
&:after {
input[type="checkbox"] + label {
width: 32px;
height: 32px;
border-radius: 4px;
border: 1px solid ${theme.border.default};
background: ${theme.bg.wash};
cursor: pointer;
position: relative;
overflow: hidden;
text-indent: -1000px;
}
input[type="checkbox"] + label:hover {
${Shadows.default};
background: ${theme.bg.default};
}
input[type="checkbox"] + label::after {
content: '';
position: absolute;
display: block;
@ -105,11 +99,49 @@ export const Checkbox = styled.span`
top: 6px;
width: 6px;
height: 12px;
border: solid
${props => (props.isChecked ? theme.bg.default : theme.border.active)};
border: solid ${theme.border.active};
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
input[type="checkbox"]:checked + label {
border: 1px solid ${theme.bg.default};
}
input[type="checkbox"]:checked + label::after {
border: solid ${theme.bg.default};
border-width: 0 2px 2px 0;
}
/* This ::before pseudo-element is used to animate the gradient
which does not support transitions. */
input[type="checkbox"] + label::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transition: opacity ${theme.animations.default};
opacity: 0;
background-image: radial-gradient(circle at top right, #a913de, #6ac9ff);
box-shadow: inset 0 0 1px rgba(0,0,0,0.4);
}
input[type="checkbox"]:checked + label::before {
opacity: 1;
background-color: ${theme.success.default};
}
input[type="checkbox"]:active + label, input[type="checkbox"]:focus + label {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.brand.default, 0.5)};
}
input[type="checkbox"]:active:checked + label, input[type="checkbox"]:focus:checked + label {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.spectrum.default, 0.5)};
}
`;
export const ResourceContent = styled.div`
@ -129,12 +161,7 @@ export const ResourceContent = styled.div`
export const AppsContainer = styled.div``;
export const AppMeta = styled.div`
display: flex;
align-items: center;
`;
export const AppRowContainer = styled.a`
export const AppRowContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
@ -170,6 +197,19 @@ export const AppRowContainer = styled.a`
}
`;
export const AppMeta = styled.a`
display: flex;
align-items: center;
padding-right: 6px;
border-radius: 8px;
&:active, &:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
background: ${theme.bg.wash};
}
`;
export const AppIcon = styled.img`
width: 40px;
height: 40px;
@ -207,8 +247,7 @@ export const AppSourcesListItem = styled.li`
align-items: center;
justify-content: center;
color: ${theme.text.tertiary};
padding: 4px 10px;
border-radius: 4px;
padding: 2px 8px;
min-width: 56px;
transition: all 0.1s ease-in-out;
@ -217,6 +256,19 @@ export const AppSourcesListItem = styled.li`
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2px;
border-radius: 8px;
&:hover {
color: ${theme.text.default};
}
&:active, &:focus {
color: ${theme.text.default};
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
background: ${theme.bg.wash};
}
}
.icon {
@ -225,9 +277,6 @@ export const AppSourcesListItem = styled.li`
left: 4px;
}
&:hover {
color: ${theme.text.default};
}
@media (max-width: 768px) {
padding: 4px;
@ -280,11 +329,16 @@ export const ResourceRowContainer = styled.a`
margin-right: 8px;
}
&:hover {
&:hover, &:active, &:focus {
background: ${theme.bg.wash};
color: ${theme.text.default};
}
&:active, &:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
}
@media (max-width: 768px) {
width: calc(100% + 40px);
border-radius: 0;
@ -292,7 +346,7 @@ export const ResourceRowContainer = styled.a`
margin-left: -24px;
padding-left: 24px;
&:hover {
&:hover, &:active, &:focus {
background: ${theme.bg.default}!important;
}
@ -309,7 +363,7 @@ export const ResourceName = styled.p`
font-weight: 400;
`;
export const Divider = styled.div`
export const Divider = styled.hr`
border-bottom: 1px solid ${tint(theme.bg.wash, -4)};
margin-top: 24px;
margin-bottom: 24px;
@ -321,7 +375,27 @@ export const Divider = styled.div`
}
`;
export const Uncollapse = styled.span`
export const Content = styled.div`
transition:
max-height ${theme.animations.default},
opacity ${theme.animations.default},
visibility ${theme.animations.default};
max-height: 2000px;
max-height: var(--maxHeight);
opacity: 1;
visibility: visible;
&[aria-hidden="true"] {
max-height: 0;
opacity: 0;
visibility: hidden;
}
`
export const Uncollapse = styled.button`
background: ${theme.bg.wash};
border-radius: 20px;
padding: 8px 16px;
@ -344,6 +418,12 @@ export const Uncollapse = styled.span`
color: ${theme.text.default};
background: ${tint(theme.bg.wash, -4)};
}
&:active, &:focus {
transition: all 0.2s ease-in-out;
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
}
`;
export const OfferContainer = styled.a`
@ -379,6 +459,12 @@ export const OfferContainer = styled.a`
&:hover {
color: ${theme.text.secondary};
}
&:active, &:focus {
box-shadow: inset 0 0 1px ${theme.border.active},
0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
background: ${theme.bg.wash};
}
`;
export const LeftBorder = styled.div`
@ -417,3 +503,22 @@ export const ExpandContainer = styled.div`
width: calc(100% + 40px);
}
`;
export const ExpandContent = styled.div`
transition:
max-height ${theme.animations.default},
opacity ${theme.animations.default},
visibility ${theme.animations.default};
max-height: 2000px;
max-height: var(--maxHeight);
opacity: 1;
visibility: visible;
&[aria-hidden="true"] {
max-height: 0;
opacity: 0;
visibility: hidden;
}
`

View file

@ -1,6 +1,7 @@
// @flow
import styled from 'styled-components';
import { theme } from '../theme';
import { hexa } from '../globals';
export const Container = styled.div`
margin-top: 128px;
@ -21,24 +22,32 @@ export const Description = styled.p`
color: ${theme.text.default};
margin-left: 4px;
}
a:active, a:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
}
`;
export const Icons = styled.div`
display: flex;
flex: 1 0 auto;
align-items: flex-start;
margin-left: -16px;
padding-bottom: 8px;
a {
color: ${theme.text.tertiary};
margin-right: 16px;
line-height: 1;
border-radius: 10px;
height: 32px;
}
a:hover {
a:hover, a:active, a:focus {
color: ${theme.text.default};
}
.icon {
margin-left: 16px;
a:active, a:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
}
`;

View file

@ -1,7 +1,7 @@
// @flow
import * as React from 'react';
import Link from 'next/link';
import { Container, ButtonRowContainer, Label } from './style';
import { Container, ButtonRowContainer, Label, LogoLink } from './style';
import { PrimaryButton, GhostButton } from '../Button';
import Logo from './Logo';
@ -14,27 +14,30 @@ export default function Header(props: Props) {
return (
<Container showHeaderShadow={showHeaderShadow} data-cy="header">
<Link href="/">
<a style={{ display: 'flex', alignItems: 'center' }}>
<Label>Security Checklist</Label>
<Logo />
</a>
</Link>
<div>
<Link href="/">
<LogoLink href="/">
<Label>Security Checklist</Label>
<Logo />
</LogoLink>
</Link>
</div>
<ButtonRowContainer>
<Link href="/about">
<a>
<GhostButton>About</GhostButton>
</a>
<GhostButton as="a" href="/about">
About
</GhostButton>
</Link>
<a
<PrimaryButton
href="https://github.com/brianlovin/security-checklist"
target="_blank"
rel="noopener noreferrer"
as="a"
>
<PrimaryButton>Contribute</PrimaryButton>
</a>
Contribute
</PrimaryButton>
</ButtonRowContainer>
</Container>
);

View file

@ -1,6 +1,7 @@
// @flow
import styled from 'styled-components';
import { theme } from '../theme';
import { hexa } from '../globals';
export const Container = styled.div`
display: grid;
@ -18,10 +19,6 @@ export const Container = styled.div`
props.showHeaderShadow ? '0 4px 8px rgba(0,0,0,0.04)' : 'none'};
transition: all 0.2s ease-in-out;
a {
margin-left: 8px;
}
@media (max-width: 968px) {
padding: 8px 16px;
grid-template-columns: 1fr 1fr;
@ -41,6 +38,26 @@ export const ButtonRowContainer = styled.div`
justify-content: flex-end;
grid-area: actions;
align-items: center;
a {
margin-left: 8px;
}
`;
export const LogoLink = styled.a`
transition: all ${props => props.theme.animations.default};
display: inline-flex;
align-items: center;
border-radius: 6px;
&:hover {
transform: scale(1.2);
}
&:active, &:focus {
transform: scale(1.2);
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
}
`;
export const Label = styled.h1`

View file

@ -74,7 +74,11 @@ export default function Page(props: Props) {
<Header showHeaderShadow={showHeaderShadow} />
<InnerContainer>{children}</InnerContainer>
<Footer />
<ScrollToTop isVisible={scrollToTopVisible} onClick={scrollToTop}>
<ScrollToTop
isVisible={scrollToTopVisible}
onClick={scrollToTop}
type="button"
>
<Icon glyph="view-forward" size={32} />
</ScrollToTop>
</Container>

View file

@ -1,6 +1,6 @@
// @flow
import styled from 'styled-components';
import { tint } from '../globals';
import { hexa, tint } from '../globals';
import { theme } from '../theme';
export const Container = styled.div`
@ -47,7 +47,7 @@ export const SectionHeading = styled.div`
}
`;
export const Heading = styled.h3`
export const Heading = styled.h2`
font-size: 40px;
font-weight: 700;
color: ${theme.text.default};
@ -58,7 +58,7 @@ export const Heading = styled.h3`
}
`;
export const Subheading = styled.h4`
export const Subheading = styled.p`
font-size: 22px;
font-weight: 400;
color: ${theme.text.tertiary};
@ -69,6 +69,18 @@ export const Subheading = styled.h4`
color: ${theme.text.default};
font-weight: 500;
}
a:hover {
text-decoration: underline;
text-decoration-color: ${theme.text.tertiary};
}
a:active, a:focus {
box-shadow: 0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.text.tertiary, 0.25)};
}
& + & {
margin-top: 16px;
}
@media (max-width: 968px) {
max-width: 100%;
@ -113,8 +125,11 @@ export const ScrollToTop = styled.button`
transition: all 0.2s ease-in-out;
}
&:active {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.09);
&:active, &:focus {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.09),
0 0 0 1px ${theme.bg.default},
0 0 0 3px ${props => hexa(props.theme.brand.default, 0.5)};
outline: none;
transform: translateY(-2px);
transition: all 0.2s ease-in-out;
}

View file

@ -6,21 +6,21 @@ import { Container } from './style';
export default function ShareButtons() {
return (
<Container>
<a
<FacebookButton
href="https://www.facebook.com/sharer/sharer.php?u=https://securitycheckli.st"
target="_blank"
rel="noopener noreferrer"
>
<FacebookButton>Share</FacebookButton>
</a>
Share
</FacebookButton>
<a
<TwitterButton
href="http://twitter.com/share?text=Check out Security Checklist, a checklist to help people stay safe online&url=https://securitycheckli.st"
target="_blank"
rel="noopener noreferrer"
>
<TwitterButton>Tweet</TwitterButton>
</a>
Tweet
</TwitterButton>
<CopyLinkButton text="https://securitycheckli.st">
Share Link

View file

@ -13,10 +13,8 @@ export const Container = styled.div`
@media (max-width: 568px) {
grid-template-columns: 1fr;
grid-gap: 0;
button {
margin-top: 16px;
a, button {
width: 100%;
}
}

View file

@ -2,7 +2,7 @@ export const getItemFromStorage = key => {
if (!localStorage) return;
try {
return JSON.parse(localStorage.getItem(key));
return JSON.parse(localStorage.getItem(key) || false);
} catch (err) {}
};

View file

@ -51,8 +51,6 @@ export default function About() {
.
</Subheading>
<div style={{ padding: '8px' }} />
<Subheading>
The code that powers this website is{' '}
<a