WIP: article actoins and reader settings

This commit is contained in:
Jackson Harper
2022-04-08 15:26:18 -07:00
parent 32b08d1112
commit 78129a4a85
8 changed files with 299 additions and 54 deletions

View File

@ -65,6 +65,7 @@ const StyledLabel = styled(Label, {
})
export type DropdownAlignment = 'start' | 'end' | 'center'
export type DropdownSide = 'top' | 'right' | 'bottom' | 'left'
type DropdownProps = {
labelText?: string
@ -73,6 +74,8 @@ type DropdownProps = {
children: React.ReactNode
styledArrow?: boolean
align?: DropdownAlignment
side?: DropdownSide
sideOffset?: number
disabled?: boolean
css?: CSS
}
@ -108,8 +111,11 @@ export function Dropdown({
labelText,
showArrow = true,
disabled = false,
side = 'bottom',
sideOffset = 0,
css
}: DropdownProps): JSX.Element {
console.log('side', side)
return (
<Root modal={false}>
<DropdownTrigger disabled={disabled}>{triggerElement}</DropdownTrigger>
@ -119,6 +125,8 @@ export function Dropdown({
// remove focus from dropdown
;(document.activeElement as HTMLElement).blur()
}}
side={side}
sideOffset={sideOffset}
align={align ? align : 'center'}
>
{labelText && <StyledLabel>{labelText}</StyledLabel>}

View File

@ -0,0 +1,39 @@
import { Box, HStack } from './LayoutPrimitives'
import { styled, theme } from '../tokens/stitches.config'
type TickedRangeSliderProps = {
ticks?: number,
value: number,
onChange: (value: number) => void,
min?: number,
max?: number,
step?: number,
}
const Tick = styled(Box, {
background: theme.colors.grayBorderHover,
width: 2,
height: 8,
})
export function TickedRangeSlider({
ticks = 8,
min = 10,
max = 28,
step = 1,
value,
onChange,
} : TickedRangeSliderProps
): JSX.Element {
return (
<Box css={{zIndex: 2}}>
<input onChange={(e) => onChange(e.target.value as any)} value={value} type="range" min={min} max={max} step={step} className='slider'/>
<HStack distribution='between' css={{position: 'relative', bottom: 12.2, left: 2, zIndex: -1}}>
{[...Array(ticks)].map((val, idx) => <Tick key={`ticks-${idx}`} />)}
</HStack>
</Box>
)
}

View File

@ -0,0 +1,14 @@
type AIconProps = {
size: number
color: string
style?: React.CSSProperties
}
export function AIcon(props: AIconProps): JSX.Element {
return (
<svg style={props.style} width={props.size} height={props.size} viewBox={`0 0 20 20`} fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.5 18L11.75 5.25L5 18" stroke={props.color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M16.5133 14.25H6.98828" stroke={props.color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)
}

View File

@ -1,20 +1,26 @@
import { Separator } from "@radix-ui/react-separator"
import { ArchiveBox, DotsThree, HighlighterCircle, TagSimple, TextAa } from "phosphor-react"
import { Button } from "../../elements/Button"
import { Box } from "../../elements/LayoutPrimitives"
import { Dropdown } from "../../elements/DropdownElements"
import { Box, SpanBox } from "../../elements/LayoutPrimitives"
import { styled, theme } from "../../tokens/stitches.config"
import { ReaderSettings } from "./ReaderSettingsModal"
export type ArticleActionsMenuLayout = 'horizontal' | 'vertical'
type ArticleActionsMenuProps = {
layout: ArticleActionsMenuLayout
articleActionHandler: (action: string, arg?: number) => void
}
export function MenuSeparator(props: ArticleActionsMenuProps) {
type MenuSeparatorProps = {
layout: ArticleActionsMenuLayout
}
export function MenuSeparator(props: MenuSeparatorProps) {
const LineSeparator = styled(Separator, {
width: '100%',
margin: 0,
backgroundColor: 'red',
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
my: '8px',
})
@ -23,12 +29,12 @@ export function MenuSeparator(props: ArticleActionsMenuProps) {
export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element {
return (
<>
<Box
css={{
display: 'flex',
flexDirection: props.layout == 'vertical' ? 'column' : 'row',
alignItems: props.layout == 'vertical' ? 'flex-start' : 'center',
alignItems: 'center',
justifyContent: props.layout == 'vertical' ? 'center' : 'flex-end',
gap: props.layout == 'vertical' ? '8px' : '4px',
paddingTop: '6px',
@ -37,21 +43,40 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element
m: '0px',
}}
>
<Button style='articleActionIcon'>
<TextAa size={24} color={theme.colors.readerFont.toString()} />
</Button>
<Dropdown
side='right'
sideOffset={18}
align='start'
triggerElement={
<SpanBox css={{ width: '100%', marginLeft: 'auto', marginRight: 'auto' }}>
<TextAa size={24} color={theme.colors.readerFont.toString()} />
</SpanBox>
}
css={{ m: '0px', p: '0px', outlineStyle: 'solid', outlineWidth: '1px', outlineColor: theme.colors.grayLine.toString() }}
>
<ReaderSettings />
</Dropdown>
<MenuSeparator layout={props.layout} />
<Button style='articleActionIcon'>
<TagSimple size={24} color={theme.colors.readerFont.toString()} />
</Button>
<Dropdown
side='right'
triggerElement={
<SpanBox css={{ width: '100%', marginLeft: 'auto', marginRight: 'auto' }}>
<TagSimple size={24} color={theme.colors.readerFont.toString()} />
</SpanBox>
}
css={{ background: 'red' }}
>
{/* <EditLabelsModal> */}
</Dropdown>
<Button style='articleActionIcon'>
<Button style='articleActionIcon' onClick={() => props.articleActionHandler('showHighlights')}>
<HighlighterCircle size={24} color={theme.colors.readerFont.toString()} />
</Button>
<MenuSeparator layout={props.layout} />
<Button style='articleActionIcon'>
<Button style='articleActionIcon' onClick={() => props.articleActionHandler('archive')}>
<ArchiveBox size={24} color={theme.colors.readerFont.toString()} />
</Button>
{/* <MenuSeparator layout={props.layout} />
@ -60,5 +85,6 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element
<DotsThree size={24} color={theme.colors.readerFont.toString()} />
</Button> */}
</Box>
</>
)
}

View File

@ -15,7 +15,6 @@ import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticle
import { LabelChip } from '../../elements/LabelChip'
type EditLabelsModalProps = {
labels: Label[]
article: ArticleAttributes
onOpenChange: (open: boolean) => void
setLabels: (labels: Label[]) => void

View File

@ -0,0 +1,90 @@
import {
ModalRoot,
ModalOverlay,
ModalContent,
} from '../../elements/ModalPrimitives'
import { Box, HStack, VStack, Separator, SpanBox } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { CrossIcon } from '../../elements/images/CrossIcon'
import { CommentIcon } from '../../elements/images/CommentIcon'
import { TrashIcon } from '../../elements/images/TrashIcon'
import { styled, theme } from '../../tokens/stitches.config'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { HighlightView } from '../../patterns/HighlightView'
import { useCallback, useState } from 'react'
import { StyledTextArea } from '../../elements/StyledTextArea'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { AlignCenterHorizontalSimple, ArrowsInLineHorizontal, ArrowsOutLineHorizontal, Minus, Pen, Plus, Trash } from 'phosphor-react'
import { AIcon } from '../../elements/images/AIcon'
import { TickedRangeSlider } from '../../elements/TickedRangeSlider'
type ReaderSettingsProps = {
}
export function ReaderSettings(props: ReaderSettingsProps): JSX.Element {
const VerticalDivider = styled(SpanBox, {
width: '1px',
height: '100%',
background: `${theme.colors.grayLine.toString()}`,
})
return (
<VStack>
<HStack
alignment='center'
css={{
width: '265px',
height: '70px',
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
}}>
<Button style='plainIcon' onClick={() => {}}>
<AIcon size={28} color={theme.colors.readerFont.toString()} />
<Minus size={28} color={theme.colors.readerFont.toString()}/>
</Button>
<VerticalDivider />
<Button style='plainIcon' onClick={() => {}}>
<AIcon size={44} color={theme.colors.readerFont.toString()} />
<Plus size={28} color={theme.colors.readerFont.toString()} />
</Button>
</HStack>
<VStack css={{
p: '0px',
m: '0px',
pb: '8px',
width: '100%',
height: '100%',
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
}}>
<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' }}>
<ArrowsInLineHorizontal size={24} color={theme.colors.readerFont.toString()} />
</Button>
<TickedRangeSlider value={14} onChange={() => {}} />
<Button style='plainIcon' css={{ pt: '10px', px: '4px' }}>
<ArrowsOutLineHorizontal size={24} color={theme.colors.readerFont.toString()} />
</Button>
</HStack>
</VStack>
<VStack css={{
p: '0px',
m: '0px',
pb: '12px',
width: '100%',
height: '100%',
}}>
<StyledText color={theme.colors.readerFontTransparent.toString()} css={{ pl: '12px', m: '0px', pt: '14px' }}>Line Height:</StyledText>
<HStack distribution='between' css={{ gap: '16px', alignItems: 'center', alignSelf: 'center' }}>
<Button style='plainIcon' css={{ pt: '10px', px: '4px' }}>
<AlignCenterHorizontalSimple size={25} color={theme.colors.readerFont.toString()} />
</Button>
<TickedRangeSlider value={14} onChange={() => {}} />
<Button style='plainIcon' css={{ pt: '10px', px: '4px' }}>
<AlignCenterHorizontalSimple size={25} color={theme.colors.readerFont.toString()} />
</Button>
</HStack>
</VStack>
</VStack>
)
}

View File

@ -29,6 +29,8 @@ import { ArchiveBox, DotsThree, HighlighterCircle, TagSimple, TextAa } from 'pho
import { Separator } from '@radix-ui/react-separator'
import { Article } from '../../../components/templates/article/Article'
import { ArticleActionsMenu } from '../../../components/templates/article/ArticleActionsMenu'
import { HighlightsModal } from '../../../components/templates/article/HighlightsModal'
import { setLinkArchivedMutation } from '../../../lib/networking/mutations/setLinkArchivedMutation'
const MenuSeparator = styled(Separator, {
width: '100%',
@ -48,6 +50,7 @@ export default function Home(): JSX.Element {
const scrollRef = useRef<HTMLDivElement | null>(null)
const { slug } = router.query
const [showLabelsModal, setShowLabelsModal] = useState(false)
const [showHighlightsModal, setShowHighlightsModal] = useState(false)
// Populate data cache
const { viewerData } = useGetViewerQuery()
@ -73,33 +76,51 @@ export default function Home(): JSX.Element {
setMarginWidth(newMargin)
}
const actionHandler = async (action: string, arg?: number) => {
switch (action) {
case 'archive':
if (article) {
await setLinkArchivedMutation({
linkId: article.id,
archived: true,
})
// TODO: merge from article actions PR
// removeItemFromCache(cache, mutate, props.article.id)
router.push(`/home`)
}
break
case 'openOriginalArticle':
const url = article?.url
if (url) {
window.open(url, '_blank')
}
break
case 'showHighlights':
setShowHighlightsModal(true)
break
case 'incrementFontSize':
await updateFontSize(Math.min(fontSize + 2, 28))
break
case 'decrementFontSize':
await updateFontSize(Math.max(fontSize - 2, 10))
break
case 'incrementMarginWidth':
updateMarginWidth(Math.min(marginWidth + 50, 560))
break
case 'decrementMarginWidth':
updateMarginWidth(Math.max(marginWidth - 50, 200))
break
case 'editLabels':
if (viewerData?.me && isVipUser(viewerData?.me)) {
setShowLabelsModal(true)
}
break
}
};
useKeyboardShortcuts(
articleKeyboardCommands(router, async (action) => {
switch (action) {
case 'openOriginalArticle':
const url = article?.url
if (url) {
window.open(url, '_blank')
}
break
case 'incrementFontSize':
await updateFontSize(Math.min(fontSize + 2, 28))
break
case 'decrementFontSize':
await updateFontSize(Math.max(fontSize - 2, 10))
break
case 'incrementMarginWidth':
updateMarginWidth(Math.min(marginWidth + 50, 560))
break
case 'decrementMarginWidth':
updateMarginWidth(Math.max(marginWidth - 50, 200))
break
case 'editLabels':
if (viewerData?.me && isVipUser(viewerData?.me)) {
setShowLabelsModal(true)
}
break
}
actionHandler(action)
})
)
@ -124,19 +145,22 @@ export default function Home(): JSX.Element {
<Toaster />
<VStack distribution="between" alignment="center" css={{
position: 'fixed',
flexDirection: 'row-reverse',
top: '-120px',
left: 8,
height: '100%',
width: '48px',
'@lgDown': {
display: 'none',
},
}}
>
<ArticleActionsMenu layout='vertical' />
</VStack>
position: 'fixed',
flexDirection: 'row-reverse',
top: '-120px',
left: 8,
height: '100%',
width: '48px',
'@lgDown': {
display: 'none',
},
}}
>
<ArticleActionsMenu
layout='vertical'
articleActionHandler={actionHandler}
/>
</VStack>
{article.contentReader == 'PDF' ? (
<PdfArticleContainerNoSSR
article={article}
@ -170,6 +194,17 @@ export default function Home(): JSX.Element {
articleReadingProgressMutation,
}}
/>
</VStack>
)}
{showHighlightsModal && (
<HighlightsModal
highlights={article.highlights}
onOpenChange={() => setShowHighlightsModal(false)}
deleteHighlightAction={(highlightId: string) => {
// removeHighlightCallback(highlightId)
}}
/>
)}
{/* {showLabelsModal && (
<EditLabelsModal
labels={article.labels || []}
@ -182,8 +217,6 @@ export default function Home(): JSX.Element {
}}
/>
)} */}
</VStack>
)}
</PrimaryLayout>
)
}

View File

@ -99,3 +99,39 @@ div#appleid-signin {
border-bottom-color: transparent;
border-left-color: transparent;
}
.slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 1px;
background: var(--colors-grayBorderHover);
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px !important;
height: 16px;
border-radius: 50%;
background: var(--colors-utilityTextContrast);
cursor: pointer;
}
.slider::-moz-range-thumb {
-webkit-appearance: none;
width: 16px !important;
height: 16px;
border-radius: 50%;
background: var(--colors-utilityTextContrast);
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--colors-grayTextContrast);
}