Make the web notebook UI better match the iOS UI
This commit is contained in:
@ -113,21 +113,6 @@ const textVariants = {
|
||||
textDecoration: 'underline',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
highlightTitleAndAuthor: {
|
||||
fontSize: '18px',
|
||||
fontStyle: 'italic',
|
||||
lineHeight: '22.5px',
|
||||
letterSpacing: '0.01em',
|
||||
margin: '0px',
|
||||
color: '$utilityTextSubtle',
|
||||
},
|
||||
highlightTitle: {
|
||||
fontSize: '14px',
|
||||
fontWeight: '400',
|
||||
lineHeight: '1.5',
|
||||
margin: '0px',
|
||||
color: '$omnivoreGray',
|
||||
},
|
||||
navLink: {
|
||||
m: 0,
|
||||
fontSize: '$1',
|
||||
|
||||
@ -8,7 +8,7 @@ type HighlightViewProps = {
|
||||
highlight: Highlight
|
||||
author?: string
|
||||
title?: string
|
||||
scrollToHighlight?: (arg: string) => void;
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
}
|
||||
|
||||
export function HighlightView(props: HighlightViewProps): JSX.Element {
|
||||
@ -21,19 +21,21 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
|
||||
margin: '0px 0px 0px 0px',
|
||||
fontSize: '18px',
|
||||
lineHeight: '27px',
|
||||
color: '$utilityTextDefault',
|
||||
cursor: 'pointer',
|
||||
color: '$grayText',
|
||||
padding: '0px 10px',
|
||||
borderLeft: '2px solid $omnivoreCtaYellow',
|
||||
})
|
||||
|
||||
return (
|
||||
<VStack css={{ width: '100%', boxSizing: 'border-box' }}>
|
||||
<StyledQuote onClick={() => {
|
||||
if (props.scrollToHighlight) {
|
||||
props.scrollToHighlight(props.highlight.id)
|
||||
}
|
||||
}}>
|
||||
{props.highlight.prefix}
|
||||
<SpanBox css={{ bg: 'rgb($highlightBackground)', p: '1px', borderRadius: '2px', }}>
|
||||
<StyledQuote
|
||||
onClick={() => {
|
||||
if (props.scrollToHighlight) {
|
||||
props.scrollToHighlight(props.highlight.id)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SpanBox css={{ p: '1px', borderRadius: '2px' }}>
|
||||
{lines.map((line: string, index: number) => (
|
||||
<Fragment key={index}>
|
||||
{line}
|
||||
@ -46,13 +48,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
|
||||
</Fragment>
|
||||
))}
|
||||
</SpanBox>
|
||||
{props.highlight.suffix}
|
||||
</StyledQuote>
|
||||
<Box css={{p: '24px', pt: '0', width: '100%', boxSizing: 'border-box'}}>
|
||||
{props.author && props.title &&(
|
||||
<StyledText style="highlightTitleAndAuthor">{props.title + props.author}</StyledText>
|
||||
)}
|
||||
</Box>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
@ -46,12 +46,15 @@ export function ArticleHeaderToolbar(
|
||||
return (
|
||||
<HStack distribution="between" alignment="center" css={{ gap: '$2' }}>
|
||||
{props.hasHighlights && (
|
||||
<Button style="plainIcon" onClick={() => {
|
||||
if (props.setShowHighlightsModal) {
|
||||
props.setShowHighlightsModal(true)
|
||||
}
|
||||
}}
|
||||
title="View all your highlights and notes">
|
||||
<Button
|
||||
style="plainIcon"
|
||||
onClick={() => {
|
||||
if (props.setShowHighlightsModal) {
|
||||
props.setShowHighlightsModal(true)
|
||||
}
|
||||
}}
|
||||
title="Notebook"
|
||||
>
|
||||
<CommentIcon
|
||||
size={24}
|
||||
strokeColor={theme.colors.grayTextContrast.toString()}
|
||||
|
||||
@ -4,7 +4,13 @@ import {
|
||||
ModalContent,
|
||||
ModalTitleBar,
|
||||
} from '../../elements/ModalPrimitives'
|
||||
import { Box, HStack, VStack, Separator, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
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'
|
||||
@ -13,20 +19,22 @@ import { TrashIcon } from '../../elements/images/TrashIcon'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import { HighlightView } from '../../patterns/HighlightView'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { StyledTextArea } from '../../elements/StyledTextArea'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { Pen, Trash } from 'phosphor-react'
|
||||
import { DotsThree, Pen, Trash } from 'phosphor-react'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
|
||||
type HighlightsModalProps = {
|
||||
highlights: Highlight[]
|
||||
scrollToHighlight?: (arg: string) => void;
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
deleteHighlightAction?: (highlightId: string) => void
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
|
||||
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] = useState<undefined | string>(undefined)
|
||||
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
|
||||
useState<undefined | string>(undefined)
|
||||
|
||||
return (
|
||||
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
|
||||
@ -39,7 +47,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
|
||||
css={{ overflow: 'auto', px: '24px' }}
|
||||
>
|
||||
<VStack distribution="start" css={{ height: '100%' }}>
|
||||
<ModalTitleBar title="All your highlights and notes" onOpenChange={props.onOpenChange} />
|
||||
<ModalTitleBar title="Notebook" onOpenChange={props.onOpenChange} />
|
||||
<Box css={{ overflow: 'auto', mt: '24px', width: '100%' }}>
|
||||
{props.highlights.map((highlight) => (
|
||||
<ModalHighlightView
|
||||
@ -47,7 +55,9 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
|
||||
highlight={highlight}
|
||||
showDelete={!!props.deleteHighlightAction}
|
||||
scrollToHighlight={props.scrollToHighlight}
|
||||
setShowConfirmDeleteHighlightId={setShowConfirmDeleteHighlightId}
|
||||
setShowConfirmDeleteHighlightId={
|
||||
setShowConfirmDeleteHighlightId
|
||||
}
|
||||
deleteHighlightAction={() => {
|
||||
if (props.deleteHighlightAction) {
|
||||
props.deleteHighlightAction(highlight.id)
|
||||
@ -56,31 +66,33 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
|
||||
/>
|
||||
))}
|
||||
{props.highlights.length === 0 && (
|
||||
<SpanBox css={{ textAlign: 'center', width: '100%', }}>
|
||||
<StyledText css={{ mb: '40px' }}>You have not added any highlights or notes to this document</StyledText>
|
||||
<SpanBox css={{ textAlign: 'center', width: '100%' }}>
|
||||
<StyledText css={{ mb: '40px' }}>
|
||||
You have not added any highlights or notes to this document
|
||||
</StyledText>
|
||||
</SpanBox>
|
||||
)}
|
||||
</Box>
|
||||
</VStack>
|
||||
</ModalContent>
|
||||
{showConfirmDeleteHighlightId ? (
|
||||
<ConfirmationModal
|
||||
message={'Are you sure you want to delete this highlight?'}
|
||||
onAccept={() => {
|
||||
if (props.deleteHighlightAction) {
|
||||
props.deleteHighlightAction(showConfirmDeleteHighlightId)
|
||||
}
|
||||
setShowConfirmDeleteHighlightId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
|
||||
icon={
|
||||
<TrashIcon
|
||||
size={40}
|
||||
strokeColor={theme.colors.grayTextContrast.toString()}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
message={'Are you sure you want to delete this highlight?'}
|
||||
onAccept={() => {
|
||||
if (props.deleteHighlightAction) {
|
||||
props.deleteHighlightAction(showConfirmDeleteHighlightId)
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
setShowConfirmDeleteHighlightId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
|
||||
icon={
|
||||
<TrashIcon
|
||||
size={40}
|
||||
strokeColor={theme.colors.grayTextContrast.toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</ModalRoot>
|
||||
)
|
||||
}
|
||||
@ -88,7 +100,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
|
||||
type ModalHighlightViewProps = {
|
||||
highlight: Highlight
|
||||
showDelete: boolean
|
||||
scrollToHighlight?: (arg: string) => void;
|
||||
scrollToHighlight?: (arg: string) => void
|
||||
deleteHighlightAction: () => void
|
||||
setShowConfirmDeleteHighlightId: (id: string | undefined) => void
|
||||
}
|
||||
@ -96,6 +108,72 @@ type ModalHighlightViewProps = {
|
||||
function ModalHighlightView(props: ModalHighlightViewProps): JSX.Element {
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
|
||||
const copyHighlight = useCallback(async () => {
|
||||
await navigator.clipboard.writeText(props.highlight.quote)
|
||||
}, [props.highlight])
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack>
|
||||
<SpanBox css={{ marginLeft: 'auto' }}>
|
||||
<Dropdown
|
||||
triggerElement={
|
||||
<DotsThree size={24} color={theme.colors.readerFont.toString()} />
|
||||
}
|
||||
>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
copyHighlight()
|
||||
}}
|
||||
title="Copy"
|
||||
/>
|
||||
{/* <DropdownOption onSelect={() => {}} title="Labels" /> */}
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
props.setShowConfirmDeleteHighlightId(props.highlight.id)
|
||||
}}
|
||||
title="Delete"
|
||||
/>
|
||||
</Dropdown>
|
||||
</SpanBox>
|
||||
|
||||
<HighlightView
|
||||
scrollToHighlight={props.scrollToHighlight}
|
||||
highlight={props.highlight}
|
||||
/>
|
||||
{!isEditing ? (
|
||||
<StyledText
|
||||
css={{
|
||||
borderRadius: '6px',
|
||||
bg: '$grayBase',
|
||||
p: '16px',
|
||||
width: '100%',
|
||||
marginTop: '16px',
|
||||
color: '$grayText',
|
||||
}}
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
{props.highlight.annotation ?? 'Add your notes...'}
|
||||
</StyledText>
|
||||
) : null}
|
||||
{isEditing && (
|
||||
<TextEditArea
|
||||
highlight={props.highlight}
|
||||
setIsEditing={setIsEditing}
|
||||
/>
|
||||
)}
|
||||
<SpanBox css={{ mt: '$2', mb: '$4' }} />
|
||||
</VStack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type TextEditAreaProps = {
|
||||
setIsEditing: (editing: boolean) => void
|
||||
highlight: Highlight
|
||||
}
|
||||
|
||||
const TextEditArea = (props: TextEditAreaProps): JSX.Element => {
|
||||
const [noteContent, setNoteContent] = useState(
|
||||
props.highlight.annotation ?? ''
|
||||
)
|
||||
@ -107,41 +185,33 @@ function ModalHighlightView(props: ModalHighlightViewProps): JSX.Element {
|
||||
[setNoteContent]
|
||||
)
|
||||
|
||||
const ButtonStack = (): JSX.Element => (
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="end"
|
||||
css={{ width: '100%', pt: '$2' }}
|
||||
>
|
||||
{props.showDelete && (
|
||||
<Button style="ghost" onClick={() => props.setShowConfirmDeleteHighlightId(props.highlight.id)}>
|
||||
<Trash width={18} height={18} color={theme.colors.grayText.toString()} />
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
)
|
||||
|
||||
const TextEditArea = (): JSX.Element => (
|
||||
<VStack css={{ width: '100%' }}>
|
||||
return (
|
||||
<VStack css={{ width: '100%' }} key="textEditor">
|
||||
<StyledTextArea
|
||||
css={{
|
||||
mx: '21px',
|
||||
my: '$3',
|
||||
width: '95%',
|
||||
p: '$1',
|
||||
minHeight: '$6',
|
||||
borderRadius: '6px',
|
||||
bg: '$grayBase',
|
||||
p: '16px',
|
||||
width: '100%',
|
||||
marginTop: '16px',
|
||||
resize: 'vertical',
|
||||
}}
|
||||
autoFocus
|
||||
placeholder={'Add your note here'}
|
||||
value={noteContent}
|
||||
onChange={handleNoteContentChange}
|
||||
maxLength={4000}
|
||||
value={noteContent}
|
||||
placeholder={'Add your notes...'}
|
||||
onChange={handleNoteContentChange}
|
||||
/>
|
||||
<HStack alignment="center" distribution="end" css={{ width: '100%' }}>
|
||||
<Button
|
||||
style="ctaPill"
|
||||
css={{ mr: '$2' }}
|
||||
onClick={() => setIsEditing(false)}
|
||||
onClick={() => {
|
||||
props.setIsEditing(false)
|
||||
setNoteContent(props.highlight.annotation ?? '')
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
@ -149,17 +219,4 @@ function ModalHighlightView(props: ModalHighlightViewProps): JSX.Element {
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack>
|
||||
<HighlightView scrollToHighlight={props.scrollToHighlight} highlight={props.highlight} />
|
||||
{props.highlight.annotation && !isEditing ? (
|
||||
<StyledText css={{ borderRadius: '6px', bg: '$grayBase', p: '16px', width: '100%' }}>{props.highlight.annotation}</StyledText>
|
||||
) : null}
|
||||
{isEditing ? <TextEditArea /> : <ButtonStack />}
|
||||
<Separator css={{ mt: '$2', mb: '$4', background: '$grayTextContrast' }} />
|
||||
</VStack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user