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

chore(comments): add support for comments layout avatar config #5944

Merged
merged 4 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import type * as React from 'react'
import {type AvatarSize} from '@sanity/ui'
// eslint-disable-next-line camelcase
import {getTheme_v2} from '@sanity/ui/theme'
import styled, {css} from 'styled-components'

export const AVATAR_HEIGHT = 25

const INLINE_STYLE: React.CSSProperties = {
minWidth: AVATAR_HEIGHT,
}

/**
* 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 Down Expand Up @@ -72,9 +83,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 +137,7 @@ const RootStack = styled(Stack)(({theme}) => {
})

interface CommentsListItemLayoutProps {
avatarSize?: AvatarSize
canDelete?: boolean
canEdit?: boolean
comment: CommentDocument
Expand All @@ -141,12 +157,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 +184,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 @@ -325,7 +344,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
>
<InnerStack space={1} data-muted={displayError}>
<Flex align="center" gap={FLEX_GAP} flex={1}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the parent comment does not have an avatar, the spacing between the author name and the comment message is a bit off (see image). To address this, I suggest creating a styled component of this Flex – like HeaderFlex – which has a min height of 25px.

const HeaderFlex = styled(Flex)`
  min-height: ${AVATAR_HEIGHT};
`  

Before and after:

Screenshot 2024-03-11 at 11 49 10

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, updated it to use the avatar size prop.
The height of the avatar will be variable, e.g. if size is 0 it will be 19px.
Screenshot 2024-03-11 at 12 15 47

<CommentsAvatar user={user} />
{withAvatar && <CommentsAvatar user={user} size={avatarSize} />}

<Flex direction="column" gap={2} paddingY={intent ? 2 : 0}>
<Flex
Expand Down Expand Up @@ -401,7 +420,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {

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

<CommentsListItemReferencedValue
hasReferencedValue={hasReferencedValue}
Expand All @@ -412,7 +431,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 +454,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 +478,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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {type EditorChange, keyGenerator, PortableTextEditor} from '@sanity/portable-text-editor'
import {type CurrentUser, type PortableTextBlock} from '@sanity/types'
import {focusFirstDescendant, focusLastDescendant, Stack} from '@sanity/ui'
import {type AvatarSize, focusFirstDescendant, focusLastDescendant, Stack} from '@sanity/ui'
import type * as React from 'react'
import {forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react'
import {type UserListWithPermissionsHookValue} from 'sanity'
Expand Down Expand Up @@ -36,6 +36,7 @@ export interface CommentInputProps {
readOnly?: boolean
value: PortableTextBlock[] | null
withAvatar?: boolean
avatarSize?: AvatarSize
}

interface CommentDiscardDialogController {
Expand All @@ -58,6 +59,7 @@ export interface CommentInputHandle {
export const CommentInput = forwardRef<CommentInputHandle, CommentInputProps>(
function CommentInput(props, ref) {
const {
avatarSize,
currentUser,
expandOnFocus,
focusLock = false,
Expand Down Expand Up @@ -221,6 +223,7 @@ export const CommentInput = forwardRef<CommentInputHandle, CommentInputProps>(

<Stack ref={innerRef}>
<CommentInputInner
avatarSize={avatarSize}
currentUser={currentUser}
focusLock={focusLock}
onBlur={onBlur}
Expand Down
Loading
Loading