Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Dual Scroll #4991

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/common/enums/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export const COMMENTS_COUNT = 40
export const TOOLBAR_FIXEDTOOLBAR_ID = 'toolbar/fixedToolbar/id'
export const COMMENT_FEED_ID_PREFIX = 'comment-feed-'
export const SUPPORT_TAB_PREFERENCE_KEY = 'support-tab-preference-key'
export const ARTICLE_DIGEST_AUTHOR_SIDEBAR_ID_PREFIX =
'article-digest-author-sidebar-'
6 changes: 2 additions & 4 deletions src/common/utils/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const unshiftConnections = ({
newData: any
path: string
}) => {
const { edges: oldEdges, pageInfo: oldPageInfo } = _get(oldData, path)
const { edges: oldEdges } = _get(oldData, path)
const {
edges: newEdges,
pageInfo: newPageInfo,
Expand All @@ -65,9 +65,7 @@ export const unshiftConnections = ({
...rest,
pageInfo: {
...newPageInfo,
endCursor: oldPageInfo.endCursor,
hasNextPage: oldPageInfo.hasNextPage,
},
edges: _uniqBy([...oldEdges, ...newEdges], (edge) => edge.node.id),
edges: _uniqBy([...newEdges, ...oldEdges], (edge) => edge.node.id),
})
}
10 changes: 9 additions & 1 deletion src/common/utils/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ type ToPathArgs =
* (works on SSR & CSR)
*/
export const toPath = (
args: ToPathArgs & { fragment?: string; search?: { [key: string]: string } }
args: ToPathArgs & {
cursor?: string
fragment?: string
search?: { [key: string]: string }
}
): {
href: string
} => {
Expand All @@ -132,6 +136,10 @@ export const toPath = (

if (args.collectionId) {
href = `${href}?collection=${args.collectionId}`

if (args.cursor) {
href = `${href}&cursor=${args.cursor}`
}
}

break
Expand Down
11 changes: 9 additions & 2 deletions src/components/ArticleDigest/AuthorSidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import classNames from 'classnames'
import gql from 'graphql-tag'

import { TEST_ID } from '~/common/enums'
import {
ARTICLE_DIGEST_AUTHOR_SIDEBAR_ID_PREFIX,
TEST_ID,
} from '~/common/enums'
import { capitalizeFirstLetter, toPath } from '~/common/utils'
import { LinkWrapper, ResponsiveImage } from '~/components'
import { UserDigest } from '~/components/UserDigest'
Expand All @@ -14,6 +17,7 @@ import styles from './styles.module.css'
export type ArticleDigestAuthorSidebarProps = {
article: ArticleDigestAuthorSidebarArticleFragment
collectionId?: string
cursor?: string
titleTextSize?: ArticleDigestTitleTextSize
titleColor?: 'greyDarker' | 'black'
showCover?: boolean
Expand Down Expand Up @@ -46,7 +50,7 @@ const fragments = {
export const ArticleDigestAuthorSidebar = ({
article,
collectionId,

cursor,
titleTextSize = 15,
titleColor = 'greyDarker',
imageSize = 'sm',
Expand All @@ -66,6 +70,7 @@ export const ArticleDigestAuthorSidebar = ({
page: 'articleDetail',
article,
collectionId,
cursor,
})

const headerClasses = classNames({
Expand All @@ -79,6 +84,7 @@ export const ArticleDigestAuthorSidebar = ({
<section
className={containerClasses}
data-test-id={TEST_ID.DIGEST_ARTICLE_AUTHOR_SIDEBAR}
id={`${ARTICLE_DIGEST_AUTHOR_SIDEBAR_ID_PREFIX}${article.id}`}
onClick={clickEvent}
>
<section className={styles.left}>
Expand All @@ -87,6 +93,7 @@ export const ArticleDigestAuthorSidebar = ({
article={article}
textSize={titleTextSize}
collectionId={collectionId}
cursor={cursor}
textWeight="normal"
is="h3"
/>
Expand Down
4 changes: 3 additions & 1 deletion src/components/ArticleDigest/Feed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type ArticleDigestFeedProps = {
Partial<ArticleDigestFeedArticlePrivateFragment>
header?: React.ReactNode
collectionId?: string
cursor?: string
excludesTimeStamp?: boolean // this is only for timestamp next to the profile
} & ArticleDigestFeedControls &
FooterActionsProps
Expand All @@ -45,7 +46,7 @@ const BaseArticleDigestFeed = ({
article,
header,
collectionId,

cursor,
hasHeader = true,
hasCircle = true,
hasAuthor = true,
Expand Down Expand Up @@ -125,6 +126,7 @@ const BaseArticleDigestFeed = ({
<ArticleDigestTitle
article={article}
collectionId={collectionId}
cursor={cursor}
textSize={16}
lineClamp={2}
onClick={onClick}
Expand Down
5 changes: 3 additions & 2 deletions src/components/ArticleDigest/Title/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type ArticleDigestTitleIs = 'h2' | 'h3'
type ArticleDigestTitleProps = {
article: ArticleDigestTitleArticleFragment
collectionId?: string

cursor?: string
textSize?: ArticleDigestTitleTextSize
textWeight?: ArticleDigestTitleTextWeight
lineClamp?: boolean | 1 | 2 | 3
Expand Down Expand Up @@ -46,7 +46,7 @@ const fragments = {
export const ArticleDigestTitle = ({
article,
collectionId,

cursor,
textSize = 16,
textWeight = 'medium',
lineClamp = true,
Expand All @@ -63,6 +63,7 @@ export const ArticleDigestTitle = ({
page: 'articleDetail',
article,
collectionId,
cursor,
})
const isBanned = state === 'banned'
const isArchived = state === 'archived'
Expand Down
36 changes: 36 additions & 0 deletions src/components/Interaction/DualScroll/EndOfResults/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import classNames from 'classnames'
import { FormattedMessage } from 'react-intl'

import { capitalizeFirstLetter } from '~/common/utils'

import styles from './styles.module.css'

type EndOfResultsProps = {
message?: React.ReactNode
spacingTop?: 'base' | 'xLoose'
}

const EndOfResults: React.FC<EndOfResultsProps> = ({
message,
spacingTop = 'xLoose',
}) => {
const containerClasses = classNames({
[styles.endOfResults]: true,
[styles[`spacingTop${capitalizeFirstLetter(spacingTop)}`]]: true,
})
return (
<section className={containerClasses}>
{typeof message === 'boolean' && message ? (
<FormattedMessage
defaultMessage="That's all"
id="B2As08"
description="src/components/Interaction/InfiniteScroll/EndOfResults/index.tsx"
/>
) : (
message
)}
</section>
)
}

export default EndOfResults
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.endOfResults {
@mixin flex-center-all;

padding-bottom: var(--sp40);
font-size: var(--text14);
color: var(--color-grey);
}

.spacingTopBase {
padding-top: var(--sp16);
}

.spacingTopXLoose {
padding-top: var(--sp32);
}
74 changes: 74 additions & 0 deletions src/components/Interaction/DualScroll/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect, useState } from 'react'

import { SpinnerBlock, useIntersectionObserver } from '~/components'

import EndOfResults from './EndOfResults'

interface Props {
hasNextPage: boolean
hasPreviousPage: boolean
loadMore: () => Promise<any>
loadPrevious: () => Promise<any>
loader?: React.ReactNode
eof?: React.ReactNode
eofSpacingTop?: 'base' | 'xLoose'
scrollableAncestor?: any
className?: string
}

export const DualScroll: React.FC<React.PropsWithChildren<Props>> = ({
hasNextPage,
hasPreviousPage,
loader = <SpinnerBlock />,
loadMore,
loadPrevious,
eof,
eofSpacingTop,
scrollableAncestor,
children,
className,
}) => {
const [isPrevLoading, setIsPrevLoading] = useState(false)

// Only use intersection observer for bottom loading
const bottomObserver = useIntersectionObserver()

// Handle bottom intersection
useEffect(() => {
if (bottomObserver.isIntersecting && hasNextPage) {
loadMore()
}
}, [bottomObserver.isIntersecting])

// Handle scroll for top loading
const handleScroll = async (event: React.UIEvent<HTMLDivElement>) => {
const element = event.currentTarget
if (element.scrollTop === 0 && hasPreviousPage) {
setIsPrevLoading(true)
await loadPrevious()
setIsPrevLoading(false)
}
}

return (
<div
onScroll={handleScroll}
style={{ overflow: 'auto' }}
className={className}
>
{/* Top loader */}
{hasPreviousPage && isPrevLoading ? loader : null}

{/* Main content */}
{children}

{/* Bottom loader */}
{hasNextPage && <span ref={bottomObserver.ref} />}
{hasNextPage && loader}

{!hasNextPage && eof && (
<EndOfResults message={eof} spacingTop={eofSpacingTop} />
)}
</div>
)
}
1 change: 1 addition & 0 deletions src/components/Interaction/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './Card'
export * from './DualScroll'
export * from './InfiniteScroll'
export * from './LinkWrapper'
21 changes: 19 additions & 2 deletions src/views/ArticleDetail/AuthorSidebar/Collection/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,35 @@ import gql from 'graphql-tag'
import { ArticleDigestSidebar } from '~/components'

export const AUTHOR_SIDEBAR_COLLECTION = gql`
query AuthorSidebarCollection($id: ID!, $after: String) {
query AuthorSidebarCollection(
$id: ID!
$before: String
$after: String
$includeBefore: Boolean
$includeAfter: Boolean
) {
node(input: { id: $id }) {
id
... on Collection {
id
title
description
articles(input: { first: 40, after: $after }) {
articles(
input: {
last: 20
before: $before
first: 20
after: $after
includeBefore: $includeBefore
includeAfter: $includeAfter
}
) {
totalCount
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
Expand Down
Loading
Loading