Files
omnivore/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx
2023-03-08 15:23:40 +08:00

355 lines
9.0 KiB
TypeScript

import {
Box,
VStack,
HStack,
SpanBox,
Blockquote,
} from '../../elements/LayoutPrimitives'
import { LabelChip } from '../../elements/LabelChip'
import type { LinkedItemCardProps } from './CardTypes'
import { CoverImage } from '../../elements/CoverImage'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { Fragment, useMemo, useState } from 'react'
import {
CaretCircleDown,
CaretDown,
CaretUp,
DotsThreeVertical,
} from 'phosphor-react'
import Link from 'next/link'
import { CardMenu } from '../CardMenu'
import {
AuthorInfoStyle,
DescriptionStyle,
MenuStyle,
MetaStyle,
TitleStyle,
} from './LibraryCardStyles'
import { Separator } from '../../elements/Separator'
import { styled } from '@stitches/react'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { StyledText } from '../../elements/StyledText'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import {
LibraryItem,
LibraryItemNode,
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { useRouter } from 'next/router'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { Button } from '../../elements/Button'
import { theme } from '../../tokens/stitches.config'
dayjs.extend(relativeTime)
const timeAgo = (date: string | undefined): string => {
if (!date) {
return ''
}
return dayjs(date).fromNow()
}
export const GridSeparator = styled(Box, {
height: '1px',
marginTop: '15px',
backgroundColor: '$thBorderColor',
})
type LibraryHighlightGridCardProps = {
viewer: UserBasicData
item: LibraryItemNode
}
// Component
export function LibraryHighlightGridCard(
props: LibraryHighlightGridCardProps
): JSX.Element {
const [isHovered, setIsHovered] = useState(false)
const [expanded, setExpanded] = useState(false)
const higlightCount = props.item.highlights?.length ?? 0
return (
<VStack
css={{
pl: '15px',
padding: '15px',
width: '100%',
height: '100%',
marginTop: '20px',
background: 'white',
borderRadius: '5px',
borderWidth: '1px',
borderStyle: 'solid',
borderColor: '$thBorderColor',
cursor: 'pointer',
bg: '$thBackground3',
}}
alignment="start"
distribution="start"
onMouseEnter={() => {
setIsHovered(true)
}}
onMouseLeave={() => {
setIsHovered(false)
}}
>
{!expanded && (
<HStack
css={{
...MetaStyle,
minHeight: '35px',
}}
distribution="start"
>
<Box>
{timeAgo(props.item.savedAt)}
{` `}
{props.item.wordsCount ?? 0 > 0
? `${Math.max(
1,
Math.round((props.item.wordsCount ?? 0) / 235)
)} min read`
: null}
{props.item.highlights?.length ?? 0 > 0
? `${props.item.highlights?.length} highlights`
: null}
</Box>
</HStack>
)}
<VStack
alignment="start"
distribution="start"
css={{ height: '100%', width: '100%' }}
>
<Box
css={{
...TitleStyle,
}}
>
{props.item.title}
</Box>
{expanded && (
<>
<GridSeparator css={{ width: '100%' }} />
<VStack
css={{ height: '100%', width: '100%' }}
distribution="start"
>
{(props.item.highlights ?? []).map((highlight) => (
<HighlightItemCard
key={highlight.id}
viewer={props.viewer}
item={props.item}
highlight={highlight}
/>
))}
</VStack>
</>
)}
<Box css={{ width: '100%' }}>
{!expanded ? (
<Button
css={{
mt: '30px',
display: 'flex',
gap: '5px',
px: '10px',
py: '6px',
fontSize: '11px',
fontFamily: '$inter',
fontWeight: '500',
bg: '$thBackground2',
color: '$thTextSubtle2',
alignItems: 'center',
border: '1px solid transparent',
}}
onClick={(event) => {
setExpanded(true)
event.preventDefault()
}}
>
{`View ${higlightCount} highlight${higlightCount > 1 ? 's' : ''}`}
<CaretDown
size={10}
weight="bold"
color={theme.colors.thHighContrast.toString()}
/>
</Button>
) : (
<Button
style="plainIcon"
css={{
width: '25px',
height: '25px',
bg: '$thBackground2',
pt: '2px',
borderRadius: '1000px',
}}
onClick={(event) => {
setExpanded(false)
event.preventDefault()
}}
>
<CaretUp size={15} weight="bold" color="#EC6A5E" />
</Button>
)}
</Box>
</VStack>
</VStack>
)
}
type HighlightItemCardProps = {
highlight: Highlight
viewer: UserBasicData | undefined
item: LibraryItemNode
}
const StyledQuote = styled(Blockquote, {
margin: '0px',
fontSize: '16px',
fontFamily: '$inter',
lineHeight: '1.50',
color: '$thHighContrast',
paddingLeft: '15px',
borderLeft: '2px solid $omnivoreCtaYellow',
})
function HighlightItemCard(props: HighlightItemCardProps): JSX.Element {
const router = useRouter()
const [hover, setHover] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const lines = useMemo(
() => props.highlight.quote.split('\n'),
[props.highlight.quote]
)
return (
<HStack
css={{ width: '100%', py: '20px', cursor: 'pointer' }}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<VStack
css={{
gap: '10px',
height: '100%',
width: '100%',
wordBreak: 'break-word',
overflow: 'clip',
}}
alignment="start"
distribution="start"
onClick={(event) => {
if (router && props.viewer) {
const dest = `/${props.viewer}/${props.item.slug}#${props.highlight.id}`
router.push(dest)
}
event.preventDefault()
}}
>
<StyledQuote>
<SpanBox css={{ p: '1px', borderRadius: '2px' }}>
{lines.map((line: string, index: number) => (
<Fragment key={index}>
{line}
{index !== lines.length - 1 && (
<>
<br />
<br />
</>
)}
</Fragment>
))}
</SpanBox>
</StyledQuote>
<Box css={{ display: 'block', pt: '16px' }}>
{props.highlight.labels?.map((label: Label, index: number) => (
<LabelChip
key={index}
text={label.name || ''}
color={label.color}
/>
))}
</Box>
<StyledText
css={{
borderRadius: '6px',
bg: '$thBackground4',
p: '10px',
width: '100%',
marginTop: '5px',
color: '$grayText',
}}
onClick={() => setIsEditing(true)}
>
{props.highlight.annotation
? props.highlight.annotation
: 'Add your notes...'}
</StyledText>
</VStack>
<SpanBox
css={{
marginLeft: 'auto',
width: '20px',
visibility: hover ? 'unset' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
<HighlightsMenu />
</SpanBox>
</HStack>
)
}
function HighlightsMenu(): JSX.Element {
return (
<Dropdown
triggerElement={
<Box
css={{
display: 'flex',
height: '20px',
width: '20px',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '1000px',
'&:hover': {
bg: '#898989',
},
}}
>
<DotsThreeVertical size={20} color="#EBEBEB" weight="bold" />
</Box>
}
>
<DropdownOption
onSelect={() => {
console.log('copy')
}}
title="Copy"
/>
<DropdownOption
onSelect={() => {
console.log('labels')
}}
title="Labels"
/>
<DropdownOption
onSelect={() => {
console.log('delete')
}}
title="Delete"
/>
</Dropdown>
)
}