Skip to content

Commit

Permalink
chore(comments): add support for comments layout avatar config (#5944)
Browse files Browse the repository at this point in the history
* chore(comments): add support for comments layout avatar config

* chore(comments): remove show prop from SpacerAvatar

* fix(comments):  nitpicks

* fix(comments): add min height to the comment list item header
  • Loading branch information
pedrobonamin authored and ninaandal committed Mar 12, 2024
1 parent e35126e commit dbfd001
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type * as React from 'react'

export const AVATAR_HEIGHT = 25

const INLINE_STYLE: React.CSSProperties = {
minWidth: AVATAR_HEIGHT,
}
import {type AvatarSize} from '@sanity/ui'
// eslint-disable-next-line camelcase
import {getTheme_v2} from '@sanity/ui/theme'
import styled, {css} from 'styled-components'

/**
* This component is used to as a spacer in situations where we want to align
* components without avatars with components that have avatars.
*/
export function SpacerAvatar() {
return <div style={INLINE_STYLE} />
}
export const SpacerAvatar = styled.div<{$size?: AvatarSize}>((props) => {
const theme = getTheme_v2(props.theme)
const {$size = 1} = props
return css`
min-width: ${theme.avatar.sizes[$size]?.size}px;
`
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ChevronDownIcon} from '@sanity/icons'
import {type CurrentUser} from '@sanity/types'
import {Flex, Stack, useLayer} from '@sanity/ui'
import {type AvatarSize, Flex, Stack, useLayer} from '@sanity/ui'
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {type UserListWithPermissionsHookValue, useTranslation} from 'sanity'
import styled, {css} from 'styled-components'
Expand Down Expand Up @@ -28,6 +28,13 @@ const EMPTY_ARRAY: [] = []

const MAX_COLLAPSED_REPLIES = 5

const DEFAULT_AVATAR_CONFIG: CommentsListItemProps['avatarConfig'] = {
avatarSize: 1,
parentCommentAvatar: true,
replyAvatar: true,
threadCommentsAvatar: true,
}

// data-active = when the comment is selected
// data-hovered = when the mouse is over the comment
const StyledThreadCard = styled(ThreadCard)(() => {
Expand Down Expand Up @@ -75,6 +82,12 @@ const GhostButton = styled.button`
`

interface CommentsListItemProps {
avatarConfig?: {
avatarSize: AvatarSize
parentCommentAvatar: boolean
replyAvatar: boolean
threadCommentsAvatar: boolean
}
canReply?: boolean
currentUser: CurrentUser
hasReferencedValue?: boolean
Expand All @@ -97,6 +110,7 @@ interface CommentsListItemProps {

export const CommentsListItem = React.memo(function CommentsListItem(props: CommentsListItemProps) {
const {
avatarConfig = DEFAULT_AVATAR_CONFIG,
canReply,
currentUser,
hasReferencedValue,
Expand Down Expand Up @@ -242,12 +256,14 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
splicedReplies.map((reply) => (
<Stack as="li" key={reply._id} {...applyCommentIdAttr(reply._id)}>
<CommentsListItemLayout
avatarSize={avatarConfig.avatarSize}
canDelete={reply.authorId === currentUser.id}
canEdit={reply.authorId === currentUser.id}
comment={reply}
currentUser={currentUser}
hasError={reply._state?.type === 'createError'}
isRetrying={reply._state?.type === 'createRetrying'}
intent={commentIntentIfDiffers(parentComment, reply)}
mentionOptions={mentionOptions}
mode={mode}
onCopyLink={onCopyLink}
Expand All @@ -257,11 +273,13 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
onInputKeyDown={handleInputKeyDown}
onReactionSelect={onReactionSelect}
readOnly={readOnly}
intent={commentIntentIfDiffers(parentComment, reply)}
withAvatar={avatarConfig.threadCommentsAvatar}
/>
</Stack>
)),
[
avatarConfig.threadCommentsAvatar,
avatarConfig.avatarSize,
currentUser,
handleInputKeyDown,
mentionOptions,
Expand Down Expand Up @@ -300,6 +318,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
>
<Stack as="li" {...applyCommentIdAttr(parentComment._id)}>
<CommentsListItemLayout
avatarSize={avatarConfig.avatarSize}
canDelete={parentComment.authorId === currentUser.id}
canEdit={parentComment.authorId === currentUser.id}
comment={parentComment}
Expand All @@ -319,6 +338,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
onReactionSelect={onReactionSelect}
onStatusChange={onStatusChange}
readOnly={readOnly}
withAvatar={avatarConfig.parentCommentAvatar}
/>
</Stack>

Expand All @@ -339,6 +359,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm

{canReply && (
<CommentInput
avatarSize={avatarConfig.avatarSize}
currentUser={currentUser}
expandOnFocus
mentionOptions={mentionOptions}
Expand All @@ -355,6 +376,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
readOnly={readOnly || mode === 'upsell'}
ref={replyInputRef}
value={value}
withAvatar={avatarConfig.replyAvatar}
/>
)}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function CommentsListItemContextMenu(props: CommentsListItemContextMenuPr

const {t} = useTranslation(commentsLocaleNamespace)

const hasContextMenuOptions = Boolean(canDelete || canEdit || onCopyLink)
return (
<TooltipDelayGroupProvider>
<Flex>
Expand Down Expand Up @@ -117,51 +118,52 @@ export function CommentsListItemContextMenu(props: CommentsListItemContextMenuPr
}}
/>
)}

<MenuButton
id="comment-actions-menu"
button={
<ContextMenuButton
aria-label={t('list-item.open-menu-aria-label')}
disabled={readOnly}
hidden={!showMenuButton}
/>
}
onOpen={onMenuOpen}
onClose={onMenuClose}
menu={
<Menu>
<MenuItem
hidden={!canEdit}
icon={EditIcon}
onClick={onEditStart}
text={t('list-item.edit-comment')}
tooltipProps={
mode === 'upsell' ? {content: t('list-item.edit-comment-upsell')} : undefined
}
disabled={mode === 'upsell'}
{hasContextMenuOptions && (
<MenuButton
id="comment-actions-menu"
button={
<ContextMenuButton
aria-label={t('list-item.open-menu-aria-label')}
disabled={readOnly}
hidden={!showMenuButton}
/>
}
onOpen={onMenuOpen}
onClose={onMenuClose}
menu={
<Menu>
<MenuItem
hidden={!canEdit}
icon={EditIcon}
onClick={onEditStart}
text={t('list-item.edit-comment')}
tooltipProps={
mode === 'upsell' ? {content: t('list-item.edit-comment-upsell')} : undefined
}
disabled={mode === 'upsell'}
/>

<MenuItem
hidden={!canDelete}
icon={TrashIcon}
onClick={onDeleteStart}
text={t('list-item.delete-comment')}
tone="critical"
/>
<MenuItem
hidden={!canDelete}
icon={TrashIcon}
onClick={onDeleteStart}
text={t('list-item.delete-comment')}
tone="critical"
/>

<MenuDivider hidden={!canDelete && !canEdit} />
<MenuDivider hidden={!canDelete && !canEdit} />

<MenuItem
hidden={!onCopyLink}
icon={LinkIcon}
onClick={onCopyLink}
text={t('list-item.copy-link')}
/>
</Menu>
}
popover={POPOVER_PROPS}
/>
<MenuItem
hidden={!onCopyLink}
icon={LinkIcon}
onClick={onCopyLink}
text={t('list-item.copy-link')}
/>
</Menu>
}
popover={POPOVER_PROPS}
/>
)}
</FloatingCard>
</Flex>
</TooltipDelayGroupProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import {hues} from '@sanity/color'
import {type CurrentUser} from '@sanity/types'
import {Box, Card, Flex, Stack, Text, TextSkeleton, useClickOutside} from '@sanity/ui'
import {
type AvatarSize,
Box,
Card,
Flex,
Stack,
Text,
TextSkeleton,
useClickOutside,
} from '@sanity/ui'
// eslint-disable-next-line camelcase
import {getTheme_v2} from '@sanity/ui/theme'
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {
type RelativeTimeOptions,
Expand All @@ -26,7 +37,7 @@ import {
type CommentsUIMode,
type CommentUpdatePayload,
} from '../../types'
import {AVATAR_HEIGHT, CommentsAvatar, SpacerAvatar} from '../avatars'
import {CommentsAvatar, SpacerAvatar} from '../avatars'
import {FLEX_GAP} from '../constants'
import {CommentMessageSerializer} from '../pte'
import {CommentInput, type CommentInputHandle} from '../pte/comment-input'
Expand All @@ -53,6 +64,14 @@ const TimeText = styled(Text)(({theme}) => {
`
})

const HeaderFlex = styled(Flex)<{$size: AvatarSize}>((props) => {
const theme = getTheme_v2(props.theme)

return css`
min-height: ${theme.avatar.sizes[props.$size]?.size}px;
`
})

const IntentText = styled(Text)(({theme}) => {
const isDark = theme.sanity.color.dark
const fg = hues.gray[isDark ? 200 : 800].hex
Expand All @@ -72,9 +91,13 @@ const InnerStack = styled(Stack)`
}
`

const ErrorFlex = styled(Flex)`
min-height: ${AVATAR_HEIGHT}px;
`
const ErrorFlex = styled(Flex)<{$size: AvatarSize}>((props) => {
const theme = getTheme_v2(props.theme)

return css`
min-height: ${theme.avatar.sizes[props.$size]?.size}px;
`
})

const RetryCardButton = styled(Card)`
// Add not on hover
Expand Down Expand Up @@ -122,6 +145,7 @@ const RootStack = styled(Stack)(({theme}) => {
})

interface CommentsListItemLayoutProps {
avatarSize?: AvatarSize
canDelete?: boolean
canEdit?: boolean
comment: CommentDocument
Expand All @@ -141,12 +165,14 @@ interface CommentsListItemLayoutProps {
onReactionSelect?: (id: string, reaction: CommentReactionOption) => void
onStatusChange?: (id: string, status: CommentStatus) => void
readOnly?: boolean
withAvatar?: boolean
}

const RELATIVE_TIME_OPTIONS: RelativeTimeOptions = {useTemporalPhrase: true}

export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
const {
avatarSize = 1,
canDelete,
canEdit,
comment,
Expand All @@ -166,6 +192,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
onReactionSelect,
onStatusChange,
readOnly,
withAvatar = true,
} = props
const {_createdAt, authorId, message, _id, lastEditedAt} = comment
const [user] = useUser(authorId)
Expand Down Expand Up @@ -324,8 +351,8 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
space={4}
>
<InnerStack space={1} data-muted={displayError}>
<Flex align="center" gap={FLEX_GAP} flex={1}>
<CommentsAvatar user={user} />
<HeaderFlex align="center" gap={FLEX_GAP} flex={1} $size={avatarSize}>
{withAvatar && <CommentsAvatar user={user} size={avatarSize} />}

<Flex direction="column" gap={2} paddingY={intent ? 2 : 0}>
<Flex
Expand Down Expand Up @@ -397,11 +424,11 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
/>
</ContextMenuBox>
)}
</Flex>
</HeaderFlex>

{isTextSelectionComment(comment) && Boolean(comment?.contentSnapshot) && (
<Flex gap={FLEX_GAP} marginBottom={3}>
<SpacerAvatar />
{withAvatar && <SpacerAvatar $size={avatarSize} />}

<CommentsListItemReferencedValue
hasReferencedValue={hasReferencedValue}
Expand All @@ -412,7 +439,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {

{isEditing && (
<Flex align="flex-start" gap={2}>
<SpacerAvatar />
{withAvatar && <SpacerAvatar $size={avatarSize} />}

<Stack flex={1}>
<CommentInput
Expand All @@ -435,15 +462,15 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {

{!isEditing && (
<Flex gap={FLEX_GAP}>
<SpacerAvatar />
{withAvatar && <SpacerAvatar $size={avatarSize} />}

<CommentMessageSerializer blocks={message} />
</Flex>
)}

{hasReactions && (
<Flex gap={FLEX_GAP} marginTop={2}>
<SpacerAvatar />
{withAvatar && <SpacerAvatar $size={avatarSize} />}

<Box onClick={stopPropagation}>
<CommentReactionsBar
Expand All @@ -459,8 +486,8 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
</InnerStack>

{displayError && (
<ErrorFlex gap={FLEX_GAP}>
<SpacerAvatar />
<ErrorFlex gap={FLEX_GAP} $size={avatarSize}>
{withAvatar && <SpacerAvatar $size={avatarSize} />}

<Flex align="center" gap={1} flex={1}>
<Text muted size={1}>
Expand Down
Loading

0 comments on commit dbfd001

Please sign in to comment.