break linkedItemCard file into multiple files

This commit is contained in:
Satindar Dhillon
2022-03-28 21:59:10 -07:00
parent 20038d1d0c
commit 159791c017
7 changed files with 376 additions and 357 deletions

View File

@ -0,0 +1,30 @@
import { Box } from './../elements/LayoutPrimitives'
type ProgressBarProps = {
fillPercentage: number
fillColor: string
backgroundColor: string
borderRadius: string
}
export function ProgressBar(props: ProgressBarProps): JSX.Element {
return (
<Box
css={{
height: '4px',
width: '100%',
borderRadius: '$1',
overflow: 'hidden',
}}
>
<Box
css={{
height: '100%',
width: `${props.fillPercentage}%`,
backgroundColor: props.fillColor,
borderRadius: props.borderRadius,
}}
/>
</Box>
)
}

View File

@ -0,0 +1,22 @@
import { LayoutType } from '../../templates/homeFeed/HomeFeedContainer'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import type { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
export type LinkedItemCardAction =
| 'showDetail'
| 'showOriginal'
| 'archive'
| 'unarchive'
| 'delete'
| 'mark-read'
| 'mark-unread'
| 'share'
| 'snooze'
export type LinkedItemCardProps = {
item: LibraryItemNode
layout: LayoutType
viewer: UserBasicData
originText?: string
handleAction: (action: LinkedItemCardAction) => void
}

View File

@ -0,0 +1,188 @@
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { CoverImage } from '../../elements/CoverImage'
import { StyledText } from '../../elements/StyledText'
import { authoredByText } from '../ArticleSubtitle'
import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon'
import { theme } from '../../tokens/stitches.config'
import { CardMenu } from '../CardMenu'
import { LabelChip } from '../../elements/LabelChip'
import { ProgressBar } from '../../elements/ProgressBar'
import type { LinkedItemCardProps } from './CardTypes'
export function GridLinkedItemCard(props: LinkedItemCardProps): JSX.Element {
return (
<VStack
css={{
p: '$2',
pr: '8px',
height: '100%',
width: '100%',
maxWidth: '100%',
borderRadius: '6px',
cursor: 'pointer',
wordBreak: 'break-word',
overflow: 'clip',
border: '1px solid $grayBorder',
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
position: 'relative',
}}
alignment="start"
distribution="start"
onClick={() => {
props.handleAction('showDetail')
}}
>
<Box
css={{
position: 'absolute',
top: '1px',
left: '1px',
width: 'calc(100% - 2px)',
'& > div': {
borderRadius: '100vmax 100vmax 0 0',
},
}}
>
<ProgressBar
fillPercentage={props.item.readingProgressPercent}
fillColor={theme.colors.highlight.toString()}
backgroundColor={theme.colors.grayTextContrast.toString()}
borderRadius={
props.item.readingProgressPercent === 100 ? '0' : '0px 8px 8px 0px'
}
/>
</Box>
<VStack
distribution="start"
alignment="start"
css={{
px: '0px',
width: '100%',
pl: '$1',
}}
>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
p: '0px',
mr: '-12px',
mt: '15px',
display: 'grid',
gridTemplateColumns: '1fr 24px',
gridTemplateRows: '1fr',
}}
>
<CardTitle title={props.item.title} />
<Box
css={{ alignSelf: 'end', alignItems: 'start', height: '100%' }}
onClick={(e) => {
// This is here to prevent menu click events from bubbling
// up and causing us to "click" on the link item.
e.stopPropagation()
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
triggerElement={
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
}
actionHandler={props.handleAction}
/>
</Box>
</HStack>
<HStack alignment="start" distribution="between">
<StyledText style="caption" css={{ my: '0', mt: '-$2' }}>
{props.item.author && (
<SpanBox css={{ mr: '8px' }}>
{authoredByText(props.item.author)}
</SpanBox>
)}
<SpanBox css={{ textDecorationLine: 'underline' }}>
{props.originText}
</SpanBox>
</StyledText>
</HStack>
</VStack>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
pt: '$2',
px: '$1',
pr: '12px',
mt: '7px',
flexGrow: '1',
}}
>
<StyledText
css={{
m: 0,
py: '0px',
mr: '$2',
fontStyle: 'normal',
fontWeight: '400',
fontSize: '14px',
lineHeight: '125%',
color: '$grayTextContrast',
flexGrow: '4',
overflow: 'hidden',
display: '-webkit-box',
WebkitLineClamp: 5,
WebkitBoxOrient: 'vertical',
}}
>
{props.item.description}
</StyledText>
{props.item.image && (
<CoverImage
src={props.item.image}
alt="Link Preview Image"
width={135}
height={90}
css={{ ml: '10px', mb: '8px', borderRadius: '3px' }}
onError={(e) => {
;(e.target as HTMLElement).style.display = 'none'
}}
/>
)}
</HStack>
<HStack css={{ mt: '8px' }}>
{props.item.labels?.map(({ description, color }, index) => (
<LabelChip key={index} text={description || ''} color={color} />
))}
</HStack>
</VStack>
)
}
type CardTitleProps = {
title: string
}
function CardTitle(props: CardTitleProps): JSX.Element {
return (
<StyledText
style="listTitle"
css={{
mt: '0',
mb: '0',
fontWeight: '700',
textAlign: 'left',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
width: '100%',
overflow: 'hidden',
}}
>
{props.title}
</StyledText>
)
}

View File

@ -0,0 +1,23 @@
import { GridLinkedItemCard } from './GridLinkedItemCard'
import { ListLinkedItemCard } from './ListLinkedItemCard'
import type { LinkedItemCardProps } from './CardTypes'
const siteName = (originalArticleUrl: string, itemUrl: string): string => {
try {
return new URL(originalArticleUrl).hostname
} catch {}
try {
return new URL(itemUrl).hostname
} catch {}
return ''
}
export function LinkedItemCard(props: LinkedItemCardProps): JSX.Element {
const originText = siteName(props.item.originalArticleUrl, props.item.url)
if (props.layout == 'LIST_LAYOUT') {
return <ListLinkedItemCard {...props} originText={originText} />
} else {
return <GridLinkedItemCard {...props} originText={originText} />
}
}

View File

@ -0,0 +1,104 @@
import { Box, HStack } from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { authoredByText } from '../ArticleSubtitle'
import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon'
import { theme } from '../../tokens/stitches.config'
import { CardMenu } from '../CardMenu'
import type { LinkedItemCardProps } from './CardTypes'
import { ProgressBar } from '../../elements/ProgressBar'
export function ListLinkedItemCard(props: LinkedItemCardProps): JSX.Element {
return (
<HStack
css={{
p: '$3',
height: '100%',
width: '100%',
maxWidth: '100%',
borderRadius: 0,
cursor: 'pointer',
wordBreak: 'break-word',
border: '1px solid $grayBorder',
borderBottom: 'none',
alignItems: 'center',
}}
onClick={() => {
props.handleAction('showDetail')
}}
>
<HStack
distribution="start"
alignment="end"
css={{
px: '$2',
flexGrow: 1,
pl: '0px',
}}
>
<StyledText
style="listTitle"
css={{ mt: '0px', mb: '$1', textAlign: 'left', lineHeight: 'normal' }}
>
{props.item.title}
</StyledText>
{props.item.author && (
<StyledText style="caption" css={{ my: '$1', ml: '8px' }}>
{authoredByText(props.item.author)}
</StyledText>
)}
<StyledText
style="caption"
css={{ my: '$1', ml: '8px', textDecorationLine: 'underline' }}
>
{props.originText}
</StyledText>
</HStack>
<Box
css={{
width: '40px',
height: '8px',
mr: '$2',
backgroundColor: '$grayBase',
display: 'grid',
placeItems: 'center',
borderRadius: '6px',
border: '1px solid $grayBorder',
px: '1px',
}}
>
<ProgressBar
fillPercentage={props.item.readingProgressPercent}
fillColor={theme.colors.highlight.toString()}
backgroundColor={theme.colors.grayTextContrast.toString()}
borderRadius={'8px'}
/>
</Box>
<Box
css={{
alignSelf: 'end',
alignItems: 'center',
display: 'grid',
placeItems: 'center',
}}
onClick={(e) => {
// This is here to prevent menu click events from bubbling
// up and causing us to "click" on the link item.
e.stopPropagation()
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
triggerElement={
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
}
actionHandler={props.handleAction}
/>
</Box>
</HStack>
)
}

View File

@ -1,348 +0,0 @@
import { Box, VStack, HStack, SpanBox } from './../elements/LayoutPrimitives'
import type { LibraryItemNode } from '../../lib/networking/queries/useGetLibraryItemsQuery'
import { CoverImage } from './../elements/CoverImage'
import { StyledText } from './../elements/StyledText'
import { authoredByText } from './../patterns/ArticleSubtitle'
import { MoreOptionsIcon } from './../elements/images/MoreOptionsIcon'
import { theme } from './../tokens/stitches.config'
import { CardMenu } from './../patterns/CardMenu'
import { LayoutType } from '../templates/homeFeed/HomeFeedContainer'
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
import { LabelChip } from './../elements/LabelChip'
export type LinkedItemCardAction =
| 'showDetail'
| 'showOriginal'
| 'archive'
| 'unarchive'
| 'delete'
| 'mark-read'
| 'mark-unread'
| 'share'
| 'snooze'
type LinkedItemCardProps = {
item: LibraryItemNode
layout: LayoutType
viewer: UserBasicData
handleAction: (action: LinkedItemCardAction) => void
}
const siteName = (originalArticleUrl: string, itemUrl: string): string => {
try {
return new URL(originalArticleUrl).hostname
} catch {}
try {
return new URL(itemUrl).hostname
} catch {}
return ''
}
export function LinkedItemCard(props: LinkedItemCardProps): JSX.Element {
if (props.layout == 'LIST_LAYOUT') {
return <ListLinkedItemCard {...props} />
} else {
return <GridLinkedItemCard {...props} />
}
}
export function GridLinkedItemCard(props: LinkedItemCardProps): JSX.Element {
const originText = siteName(props.item.originalArticleUrl, props.item.url)
return (
// <Link href={`/${username}/${props.item.slug}`} passHref={true}>
<VStack
css={{
p: '$2',
pr: '8px',
height: '100%',
width: '100%',
maxWidth: '100%',
borderRadius: '6px',
cursor: 'pointer',
wordBreak: 'break-word',
overflow: 'clip',
border: '1px solid $grayBorder',
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
position: 'relative',
}}
alignment="start"
distribution="start"
onClick={() => {
props.handleAction('showDetail')
}}
>
<Box
css={{
position: 'absolute',
top: '1px',
left: '1px',
width: 'calc(100% - 2px)',
'& > div': {
borderRadius: '100vmax 100vmax 0 0',
},
}}
>
<ProgressBar
fillPercentage={props.item.readingProgressPercent}
fillColor={theme.colors.highlight.toString()}
backgroundColor={theme.colors.grayTextContrast.toString()}
borderRadius={
props.item.readingProgressPercent === 100 ? '0' : '0px 8px 8px 0px'
}
/>
</Box>
<VStack
distribution="start"
alignment="start"
css={{
px: '0px',
width: '100%',
pl: '$1',
}}
>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
p: '0px',
mr: '-12px',
mt: '15px',
display: 'grid',
gridTemplateColumns: '1fr 24px',
gridTemplateRows: '1fr',
}}
>
<StyledText
style="listTitle"
css={{
mt: '0',
mb: '0',
fontWeight: '700',
textAlign: 'left',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
width: '100%',
overflow: 'hidden',
}}
>
{props.item.title}
</StyledText>
<Box
css={{ alignSelf: 'end', alignItems: 'start', height: '100%' }}
onClick={(e) => {
// This is here to prevent menu click events from bubbling
// up and causing us to "click" on the link item.
e.stopPropagation()
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
triggerElement={
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
}
actionHandler={props.handleAction}
/>
</Box>
</HStack>
<HStack alignment="start" distribution="between">
<StyledText style="caption" css={{ my: '0', mt: '-$2' }}>
{props.item.author && (
<SpanBox css={{ mr: '8px' }}>
{authoredByText(props.item.author)}
</SpanBox>
)}
<SpanBox css={{ textDecorationLine: 'underline' }}>
{originText}
</SpanBox>
</StyledText>
</HStack>
</VStack>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
pt: '$2',
px: '$1',
pr: '12px',
mt: '7px',
flexGrow: '1',
}}
>
<StyledText
css={{
m: 0,
py: '0px',
mr: '$2',
fontStyle: 'normal',
fontWeight: '400',
fontSize: '14px',
lineHeight: '125%',
color: '$grayTextContrast',
flexGrow: '4',
overflow: 'hidden',
display: '-webkit-box',
WebkitLineClamp: 5,
WebkitBoxOrient: 'vertical',
}}
>
{props.item.description}
</StyledText>
{props.item.image && (
<CoverImage
src={props.item.image}
alt="Link Preview Image"
width={135}
height={90}
css={{ ml: '10px', mb: '8px', borderRadius: '3px' }}
onError={(e) => {
;(e.target as HTMLElement).style.display = 'none'
}}
/>
)}
</HStack>
<HStack css={{ mt: '8px' }}>
{props.item.labels?.map(({ description, color }, index) => (
<LabelChip key={index} text={description || ''} color={color} />
))}
</HStack>
</VStack>
// </Link>
)
}
export function ListLinkedItemCard(props: LinkedItemCardProps): JSX.Element {
const originText = siteName(props.item.originalArticleUrl, props.item.url)
return (
// <Link href={`/${username}/${props.item.slug}`} passHref={true}>
<HStack
css={{
p: '$3',
height: '100%',
width: '100%',
maxWidth: '100%',
borderRadius: 0,
cursor: 'pointer',
wordBreak: 'break-word',
border: '1px solid $grayBorder',
borderBottom: 'none',
alignItems: 'center',
}}
onClick={() => {
props.handleAction('showDetail')
}}
>
<HStack
distribution="start"
alignment="end"
css={{
px: '$2',
flexGrow: 1,
pl: '0px',
}}
>
<StyledText
style="listTitle"
css={{ mt: '0px', mb: '$1', textAlign: 'left', lineHeight: 'normal' }}
>
{props.item.title}
</StyledText>
{props.item.author && (
<StyledText style="caption" css={{ my: '$1', ml: '8px' }}>
{authoredByText(props.item.author)}
</StyledText>
)}
<StyledText
style="caption"
css={{ my: '$1', ml: '8px', textDecorationLine: 'underline' }}
>
{originText}
</StyledText>
</HStack>
<Box
css={{
width: '40px',
height: '8px',
mr: '$2',
backgroundColor: '$grayBase',
display: 'grid',
placeItems: 'center',
borderRadius: '6px',
border: '1px solid $grayBorder',
px: '1px',
}}
>
<ProgressBar
fillPercentage={props.item.readingProgressPercent}
fillColor={theme.colors.highlight.toString()}
backgroundColor={theme.colors.grayTextContrast.toString()}
borderRadius={'8px'}
/>
</Box>
<Box
css={{
alignSelf: 'end',
alignItems: 'center',
display: 'grid',
placeItems: 'center',
}}
onClick={(e) => {
// This is here to prevent menu click events from bubbling
// up and causing us to "click" on the link item.
e.stopPropagation()
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
triggerElement={
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
}
actionHandler={props.handleAction}
/>
</Box>
</HStack>
// </Link>
)
}
type ProgressBarProps = {
fillPercentage: number
fillColor: string
backgroundColor: string
borderRadius: string
}
function ProgressBar(props: ProgressBarProps): JSX.Element {
return (
<Box
css={{
height: '4px',
width: '100%',
borderRadius: '$1',
overflow: 'hidden',
}}
>
<Box
css={{
height: '100%',
width: `${props.fillPercentage}%`,
backgroundColor: props.fillColor,
borderRadius: props.borderRadius,
}}
/>
</Box>
)
}

View File

@ -5,10 +5,8 @@ import type {
LibraryItem,
LibraryItemsQueryInput,
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import {
LinkedItemCard,
LinkedItemCardAction,
} from '../../patterns/LinkedItemCard'
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
import { useRouter } from 'next/router'
import { Button } from '../../elements/Button'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -573,13 +571,15 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
},
'&:focus': {
'> div': {
boxShadow: layout === 'LIST_LAYOUT' ? 'none' : '$cardItemShadow',
}
boxShadow:
layout === 'LIST_LAYOUT' ? 'none' : '$cardItemShadow',
},
},
'&:hover': {
'> div': {
boxShadow: layout === 'LIST_LAYOUT' ? 'none' : '$cardItemShadow',
}
boxShadow:
layout === 'LIST_LAYOUT' ? 'none' : '$cardItemShadow',
},
},
}}
>
@ -592,7 +592,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
props.actionHandler(action, linkedItem)
}}
/>
)}
)}
</Box>
))}
</Box>