diff --git a/packages/web/components/elements/ProgressBar.tsx b/packages/web/components/elements/ProgressBar.tsx new file mode 100644 index 000000000..c8d15b868 --- /dev/null +++ b/packages/web/components/elements/ProgressBar.tsx @@ -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 ( + + + + ) +} diff --git a/packages/web/components/patterns/ArticleSubtitle.tsx b/packages/web/components/patterns/ArticleSubtitle.tsx index 811f028f6..96fe5e15e 100644 --- a/packages/web/components/patterns/ArticleSubtitle.tsx +++ b/packages/web/components/patterns/ArticleSubtitle.tsx @@ -51,7 +51,7 @@ function articleSubtitle(url: string, author?: string): string { } export function authoredByText(author: string): string { - return `By ${removeHTMLTags(author)}` + return `by ${removeHTMLTags(author)}` } function removeHTMLTags(str: string | null | undefined): string { diff --git a/packages/web/components/patterns/LibraryCards/CardTypes.tsx b/packages/web/components/patterns/LibraryCards/CardTypes.tsx new file mode 100644 index 000000000..6fd207761 --- /dev/null +++ b/packages/web/components/patterns/LibraryCards/CardTypes.tsx @@ -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 +} diff --git a/packages/web/components/patterns/LibraryCards/GridLinkedItemCard.tsx b/packages/web/components/patterns/LibraryCards/GridLinkedItemCard.tsx new file mode 100644 index 000000000..bb60b4c37 --- /dev/null +++ b/packages/web/components/patterns/LibraryCards/GridLinkedItemCard.tsx @@ -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 ( + { + props.handleAction('showDetail') + }} + > + div': { + borderRadius: '100vmax 100vmax 0 0', + }, + }} + > + + + + + + { + // This is here to prevent menu click events from bubbling + // up and causing us to "click" on the link item. + e.stopPropagation() + }} + > + + } + actionHandler={props.handleAction} + /> + + + + + {props.item.author && ( + + {authoredByText(props.item.author)} + + )} + + {props.originText} + + + + + + + {props.item.description} + + {props.item.image && ( + { + ;(e.target as HTMLElement).style.display = 'none' + }} + /> + )} + + + {props.item.labels?.map(({ description, color }, index) => ( + + ))} + + + ) +} + +type CardTitleProps = { + title: string +} + +function CardTitle(props: CardTitleProps): JSX.Element { + return ( + + {props.title} + + ) +} diff --git a/packages/web/components/patterns/LibraryCards/LinkedItemCard.tsx b/packages/web/components/patterns/LibraryCards/LinkedItemCard.tsx new file mode 100644 index 000000000..12a4a4be7 --- /dev/null +++ b/packages/web/components/patterns/LibraryCards/LinkedItemCard.tsx @@ -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 + } else { + return + } +} diff --git a/packages/web/components/patterns/LibraryCards/ListLinkedItemCard.tsx b/packages/web/components/patterns/LibraryCards/ListLinkedItemCard.tsx new file mode 100644 index 000000000..98a4af2f8 --- /dev/null +++ b/packages/web/components/patterns/LibraryCards/ListLinkedItemCard.tsx @@ -0,0 +1,213 @@ +import { + Box, + HStack, + VStack, + MediumBreakpointBox, +} 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 ( + } + largerLayoutNode={} + /> + ) +} + +export function ListLinkedItemCardNarrow( + props: LinkedItemCardProps +): JSX.Element { + return ( + { + props.handleAction('showDetail') + }} + > + + + + {props.item.title} + + + {props.item.author && ( + + {authoredByText(props.item.author)} + + )} + + {props.originText} + + + + + { + // This is here to prevent menu click events from bubbling + // up and causing us to "click" on the link item. + e.stopPropagation() + }} + > + + } + actionHandler={props.handleAction} + /> + + + ) +} + +export function ListLinkedItemCardWide( + props: LinkedItemCardProps +): JSX.Element { + return ( + { + props.handleAction('showDetail') + }} + > + + + {props.item.title} + + {props.item.author && ( + + {authoredByText(props.item.author)} + + )} + + {props.originText} + + + + + + { + // This is here to prevent menu click events from bubbling + // up and causing us to "click" on the link item. + e.stopPropagation() + }} + > + + } + actionHandler={props.handleAction} + /> + + + ) +} diff --git a/packages/web/components/patterns/LinkedItemCard.tsx b/packages/web/components/patterns/LinkedItemCard.tsx deleted file mode 100644 index 1dc194d36..000000000 --- a/packages/web/components/patterns/LinkedItemCard.tsx +++ /dev/null @@ -1,270 +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' - -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 - } else { - return - } -} - -export function GridLinkedItemCard(props: LinkedItemCardProps): JSX.Element { - const originText = siteName(props.item.originalArticleUrl, props.item.url) - - return ( - // - { - props.handleAction('showDetail') - }} - > - - - - {originText} - - { - // This is here to prevent menu click events from bubbling - // up and causing us to "click" on the link item. - e.stopPropagation() - }} - > - - } - actionHandler={props.handleAction} - /> - - - - {props.item.title} - - - - - {props.item.author && ( - - {authoredByText(props.item.author)} - {props.item.description ? ' \u2013 ' : ''} - - )} - {props.item.description?.substring(0, 300)} - - {props.item.image && ( - { - ;(e.target as HTMLElement).style.display = 'none' - }} - /> - )} - - - - // - ) -} - -export function ListLinkedItemCard(props: LinkedItemCardProps): JSX.Element { - const originText = siteName(props.item.originalArticleUrl, props.item.url) - - return ( - // - { - props.handleAction('showDetail') - }} - > - - - - {props.item.title} - - {props.item.author && ( - - {authoredByText(props.item.author)} - - )} - - {originText} - - - { - // This is here to prevent menu click events from bubbling - // up and causing us to "click" on the link item. - e.stopPropagation() - }} - > - - } - actionHandler={props.handleAction} - /> - - - - - // - ) -} - -type ProgressBarProps = { - fillPercentage: number - fillColor: string - backgroundColor: string -} - -function ProgressBar(props: ProgressBarProps): JSX.Element { - return ( - - - - ) -} diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index fb55e6910..b25ce4480 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -1,14 +1,12 @@ import { Box, HStack, VStack } from './../../elements/LayoutPrimitives' +import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery' +import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery' import type { LibraryItem, LibraryItemsQueryInput, } from '../../../lib/networking/queries/useGetLibraryItemsQuery' -import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery' -import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery' -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' @@ -115,6 +113,13 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element { setSize(size + 1) }, [size, isValidating]) + useEffect(() => { + if (isValidating || !hasMore || size !== 1) { + return + } + setSize(size + 1) + }, [size, isValidating]) + const focusFirstItem = useCallback(() => { if (libraryItems.length < 1) { return @@ -433,6 +438,7 @@ type HomeFeedContentProps = { function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { const { viewerData } = useGetViewerQuery() + const { preferencesData, isValidating: isValidatingPreferences } = useGetUserPreferences() const [layout, setLayout] = useState( @@ -553,19 +559,15 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { gridAutoRows: 'auto', borderRadius: '8px', gridGap: layout == 'LIST_LAYOUT' ? '0' : '$3', - border: - props.hasData && layout == 'LIST_LAYOUT' - ? '1px solid $grayBorder' - : 'none', marginTop: layout == 'LIST_LAYOUT' ? '21px' : '0', marginBottom: '0px', - paddingTop: layout == 'LIST_LAYOUT' ? '2px' : '21px', + paddingTop: layout == 'LIST_LAYOUT' ? '0' : '21px', paddingBottom: layout == 'LIST_LAYOUT' ? '0px' : '21px', - overflow: 'visible', + overflow: 'hidden', '@smDown': { border: 'unset', width: layout == 'LIST_LAYOUT' ? '100vw' : undefined, - margin: layout == 'LIST_LAYOUT' ? '0 -16px' : undefined, + margin: layout == 'LIST_LAYOUT' ? '16px -16px' : undefined, borderRadius: layout == 'LIST_LAYOUT' ? 0 : undefined, }, '@md': { @@ -577,24 +579,6 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { }, }} > - {props.hasData && layout === 'LIST_LAYOUT' && ( - // list view gets a title - - - {props.totalItems} links - - - )} {props.items.map((linkedItem) => ( div': { bg: '$grayBg', }, diff --git a/packages/web/components/tokens/stitches.config.ts b/packages/web/components/tokens/stitches.config.ts index 6e78f741f..7e9500fcf 100644 --- a/packages/web/components/tokens/stitches.config.ts +++ b/packages/web/components/tokens/stitches.config.ts @@ -102,7 +102,8 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } = borderStyles: {}, shadows: { panelShadow: '0px 4px 18px rgba(120, 123, 134, 0.12)', - cardBoxShadow: '0px 0px 9px -2px rgba(32, 31, 29, 0.09), 0px 7px 12px rgba(32, 31, 29, 0.07)' + cardBoxShadow: + '0px 0px 9px -2px rgba(32, 31, 29, 0.09), 0px 7px 12px rgba(32, 31, 29, 0.07)', }, zIndices: {}, transitions: {}, @@ -148,7 +149,7 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } = avatarFont: '#0A0806', labelButtonsBg: '#F5F5F4', - tooltipIcons: '#FDFAEC' + tooltipIcons: '#FDFAEC', }, }, media: { @@ -197,8 +198,9 @@ const darkThemeSpec = { labelButtonsBg: '#5F5E58', }, shadows: { - cardBoxShadow: '0px 0px 9px -2px rgba(32, 31, 29, 0.09), 0px 7px 12px rgba(32, 31, 29, 0.07)' - } + cardBoxShadow: + '0px 0px 9px -2px rgba(32, 31, 29, 0.09), 0px 7px 12px rgba(32, 31, 29, 0.07)', + }, } // Avatar Fallback color