Use a Radix dialog instead of dropdown, add OpenDyslexic font, prefetch fonts

The main change here is using Dialog instead of dropdown. The
dialog uses a fixed position, so when the underlying document
dimensions change based on a font change, the dialog will still
be positioned properly.
This commit is contained in:
Jackson Harper 2022-06-21 15:38:02 -07:00
parent bbae99dd7e
commit 99ec93449b
13 changed files with 128 additions and 114 deletions

View file

@ -39,10 +39,6 @@ export const ModalContent = styled(Modal, {
width: '90vw',
maxWidth: '600px',
maxHeight: '85vh',
'@media (prefers-reduced-motion: no-preference)': {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
willChange: 'transform',
},
'@smDown': {
maxWidth: '95%',
width: '95%',

View file

@ -7,7 +7,9 @@ import { Box, SpanBox } from "../../elements/LayoutPrimitives"
import { TooltipWrapped } from "../../elements/Tooltip"
import { styled, theme } from "../../tokens/stitches.config"
import { SetLabelsControl } from "./SetLabelsControl"
import { ReaderSettingsControl } from "./ReaderSettingsControl"
import { DisplaySettingsModal } from "./DisplaySettingsModal"
import { useReaderSettings } from "../../../lib/hooks/useReaderSettings"
import { useRef } from "react"
export type ArticleActionsMenuLayout = 'top' | 'side'
@ -56,6 +58,9 @@ const ActionDropdown = (props: ActionDropdownProps): JSX.Element => {
}
export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element {
const readerSettings = useReaderSettings()
const displaySettingsButtonRef = useRef<HTMLElement | null>(null)
return (
<>
<Box
@ -70,29 +75,19 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element
>
{props.showReaderDisplaySettings && (
<>
<ActionDropdown
layout={props.layout}
triggerElement={
<TooltipWrapped
tooltipContent="Adjust Display Settings"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<TextAa size={24} color={theme.colors.readerFont.toString()} />
</TooltipWrapped>
}
<Button style='articleActionIcon' onClick={() => readerSettings.setShowEditDisplaySettingsModal(true)}>
<TooltipWrapped
tooltipContent="Adjust Display Settings"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<ReaderSettingsControl
fontFamily={props.fontFamily}
lineHeight={props.lineHeight}
marginWidth={props.marginWidth}
articleActionHandler={props.articleActionHandler}
/>
</ActionDropdown>
<MenuSeparator layout={props.layout} />
<SpanBox ref={displaySettingsButtonRef}>
<TextAa size={24} color={theme.colors.readerFont.toString()} />
</SpanBox>
</TooltipWrapped>
</Button>
<MenuSeparator layout={props.layout} />
</>
)}
<SpanBox css={{
'display': 'flex',
'@smDown': {
@ -190,6 +185,17 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element
<DotsThree size={24} color={theme.colors.readerFont.toString()} />
</Button> */}
</Box>
{readerSettings.showEditDisplaySettingsModal && (
<DisplaySettingsModal
centerX={props.layout != 'side'}
triggerElementRef={displaySettingsButtonRef}
lineHeight={readerSettings.lineHeight}
marginWidth={readerSettings.marginWidth}
fontFamily={readerSettings.fontFamily}
articleActionHandler={props.articleActionHandler}
onOpenChange={() => readerSettings.setShowEditDisplaySettingsModal(false)}
/>
)}
</>
)
}

View file

@ -1,61 +1,39 @@
import { X } from 'phosphor-react'
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
import { UserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import { Button } from '../../elements/Button'
import { CrossIcon } from '../../elements/images/CrossIcon'
import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
import { VStack } from '../../elements/LayoutPrimitives'
import {
ModalRoot,
ModalOverlay,
ModalContent,
} from '../../elements/ModalPrimitives'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import { ReaderSettingsControl } from './ReaderSettingsControl'
type DisplaySettingsModalProps = {
centerX: boolean
onOpenChange: (open: boolean) => void
lineHeight: number
marginWidth: number
fontFamily: string
triggerElementRef?: React.RefObject<HTMLElement>
articleActionHandler: (action: string, arg?: number | string) => void
}
export function DisplaySettingsModal(props: DisplaySettingsModalProps): JSX.Element {
const top = props.triggerElementRef?.current?.getBoundingClientRect().bottom ?? 0
const left = props.triggerElementRef?.current?.getBoundingClientRect().left ?? 0
return (
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
<ModalOverlay />
<ModalContent
css={{ overflow: 'auto' }}
css={{
width: '245px',
top: props.triggerElementRef?.current ? top : '50%',
left: props.triggerElementRef?.current ? (left - (props.centerX ? 265 / 2 : 0)) : '50%',
transform: props.triggerElementRef?.current ? 'unset' : 'translate(-50%, -50%)',
}}
onPointerDownOutside={(event) => {
event.preventDefault()
props.onOpenChange(false)
}}
>
<VStack css={{ width: '100%' }}>
<HStack
distribution="between"
alignment="center"
css={{ width: '100%' }}
>
<StyledText style="modalHeadline" css={{ pl: '16px' }}>Labels</StyledText>
<Button
css={{ pt: '16px', pr: '16px' }}
style="ghost"
onClick={() => {
props.onOpenChange(false)
}}
>
<CrossIcon
size={14}
strokeColor={theme.colors.grayText.toString()}
/>
</Button>
</HStack>
<ReaderSettingsControl
lineHeight={props.lineHeight}
marginWidth={props.marginWidth}
fontFamily={props.fontFamily}
articleActionHandler={props.articleActionHandler}
/>
</VStack>

View file

@ -11,6 +11,7 @@ const FONT_FAMILIES = [
'Open Sans',
'Roboto',
'Crimson Text',
'OpenDyslexic',
'Source Serif Pro'
]

View file

@ -6,14 +6,10 @@ import { useEffect, useState } from 'react'
import { AlignCenterHorizontalSimple, ArrowsInLineHorizontal, ArrowsOutLineHorizontal, CaretRight } from 'phosphor-react'
import { TickedRangeSlider } from '../../elements/TickedRangeSlider'
import { showSuccessToast } from '../../../lib/toastHelpers'
import { FontStepperDown } from '../../elements/images/FontStepperDown'
import { FontStepperUp } from '../../elements/images/FontStepperUp'
import { FontFamiliesOptions } from './FontFamiliesOptions'
import { useReaderSettings } from '../../../lib/hooks/useReaderSettings'
type ReaderSettingsProps = {
marginWidth: number
lineHeight: number
fontFamily: string
articleActionHandler: (action: string, arg?: number | string) => void
}
@ -30,26 +26,17 @@ const HorizontalDivider = styled(SpanBox, {
})
export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
const [lineHeight, setLineHeight] = useState(props.lineHeight)
const [marginWidth, setMarginWidth] = useState(props.marginWidth)
const [fontFamily, setFontFamily] = useState(props.fontFamily)
const [showFontOptions, setShowFontOptions] = useState(false)
useEffect(() => {
setLineHeight(props.lineHeight)
setMarginWidth(props.marginWidth)
setFontFamily(props.fontFamily)
}, [props.lineHeight, props.marginWidth, props.fontFamily, setLineHeight, setMarginWidth, setFontFamily])
const readeringSettings = useReaderSettings()
return (
<VStack>
<VStack css={{ width: '100%' }}>
{showFontOptions ? (
<FontFamiliesOptions
selected={fontFamily}
selected={readeringSettings.fontFamily}
setShowFontFamilies={setShowFontOptions}
onSelect={(font: string) => {
setFontFamily(font)
readeringSettings.setFontFamily(font)
props.articleActionHandler('setFontFamily', font)
}}
/>
@ -60,17 +47,17 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
distribution='between'
css={{
width: '100%',
height: '70px',
marginTop: '4px',
height: '44px',
verticalAlign: 'baseline',
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
}}
>
<Button style='plainIcon' css={{ width: '50%' }} onClick={() => props.articleActionHandler('decrementFontSize')}>
<FontStepperDown color={theme.colors.readerFont.toString()} />
<Button style='plainIcon' css={{ width: '50%', fontSize: '32px' }} onClick={() => props.articleActionHandler('decrementFontSize')}>
-
</Button>
<VerticalDivider />
<Button style='plainIcon' css={{ width: '50%', height: '100%' }} onClick={() => props.articleActionHandler('incrementFontSize')}>
<FontStepperUp color={theme.colors.readerFont.toString()} />
<Button style='plainIcon' css={{ width: '50%', height: '100%', fontSize: '32px' }} onClick={() => props.articleActionHandler('incrementFontSize')}>
+
</Button>
</HStack>
<HStack
@ -81,7 +68,8 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
px: '12px',
py: '12px',
width: '100%',
height: '100%',
height: '44px',
verticalAlign: 'baseline'
}}
>
<StyledText css={{ m: '0px' }}>Font:</StyledText>
@ -91,9 +79,9 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
onClick={() => setShowFontOptions(true)}
>
<StyledText
css={{ m: '0px',fontSize: 17, fontWeight: '600', fontFamily: fontFamily, textTransform: 'capitalize' }}
css={{ m: '0px', fontFamily: readeringSettings.fontFamily, textTransform: 'capitalize' }}
>
{fontFamily}
{readeringSettings.fontFamily}
</StyledText>
<Box css={{ }}>
<CaretRight width={16} height={16} color={theme.colors.grayTextContrast.toString()}/>
@ -117,22 +105,22 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
},
}}
>
<StyledText color={theme.colors.readerFontTransparent.toString()} css={{ pl: '8px', m: '0px', pt: '14px' }}>Margin:</StyledText>
<StyledText color={theme.colors.readerFontTransparent.toString()} css={{ pl: '12px', m: '0px', pt: '14px' }}>Margin:</StyledText>
<HStack distribution='between' css={{ gap: '16px', alignItems: 'center', alignSelf: 'center' }}>
<Button style='plainIcon' css={{ pt: '10px', px: '4px' }} onClick={() => {
const newMarginWith = Math.max(marginWidth - 45, 200)
setMarginWidth(newMarginWith)
<Button style='plainIcon' css={{ pt: '10px', pl: '12px' }} onClick={() => {
const newMarginWith = Math.max(readeringSettings.marginWidth - 45, 200)
readeringSettings.setMarginWidth(newMarginWith)
props.articleActionHandler('setMarginWidth', newMarginWith)
}}>
<ArrowsOutLineHorizontal size={24} color={theme.colors.readerFont.toString()} />
</Button>
<TickedRangeSlider min={200} max={560} step={45} value={marginWidth} onChange={(value) => {
setMarginWidth(value)
<TickedRangeSlider min={200} max={560} step={45} value={readeringSettings.marginWidth} onChange={(value) => {
readeringSettings.setMarginWidth(value)
props.articleActionHandler('setMarginWidth', value)
}} />
<Button style='plainIcon' css={{ pt: '10px', px: '4px' }} onClick={() => {
const newMarginWith = Math.min(marginWidth + 45, 560)
setMarginWidth(newMarginWith)
<Button style='plainIcon' css={{ pt: '10px', pr: '12px' }} onClick={() => {
const newMarginWith = Math.min(readeringSettings.marginWidth + 45, 560)
readeringSettings.setMarginWidth(newMarginWith)
props.articleActionHandler('setMarginWidth', newMarginWith)
}}>
<ArrowsInLineHorizontal size={24} color={theme.colors.readerFont.toString()} />
@ -151,20 +139,20 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
}}>
<StyledText color={theme.colors.readerFontTransparent.toString()} css={{ pl: '12px', m: '0px', pt: '14px' }}>Line Spacing:</StyledText>
<HStack distribution='between' css={{ gap: '16px', alignItems: 'center', alignSelf: 'center' }}>
<Button style='plainIcon' css={{ pt: '10px', px: '4px' }} onClick={() => {
const newLineHeight = Math.max(lineHeight - 25, 100)
setLineHeight(newLineHeight)
<Button style='plainIcon' css={{ pt: '10px', pl: '12px' }} onClick={() => {
const newLineHeight = Math.max(readeringSettings.lineHeight - 25, 100)
readeringSettings.setLineHeight(newLineHeight)
props.articleActionHandler('setLineHeight', newLineHeight)
}}>
<AlignCenterHorizontalSimple size={25} color={theme.colors.readerFont.toString()} />
</Button>
<TickedRangeSlider min={100} max={300} step={25} value={lineHeight} onChange={(value) => {
setLineHeight(value)
<TickedRangeSlider min={100} max={300} step={25} value={readeringSettings.lineHeight} onChange={(value) => {
readeringSettings.setLineHeight(value)
props.articleActionHandler('setLineHeight', value)
}} />
<Button style='plainIcon' css={{ pt: '10px', px: '4px' }} onClick={() => {
const newLineHeight = Math.min(lineHeight + 25, 300)
setLineHeight(newLineHeight)
<Button style='plainIcon' css={{ pt: '10px', pr: '12px' }} onClick={() => {
const newLineHeight = Math.min(readeringSettings.lineHeight + 25, 300)
readeringSettings.setLineHeight(newLineHeight)
props.articleActionHandler('setLineHeight', newLineHeight)
}}>
<AlignCenterHorizontalSimple size={25} color={theme.colors.readerFont.toString()} />
@ -173,10 +161,11 @@ export function ReaderSettingsControl(props: ReaderSettingsProps): JSX.Element {
</VStack>
<HorizontalDivider />
<Button style='plainIcon' css={{ justifyContent: 'center', textDecoration: 'underline', display: 'flex', gap: '4px', width: '100%', fontSize: '12px', p: '8px', pb: '14px', pt: '16px', height: '42px', alignItems: 'center' }}
<Button style='plainIcon' css={{ justifyContent: 'center', textDecoration: 'underline', display: 'flex', gap: '4px', width: '100%', fontSize: '12px', p: '12px', pb: '14px', pt: '16px', height: '42px', alignItems: 'center' }}
onClick={() => {
setMarginWidth(290)
setLineHeight(150)
readeringSettings.setFontFamily('Inter')
readeringSettings.setMarginWidth(290)
readeringSettings.setLineHeight(150)
props.articleActionHandler('resetReaderSettings')
showSuccessToast('Display settings reset', { position: 'bottom-right' })
}}

View file

@ -223,6 +223,7 @@ type ArticleKeyboardAction =
| 'decrementFontSize'
| 'incrementMarginWidth'
| 'decrementMarginWidth'
| 'editDisplaySettings'
| 'setLabels'
export function articleKeyboardCommands(
@ -266,6 +267,12 @@ export function articleKeyboardCommands(
shortcutKeyDescription: '[',
callback: () => actionHandler('decrementMarginWidth'),
},
{
shortcutKeys: ['d'],
actionDescription: 'Edit Display Settings',
shortcutKeyDescription: 'd',
callback: () => actionHandler('editDisplaySettings'),
},
{
shortcutKeys: ['l'],
actionDescription: 'Edit labels',

View file

@ -255,12 +255,8 @@ export default function Home(): JSX.Element {
onOpenChange={() => readerSettings.setShowSetLabelsModal(false)}
/>
)}
{readerSettings.showEditDisplaySettingsModal && (
<DisplaySettingsModal
lineHeight={readerSettings.lineHeight}
marginWidth={readerSettings.marginWidth}
fontFamily={readerSettings.fontFamily}
articleActionHandler={actionHandler}
onOpenChange={() => readerSettings.setShowEditDisplaySettingsModal(false)}
/>

View file

@ -3,7 +3,6 @@
import { useEffect } from 'react'
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
import { getCssText, globalStyles } from '../components/tokens/stitches.config'
import { useRouter } from 'next/router'
export default class Document extends NextDocument {
render() {
@ -53,6 +52,27 @@ export default class Document extends NextDocument {
<link rel="manifest" href="/manifest.webmanifest" />
<script async src="/static/scripts/intercom.js" />
<script async src="/static/scripts/inject-sw.js" />
{/* prefetch (not preload) fonts that will be used by the reader */}
<link rel="prefetch" href='/static/fonts/Lora/Lora-Regular.ttf' />
<link rel="prefetch" href='/static/fonts/Lora/Lora-Bold.ttf' />
<link rel="prefetch" href='/static/fonts/Lora/Lora-Italic.ttf' />
<link rel="prefetch" href='/static/fonts/Merriweather/Merriweather-Regular.ttf' />
<link rel="prefetch" href='/static/fonts/Merriweather/Merriweather-Bold.ttf' />
<link rel="prefetch" href='/static/fonts/Merriweather/Merriweather-Italic.ttf' />
<link rel="prefetch" href='/static/fonts/Open_Sans/OpenSans-Regular.ttf' />
<link rel="prefetch" href='/static/fonts/Open_Sans/OpenSans-Bold.ttf' />
<link rel="prefetch" href='/static/fonts/Open_Sans/OpenSans-Italic.ttf' />
<link rel="prefetch" href='/static/fonts/Roboto/Roboto-Regular.ttf' />
<link rel="prefetch" href='/static/fonts/Roboto/Roboto-Bold.ttf' />
<link rel="prefetch" href='/static/fonts/Roboto/Roboto-Italic.ttf' />
<link rel="prefetch" href='/static/fonts/Crimson_Text/CrimsonText-Regular.ttf' />
<link rel="prefetch" href='/static/fonts/Crimson_Text/CrimsonText-Bold.ttf' />
<link rel="prefetch" href='/static/fonts/Crimson_Text/CrimsonText-Italic.ttf' />
<link rel="prefetch" href='/static/fonts/Source_Serif_Pro/SourceSerifPro-Regular.ttf' />
<link rel="prefetch" href='/static/fonts/Source_Serif_Pro/SourceSerifPro-Bold.ttf' />
<link rel="prefetch" href='/static/fonts/Source_Serif_Pro/SourceSerifPro-Italic.ttf' />
<link rel="prefetch" href='/static/fonts/SFMono/SFMonoRegular.otf' />
</Head>
<body>
<Main />

View file

@ -15,8 +15,8 @@ export default {
export const ReaderSettingsStory: ComponentStory<typeof ReaderSettingsControl> = (args: any) => {
return (
<div style={{ width: '265px', border: '2px solid black' }}>
<ReaderSettingsControl fontFamily='Inter' marginWidth={300} lineHeight={200} articleActionHandler={(action) => {
<div style={{ width: '245px', border: '2px solid black' }}>
<ReaderSettingsControl articleActionHandler={(action) => {
console.log('articleActionHandler')
}} />
</div>

View file

@ -186,6 +186,27 @@ div#appleid-signin {
src: url('/static/fonts/Crimson_Text/CrimsonText-Italic.ttf');
}
@font-face {
font-family: 'OpenDyslexic';
font-weight: 400;
font-style: normal;
src: url('/static/fonts/OpenDyslexic/OpenDyslexicAlta-Regular.otf');
}
@font-face {
font-family: 'OpenDyslexic';
font-weight: 700;
font-style: normal;
src: url('/static/fonts/OpenDyslexic/OpenDyslexicAlta-Bold.otf');
}
@font-face {
font-family: 'OpenDyslexic';
font-weight: 400;
font-style: italic;
src: url('/static/fonts/OpenDyslexic/OpenDyslexicAlta-Italic.otf');
}
@font-face {
font-family: 'Source Serif Pro';
font-weight: 400;