A11y and donate popup

This commit is contained in:
Ben Grant 2021-06-01 02:45:15 +10:00
parent decfc2b7e6
commit 2544f3b312
9 changed files with 118 additions and 47 deletions

View file

@ -107,6 +107,11 @@ const App = () => {
}, },
})} })}
/> />
<Suspense fallback={<Loading />}>
<Settings />
</Suspense>
<Switch> <Switch>
<Route path="/" exact render={props => ( <Route path="/" exact render={props => (
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
@ -135,10 +140,6 @@ const App = () => {
)} /> )} />
</Switch> </Switch>
<Suspense fallback={<Loading />}>
<Settings />
</Suspense>
{eggVisible && <Egg eggKey={eggKey} onClose={() => setEggVisible(false)} />} {eggVisible && <Egg eggKey={eggKey} onClose={() => setEggVisible(false)} />}
</ThemeProvider> </ThemeProvider>
</BrowserRouter> </BrowserRouter>

View file

@ -1,15 +1,33 @@
import { useEffect } from 'react'; import { useState, useEffect, useRef } from 'react';
import { Button } from 'components'; import { Button } from 'components';
import { useTWAStore } from 'stores'; import { useTWAStore } from 'stores';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import {
Wrapper,
Options,
} from './donateStyle';
const PAYMENT_METHOD = 'https://play.google.com/billing'; const PAYMENT_METHOD = 'https://play.google.com/billing';
const SKU = 'crab_donation'; const SKU = 'crab_donation';
const Donate = ({ onDonate = null }) => { const Donate = () => {
const store = useTWAStore(); const store = useTWAStore();
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const firstLinkRef = useRef();
const buttonRef = useRef();
const modalRef = useRef();
const [isOpen, _setIsOpen] = useState(false);
const setIsOpen = open => {
_setIsOpen(open);
if (open) {
window.setTimeout(() => firstLinkRef.current.focus(), 150);
}
};
useEffect(() => { useEffect(() => {
if (store.TWA === undefined) { if (store.TWA === undefined) {
store.setTWA(document.referrer.includes('android-app://fit.crab')); store.setTWA(document.referrer.includes('android-app://fit.crab'));
@ -71,7 +89,7 @@ const Donate = ({ onDonate = null }) => {
}; };
return ( return (
<div style={{ marginTop: 6, marginLeft: 12 }}> <Wrapper>
<a <a
onClick={event => { onClick={event => {
gtag('event', 'donate', { 'event_category': 'donate' }); gtag('event', 'donate', { 'event_category': 'donate' });
@ -82,14 +100,15 @@ const Donate = ({ onDonate = null }) => {
alert(t('donate.messages.error')); alert(t('donate.messages.error'));
} }
} }
} else if (onDonate !== null) { } else {
event.preventDefault(); event.preventDefault();
onDonate(); setIsOpen(true);
} }
}} }}
href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=5" href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=5"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
ref={buttonRef}
> >
<Button <Button
buttonHeight="30px" buttonHeight="30px"
@ -99,7 +118,21 @@ const Donate = ({ onDonate = null }) => {
title={t('donate.title')} title={t('donate.title')}
>{t('donate.button')}</Button> >{t('donate.button')}</Button>
</a> </a>
</div>
<Options
isOpen={isOpen}
ref={modalRef}
onBlur={e => {
if (modalRef.current.contains(e.relatedTarget)) return;
setIsOpen(false);
}}
>
<a onClick={() => setIsOpen(false)} ref={firstLinkRef} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=2" target="_blank" rel="noreferrer">{t('donate.options.$2')}</a>
<a onClick={() => setIsOpen(false)} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=5" target="_blank" rel="noreferrer"><strong>{t('donate.options.$5')}</strong></a>
<a onClick={() => setIsOpen(false)} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=10" target="_blank" rel="noreferrer">{t('donate.options.$10')}</a>
<a onClick={() => setIsOpen(false)} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD" target="_blank" rel="noreferrer">{t('donate.options.choose')}</a>
</Options>
</Wrapper>
); );
} }

View file

@ -0,0 +1,52 @@
import styled from '@emotion/styled';
export const Wrapper = styled.div`
margin-top: 6px;
margin-left: 12px;
position: relative;
`;
export const Options = styled.div`
position: absolute;
bottom: calc(100% + 20px);
right: 0;
background-color: ${props => props.theme.background};
${props => props.theme.mode === 'dark' && `
border: 1px solid ${props.theme.primaryBackground};
`}
z-index: 60;
padding: 4px 10px;
border-radius: 14px;
box-sizing: border-box;
max-width: calc(100vw - 20px);
box-shadow: 0 3px 6px 0 rgba(0,0,0,.3);
visibility: hidden;
pointer-events: none;
opacity: 0;
transform: translateY(5px);
transition: opacity .15s, transform .15s, visibility .15s;
${props => props.isOpen && `
pointer-events: all;
opacity: 1;
transform: translateY(0);
visibility: visible;
`}
& a {
display: block;
white-space: nowrap;
text-align: center;
padding: 4px 20px;
margin: 6px 0;
text-decoration: none;
border-radius: 100px;
background-color: ${props => props.theme.primary};
color: ${props => props.theme.background};
&:hover {
text-decoration: underline;
}
}
`;

View file

@ -1,28 +1,15 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Donate } from 'components'; import { Donate } from 'components';
import { Wrapper, Link } from './footerStyle'; import { Wrapper } from './footerStyle';
const Footer = (props) => { const Footer = (props) => {
const [donateMode, setDonateMode] = useState(false);
const { t } = useTranslation('common'); const { t } = useTranslation('common');
return ( return (
<Wrapper id="donate" donateMode={donateMode} {...props}> <Wrapper id="donate" {...props}>
{donateMode ? ( <span>{t('donate.info')}</span>
<> <Donate />
<Link href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=2" target="_blank">{t('donate.options.$2')}</Link>
<Link href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=5" target="_blank"><strong>{t('donate.options.$5')}</strong></Link>
<Link href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=10" target="_blank">{t('donate.options.$10')}</Link>
<Link href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD" target="_blank">{t('donate.options.choose')}</Link>
</>
) : (
<>
<span>{t('donate.info')}</span>
<Donate onDonate={() => setDonateMode(true)} />
</>
)}
</Wrapper> </Wrapper>
); );
}; };

View file

@ -19,20 +19,4 @@ export const Wrapper = styled.footer`
margin-bottom: 20px; margin-bottom: 20px;
} }
`} `}
${props => props.donateMode && `
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
`}
`;
export const Link = styled.a`
padding: 11px 10px;
white-space: nowrap;
& strong {
font-weight: 800;
}
`; `;

View file

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useRef } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -27,9 +27,18 @@ const setDefaults = (lang, store) => {
const Settings = () => { const Settings = () => {
const theme = useTheme(); const theme = useTheme();
const store = useSettingsStore(); const store = useSettingsStore();
const [isOpen, setIsOpen] = useState(false); const [isOpen, _setIsOpen] = useState(false);
const { t, i18n } = useTranslation('common'); const { t, i18n } = useTranslation('common');
const setLocale = useLocaleUpdateStore(state => state.setLocale); const setLocale = useLocaleUpdateStore(state => state.setLocale);
const firstControlRef = useRef();
const setIsOpen = open => {
_setIsOpen(open);
if (open) {
window.setTimeout(() => firstControlRef.current.focus(), 150);
}
};
useEffect(() => { useEffect(() => {
if (Object.keys(locales).includes(i18n.language)) { if (Object.keys(locales).includes(i18n.language)) {
@ -57,7 +66,6 @@ const Settings = () => {
<> <>
<OpenButton <OpenButton
isOpen={isOpen} isOpen={isOpen}
tabIndex="1"
type="button" type="button"
onClick={() => setIsOpen(!isOpen)} title={t('options.name')} onClick={() => setIsOpen(!isOpen)} title={t('options.name')}
> >
@ -78,6 +86,7 @@ const Settings = () => {
}} }}
value={store.weekStart === 0 ? 'Sunday' : 'Monday'} value={store.weekStart === 0 ? 'Sunday' : 'Monday'}
onChange={value => store.setWeekStart(value === 'Sunday' ? 0 : 1)} onChange={value => store.setWeekStart(value === 'Sunday' ? 0 : 1)}
inputRef={firstControlRef}
/> />
<ToggleField <ToggleField

View file

@ -64,12 +64,14 @@ export const Modal = styled.div`
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-10px);
transition: opacity .15s, transform .15s; visibility: hidden;
transition: opacity .15s, transform .15s, visibility .15s;
${props => props.isOpen && ` ${props => props.isOpen && `
pointer-events: all; pointer-events: all;
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
visibility: visible;
`} `}
`; `;

View file

@ -15,6 +15,7 @@ const ToggleField = ({
options = [], options = [],
value, value,
onChange, onChange,
inputRef,
...props ...props
}) => ( }) => (
<Wrapper> <Wrapper>
@ -30,6 +31,7 @@ const ToggleField = ({
id={`${name}-${label}`} id={`${name}-${label}`}
checked={value === key} checked={value === key}
onChange={() => onChange(key)} onChange={() => onChange(key)}
ref={inputRef}
/> />
<LabelButton htmlFor={`${name}-${label}`}>{label}</LabelButton> <LabelButton htmlFor={`${name}-${label}`}>{label}</LabelButton>
</Option> </Option>

View file

@ -9,6 +9,7 @@ export const ToggleContainer = styled.div`
border: 1px solid ${props => props.theme.primary}; border: 1px solid ${props => props.theme.primary};
border-radius: 3px; border-radius: 3px;
overflow: hidden; overflow: hidden;
&:focus-within { &:focus-within {
outline: Highlight auto 1px; outline: Highlight auto 1px;
outline: -webkit-focus-ring-color auto 1px; outline: -webkit-focus-ring-color auto 1px;