Skip to content
Closed
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
3 changes: 1 addition & 2 deletions packages/components/src/CommentItem/CommentItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ export const CommentItem = (props: CommentItemProps) => {
}

return (
<Box data-cy="comment">
<Box id={`comment:${_id}`} data-cy="comment">
<Flex
p="3"
bg={'white'}
mb={4}
sx={{
width: '100%',
flexDirection: 'column',
Expand Down
60 changes: 39 additions & 21 deletions packages/components/src/CommentList/CommentList.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
import type { ComponentStory, ComponentMeta } from '@storybook/react'
import { CommentList } from './CommentList'
import { faker } from '@faker-js/faker'

export default {
title: 'Components/CommentList',
component: CommentList,
} as ComponentMeta<typeof CommentList>

const comments = [
{
_created: '2022-06-15T09:41:09.571Z',
_creatorId: 'TestCreatorID',
_id: 'testID',
creatorName: 'TestName',
isUserVerified: false,
text: 'Test text one',
isEditable: true,
},
{
_created: '2022-06-15T09:41:09.571Z',
_creatorId: 'TestCreatorID2',
_id: 'testID2',
creatorName: 'TestName2',
isUserVerified: false,
text: 'Test text two',
isEditable: true,
},
]
const createComments = (numberOfComments = 2, commentOverloads = {}) =>
[...Array(numberOfComments).keys()].slice(0).map(() => ({
_created: faker.date.past().toString(),
creatorCountry: faker.address.countryCode().toLowerCase(),
_creatorId: faker.internet.userName(),
_id: faker.database.mongodbObjectId(),
creatorName: faker.internet.userName(),
isUserVerified: faker.datatype.boolean(),
text: faker.lorem.text(),
isEditable: faker.datatype.boolean(),
...commentOverloads,
}))

export const Default: ComponentStory<typeof CommentList> = () => (
<CommentList
comments={comments}
comments={createComments(2)}
articleTitle="Test article"
handleDelete={() => Promise.resolve()}
handleEditRequest={() => Promise.resolve()}
handleEdit={() => Promise.resolve()}
/>
)

export const Expandable: ComponentStory<typeof CommentList> = () => (
<CommentList
comments={createComments(20)}
articleTitle="Test article"
handleDelete={() => Promise.resolve()}
handleEditRequest={() => Promise.resolve()}
handleEdit={() => Promise.resolve()}
/>
)

const highlightedCommentList = createComments(20, { isEditable: false })

export const Highlighted: ComponentStory<typeof CommentList> = () => (
<CommentList
comments={highlightedCommentList}
highlightedCommentId={
highlightedCommentList[highlightedCommentList.length - 2]._id
}
articleTitle="Test article"
handleDelete={() => Promise.resolve()}
handleEditRequest={() => Promise.resolve()}
Expand Down
45 changes: 39 additions & 6 deletions packages/components/src/CommentList/CommentList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import ReactGA from 'react-ga4'
import { Box } from 'theme-ui'
import { Button, CommentItem } from '../'
Expand All @@ -10,16 +10,39 @@ export const CommentList: React.FC<{
handleEdit: (_id: string, comment: string) => Promise<void>
handleEditRequest: () => Promise<void>
handleDelete: (_id: string) => Promise<void>
highlightedCommentId?: string
articleTitle?: string
}> = ({
articleTitle,
comments,
handleEditRequest,
handleDelete,
highlightedCommentId,
handleEdit,
}) => {
const [moreComments, setMoreComments] = useState(1)
const shownComments = moreComments * MAX_COMMENTS
const scrollIntoRelevantComment = (commentId: string) => {
setTimeout(() => {
// the delay is needed, otherwise the scroll is not happening in Firefox
document
.getElementById(`comment:${commentId}`)
?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}, 0)
}

useEffect(() => {
if (!highlightedCommentId) return

const i = comments.findIndex((comment) =>
highlightedCommentId.includes(comment._id),
)
if (i >= 0) {
setMoreComments(Math.floor(i / MAX_COMMENTS) + 1)
scrollIntoRelevantComment(highlightedCommentId)
}
}, [highlightedCommentId])

return (
<Box
mb={4}
Expand All @@ -29,19 +52,29 @@ export const CommentList: React.FC<{
}}
>
{comments &&
comments
.slice(0, shownComments)
.map((comment: Comment) => (
comments.slice(0, shownComments).map((comment: Comment) => (
<Box
key={comment._id}
sx={{
marginBottom: 4,
border: `${
highlightedCommentId === comment._id
? '2px dashed black'
: 'none'
}`,
borderRadius: 1,
}}
>
<CommentItem
key={comment._id}
{...comment}
isUserVerified={!!comment.isUserVerified}
isEditable={!!comment.isEditable}
handleEditRequest={handleEditRequest}
handleDelete={handleDelete}
handleEdit={handleEdit}
/>
))}
</Box>
))}
{comments && comments.length > shownComments && (
<Button
sx={{ width: 'max-content', margin: '0 auto' }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { faker } from '@faker-js/faker'
import type { ComponentStory, ComponentMeta } from '@storybook/react'
import { NotificationItem } from './NotificationItem'

Expand All @@ -7,45 +8,25 @@ export default {
} as ComponentMeta<typeof NotificationItem>

export const Default: ComponentStory<typeof NotificationItem> = () => (
<NotificationItem
triggeredBy={{
displayName: 'Example User',
userId: 'abc',
}}
type="howto_useful"
relevantUrl="http://example.com"
/>
<NotificationItem type="howto_useful">
{faker.lorem.sentence()}
</NotificationItem>
)

export const Comment: ComponentStory<typeof NotificationItem> = () => (
<NotificationItem
triggeredBy={{
displayName: 'Example User',
userId: 'abc',
}}
type="new_comment"
relevantUrl="http://example.com"
/>
<NotificationItem type="new_comment">
{faker.lorem.sentence()}
</NotificationItem>
)

export const CommentResearch: ComponentStory<typeof NotificationItem> = () => (
<NotificationItem
triggeredBy={{
displayName: 'Example User',
userId: 'abc',
}}
type="new_comment_research"
relevantUrl="http://example.com"
/>
<NotificationItem type="new_comment_research">
{faker.lorem.sentence()}
</NotificationItem>
)

export const UsefulResearch: ComponentStory<typeof NotificationItem> = () => (
<NotificationItem
triggeredBy={{
displayName: 'Example User',
userId: 'abc',
}}
type="research_useful"
relevantUrl="http://example.com"
/>
<NotificationItem type="research_useful">
{faker.lorem.sentence()}
</NotificationItem>
)
107 changes: 35 additions & 72 deletions packages/components/src/NotificationItem/NotificationItem.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { Link } from 'react-router-dom'
import { ThemeProvider } from '@emotion/react'
import { Flex, Box } from 'theme-ui'
import { Icon } from '../Icon/Icon'
import type { availableGlyphs } from '../Icon/types'

export interface NotificationItemProps {
triggeredBy: {
displayName: string
userId: string
}
type: string
relevantUrl?: string
type notificationType =
| 'howto_mention'
| 'new_comment'
| 'howto_useful'
| 'new_comment_research'
| 'research_useful'

export interface UserNotificationItem {
type: notificationType
children: React.ReactNode
}

function getIconByType(type: notificationType): availableGlyphs {
return ['howto_useful', 'research_useful'].includes(type)
? 'useful'
: 'comment'
}

export const NotificationItem = (props: NotificationItemProps) => {
const { triggeredBy, relevantUrl, type } = props
export const NotificationItem = (props: UserNotificationItem) => {
const { type } = props
return (
<Flex
bg={'white'}
Expand All @@ -27,72 +37,25 @@ export const NotificationItem = (props: NotificationItemProps) => {
fontFamily: 'Inter, sans-serif',
}}
>
{['howto_useful', 'research_useful'].includes(type) ? (
<ThemeProvider
theme={{
styles: {
a: {
textDecoration: 'underline',
padding: '0 .25em',
color: '#61646b',
display: 'inline',
},
},
}}
>
<Flex style={{ textAlign: 'left', color: 'black' }}>
<Box sx={{ opacity: 0.6 }}>
<Icon glyph="useful" size={15} mr={2} />
</Box>
<Box>
Yay,
<Link
style={{
textDecoration: 'underline',
padding: '3px',
color: '#61646b',
}}
to={'/u/' + triggeredBy.userId}
>
{triggeredBy.displayName}
</Link>
found your
<Link
style={{
textDecoration: 'underline',
padding: '3px',
color: '#61646b',
fontWeight: 500,
display: 'inline',
}}
to={relevantUrl || ''}
>
{type === 'howto_useful' ? 'how-to' : 'research'}
</Link>
useful
</Box>
</Flex>
) : (
<Flex>
<Box sx={{ opacity: 0.6 }}>
<Icon glyph="comment" size={15} mr={2} />
</Box>
<Box style={{ textAlign: 'left' }}>
New comment on your
<Link
style={{
textDecoration: 'underline',
padding: '3px',
color: '#61646b',
}}
to={relevantUrl || ''}
>
{type == 'new_comment_research' ? 'Research' : 'how-to'}
</Link>
by
<Link
style={{
textDecoration: 'underline',
padding: '3px',
color: '#61646b',
fontWeight: 500,
display: 'inline',
}}
to={'/u/' + triggeredBy.userId}
>
{triggeredBy.displayName}
</Link>
<Icon glyph={getIconByType(type)} size={15} mr={2} />
</Box>
{props.children}
</Flex>
)}
</ThemeProvider>
</Flex>
)
}
Loading