Skip to content

Commit

Permalink
fix(tasks): tasks UI updates. (#6136)
Browse files Browse the repository at this point in the history
* fix(tasks): don't line break dates in activity log edx-1211

* fix(tasks):  user Icon and text misaligned in activity feed edx-1207

* fix(tasks): reduce padding on tasks comment reply edx-1209

* fix(tasks): update comment icon alignment edx-1208

* fix(tasks): force assignee selection menu to bottom position

* fix(tasks): reduce description input min height

* fix(tasks): increase distance between actions in form create

* fix(tasks): update placeholder texts

* fix(tasks): update remove task dialog copy

* fix(tasks): don't create empty tasks in the form, update create form ui

* fix(tasks): remove disabled state from document tab, update empty state

* fix(tasks): add tooltips to selection tokens for task status, assignee, and due date

* fix(tasks): update notification target equality check

* fix(tasks): update translation and check for studioUrl

* fix(tasks): update styled components import

* fix(tasks): fix Invalid url on task creation
  • Loading branch information
pedrobonamin authored Apr 8, 2024
1 parent fe11588 commit d45b210
Show file tree
Hide file tree
Showing 23 changed files with 265 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
data-active={isSelected ? 'true' : 'false'}
data-hovered={mouseOver ? 'true' : 'false'}
data-testid="comments-list-item"
data-ui="comments-list-item"
onClick={handleThreadRootClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
Expand Down
45 changes: 37 additions & 8 deletions packages/sanity/src/tasks/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@ const tasksLocaleStrings = defineLocalesResources('tasks', {
/** The label for the button that will previous to the next task */
'buttons.previous.tooltip': 'Go to previous task',
/** Text for the remove task dialog asking for confirmation of deletion */
'dialog.remove-task.body': 'Are you sure you want to delete this task?',
/** Text for the remove task dialog clarifying that deletion is permanent */
'dialog.remove-task.body2': 'Once deleted, it cannot be restored.',
'dialog.remove-task.body': 'Once deleted, a task cannot be recovered.',
/** The label for the cancel button on the remove task dialog */
'dialog.remove-task.buttons.cancel.text': 'Cancel',
/** The label for the confirmation button on the remove task dialog */
'dialog.remove-task.buttons.confirm.text': 'Remove',
'dialog.remove-task.buttons.confirm.text': 'Delete',
/** The title for the remove task dialog */
'dialog.remove-task.title': 'Remove task',
'dialog.remove-task.title': 'Delete this task?',
/** The text used as a placeholder for the footer action in a document with a single task */
'document.footer.open-tasks.placeholder_one': 'Open task',
/** The text used as a placeholder for the footer action in a document with multiple tasks */
Expand All @@ -42,50 +40,81 @@ const tasksLocaleStrings = defineLocalesResources('tasks', {
/** The label used in the button in the footer action in a document with multiple tasks */
'document.footer.open-tasks.text_other': '{{count}} open tasks',

/** The heading in the tasks sidebar, in the assigned tab, when the user hasn't been assigned to any task*/
'empty-state.list.assigned.heading': "You haven't been assigned any tasks",
/** The text in the tasks sidebar, in the assigned tab, when the user hasn't been assigned to any task*/
'empty-state.list.assigned.text': "Once you're assigned tasks they'll show up here",
/** The text in the tasks sidebar button any of the empty states is rendered*/
'empty-state.list.create-new': 'Create new task',
/** The heading in the tasks sidebar, in the document tab, when the document doesn't have any task*/
'empty-state.list.document.heading': "This document doesn't have any tasks yet",
/** The text in the tasks sidebar, in the document tab, when the document doesn't have any task*/
'empty-state.list.document.text': 'Once a document has connected tasks, they will be shown here.',
/** The heading in the tasks sidebar, when viewing the document tab, but there is not an active document*/
'empty-state.list.no-active-document.heading': "Open a document to see it's task",
/** The text in the tasks sidebar, when viewing the document tab, but there is not an active document*/
'empty-state.list.no-active-document.text': 'Tasks on your active document will be shown here.',
/** The heading in the tasks sidebar, in the subscriber tab, when the user is not subscribed to any task*/
'empty-state.list.subscribed.heading': "You haven't subscribed to any tasks",
/** The text in the tasks sidebar, in the subscriber tab, when the user is not subscribed to any task*/
'empty-state.list.subscribed.text':
'When you create, modify, or comment on a task you will be subscribed automatically',

/** The heading in the tasks sidebar, in the assigned tab, under the closed details, when it's empty.*/
'empty-state.status.list.closed.assigned.heading': 'No completed tasks',
/** The text in the tasks sidebar, in the assigned tab, under the closed details, when it's empty.*/
'empty-state.status.list.closed.assigned.text': 'Your tasks marked done will show up here',
/** The heading in the tasks sidebar, in the document tab, under the closed details, when it's empty.*/
'empty-state.status.list.closed.document.heading': 'No completed tasks',
/** The heading in the tasks sidebar, in the subscribed tab, under the closed details, when it's empty.*/
'empty-state.status.list.closed.subscribed.heading': 'No completed tasks',
/** The text in the tasks sidebar, in the subscribed tab, under the closed details, when it's empty.*/
'empty-state.status.list.closed.subscribed.text':
'Tasks you subscribe to marked done will show up here',

/** The heading in the tasks sidebar, in the assigned tab, under the open details, when it's empty.*/
'empty-state.status.list.open.assigned.heading': "You're all caught up",
/** The text in the tasks sidebar, in the assigned tab, under the open details, when it's empty.*/
'empty-state.status.list.open.assigned.text': 'New tasks assigned to you will show up here',
/** The heading in the tasks sidebar, in the document tab, under the open details, when it's empty.*/
'empty-state.status.list.open.document.heading': 'No tasks on this document',
/** The heading in the tasks sidebar, in the subscribed tab, under the open details, when it's empty.*/
'empty-state.status.list.open.subscribed.heading': 'No subscribed tasks',
/** The text in the tasks sidebar, in the subscribed tab, under the open details, when it's empty.*/
'empty-state.status.list.open.subscribed.text': 'Tasks you subscribe to will show up here',

/** Text used in the assignee input when there is no user assigned */
'form.input.assignee.no-user-assigned.text': 'Not assigned',
'form.input.assignee.no-user-assigned.text': 'Unassigned',
/** Text used in the assignee input tooltip when there is no user assigned */
'form.input.assignee.no-user-assigned.tooltip': 'Set assignee',
/** Text used in the assignee input when searching and no users are found */
'form.input.assignee.search.no-users.text': 'No users found',
/** Placeholder text used in the search box in the assignee input */
'form.input.assignee.search.placeholder': 'Select username',
'form.input.assignee.search.placeholder': 'Select assignee',
/** Text used in the assignee input when user is not authorized */
'form.input.assignee.unauthorized.text': 'Unauthorized',
/** Text used in the assignee input tooltip when there is no user assigned */
'form.input.assignee.user-assigned.tooltip': 'Change assignee',
/** Text used in the assignee input when user is not found */
'form.input.assignee.user-not-found.text': 'User not found',
/** The label used in the create more toggle */
'form.input.create-more.text': 'Create more',
/** The label used in the date input button tooltip when it's empty */
'form.input.date.buttons.empty.tooltip': 'Set due date',
/** The label used in the date input to remove the current value */
'form.input.date.buttons.remove.text': 'Remove',
/** The label used in the date input button tooltip when it has value */
'form.input.date.buttons.tooltip': 'Change due date',
/** Placeholder text used in the description input */
'form.input.description.placeholder': 'Add description',
/** Text used in the tooltip in the status change button */
'form.input.status.button.tooltip': 'Change status',
/** The label used in the target input to remove the current value */
'form.input.target.buttons.remove.text': 'Remove target content',
/** The text used in the target input when encountering a schema error */
'form.input.target.error.schema-not-found': 'Schema not found',
/** The placeholder text used in the target input for the search component */
'form.input.target.search.placeholder': 'Select document',
'form.input.target.search.placeholder': 'Select target document',
/** The placeholder text for the title input */
'form.input.title.placeholder': 'Task title',
/** The status error message presented when the user does not supply a title */
Expand Down
32 changes: 24 additions & 8 deletions packages/sanity/src/tasks/src/tasks/components/TasksUserAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {getTheme_v2} from '@sanity/ui/theme'
import {type User, UserAvatar, useUser} from 'sanity'
import {css, styled} from 'styled-components'

import {Tooltip} from '../../../../ui-components'

const AvatarRoot = styled.div<{$size: AvatarSize; $border?: boolean; $removeBg?: boolean}>(
(props) => {
const theme = getTheme_v2(props.theme)
Expand All @@ -31,7 +33,12 @@ const AvatarSkeleton = styled(Skeleton)<{$size: AvatarSize}>((props) => {
`
})

export function TasksUserAvatar(props: {user?: User; size?: AvatarSize; border?: boolean}) {
export function TasksUserAvatar(props: {
user?: User
size?: AvatarSize
border?: boolean
withTooltip?: boolean
}) {
const {user, size = 0, border = true} = props
const [loadedUser, loading] = useUser(user?.id || '')

Expand All @@ -50,12 +57,21 @@ export function TasksUserAvatar(props: {user?: User; size?: AvatarSize; border?:
}

return (
<AvatarRoot $size={size} $removeBg={!!loadedUser?.imageUrl}>
<UserAvatar
user={loadedUser}
size={size}
{...(loadedUser?.imageUrl ? {color: undefined} : {})}
/>
</AvatarRoot>
<Tooltip
content={loadedUser.displayName}
disabled={!props.withTooltip}
portal
fallbackPlacements={['top', 'top-start']}
placement="top-end"
>
<AvatarRoot $size={size} $removeBg={!!loadedUser?.imageUrl}>
<UserAvatar
user={loadedUser}
size={size}
{...(loadedUser?.imageUrl ? {color: undefined} : {})}
{...(props.withTooltip ? {title: null} : {})}
/>
</AvatarRoot>
</Tooltip>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Box, Flex, Text} from '@sanity/ui'
import {memo} from 'react'

import {Tooltip} from '../../../../../ui-components'
import {getChangeDetails, UpdatedTimeAgo, UserName} from './helpers'
import {getChangeDetails, NoWrap, UpdatedTimeAgo, UserName} from './helpers'
import {type FieldChange} from './helpers/parseTransactions'

interface EditedAtProps {
Expand All @@ -25,7 +25,9 @@ export const EditedAt = memo(
<Text muted size={1}>
<UserName userId={activity.author} /> {text} {changeTo}{' '}
<Tooltip content={formattedDate} placement="top-end">
<time dateTime={formattedDate}>{timeAgo}</time>
<NoWrap>
<time dateTime={formattedDate}>{timeAgo}</time>
</NoWrap>
</Tooltip>
</Text>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function TasksActivityCommentInput(props: TasksCommentActivityInputProps)
const {t} = useTranslation(tasksLocaleNamespace)

return (
<ActivityItem userId={currentUser.id}>
<ActivityItem userId={currentUser.id} avatarPaddingTop={3}>
<Card tone="transparent" radius={3} padding={2}>
<CommentInput
withAvatar={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// eslint-disable-next-line camelcase
import {getTheme_v2} from '@sanity/ui/theme'
import {css, styled} from 'styled-components'

import {CommentsListItem, type CommentsListItemProps} from '../../../../../structure/comments'
import {ActivityItem} from './TasksActivityItem'

Expand All @@ -12,19 +16,31 @@ interface TasksActivityCommentItemProps extends Omit<CommentsListItemProps, 'mod
// ...
}

const CommentListItemRoot = styled.div((props) => {
const theme = getTheme_v2(props.theme)
return css`
[data-ui='comments-list-item'] {
padding-right: ${theme.space[2]}px;
&:focus-within {
padding-bottom: ${theme.space[2]}px;
}
}
`
})
export function TasksActivityCommentItem(props: TasksActivityCommentItemProps) {
const {parentComment} = props

return (
<ActivityItem userId={parentComment.authorId}>
<CommentsListItem
{...props}
avatarConfig={COMMENTS_LIST_ITEM_AVATAR_CONFIG}
canReply
innerPadding={1}
isSelected={false}
mode="default" // TODO: set dynamic mode?
/>
<ActivityItem userId={parentComment.authorId} avatarPaddingTop={3}>
<CommentListItemRoot>
<CommentsListItem
{...props}
avatarConfig={COMMENTS_LIST_ITEM_AVATAR_CONFIG}
canReply
isSelected={false}
mode="default"
/>
</CommentListItemRoot>
</ActivityItem>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {styled} from 'styled-components'

import {Tooltip} from '../../../../../ui-components'
import {tasksLocaleNamespace} from '../../../../i18n'
import {UpdatedTimeAgo} from './helpers'
import {NoWrap, UpdatedTimeAgo} from './helpers'
import {ActivityItem} from './TasksActivityItem'

const UserSkeleton = styled(TextSkeleton)`
Expand Down Expand Up @@ -33,7 +33,9 @@ export const TasksActivityCreatedAt = memo(
</strong>
{t('panel.activity.created-fragment')}{' '}
<Tooltip content={formattedDate} placement="top-end">
<time dateTime={createdAt}>{timeAgo}</time>
<NoWrap>
<time dateTime={createdAt}>{timeAgo}</time>
</NoWrap>
</Tooltip>
</Text>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,27 @@ import {styled} from 'styled-components'

import {TasksUserAvatar} from '../TasksUserAvatar'

const ActivityChildrenRoot = styled(Flex)`
height: 100%;
`
const ActivityItemChildrenContainer = styled.div`
width: 100%;
`
export function ActivityItem({userId, children}: {userId: string; children: React.ReactNode}) {

interface ActivityItemProps {
userId: string
children: React.ReactNode
avatarPaddingTop?: number
}
export function ActivityItem({avatarPaddingTop = 1, userId, children}: ActivityItemProps) {
return (
<Flex gap={1}>
<Box marginRight={3} paddingTop={1}>
<Flex>
<Box marginRight={3} paddingTop={avatarPaddingTop}>
<TasksUserAvatar user={{id: userId}} size={0} />
</Box>
<ActivityItemChildrenContainer>{children}</ActivityItemChildrenContainer>
<ActivityChildrenRoot align="center" flex={1}>
<ActivityItemChildrenContainer>{children}</ActivityItemChildrenContainer>
</ActivityChildrenRoot>
</Flex>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const RELATIVE_TIME_OPTIONS: RelativeTimeOptions = {
const Strong = styled.strong`
font-weight: 600;
`
export const NoWrap = styled.span`
white-space: nowrap;
`

export function UpdatedTimeAgo(timestamp: string) {
const date = new Date(timestamp)
Expand All @@ -58,7 +61,11 @@ function DueByChange({date}: {date: string}) {
const dueBy = new Date(date)
const dateFormatter = useDateTimeFormat(DUE_BY_DATE_OPTIONS)
const formattedDate = dateFormatter.format(dueBy)
return <Strong>{formattedDate}</Strong>
return (
<Strong>
<NoWrap>{formattedDate}</NoWrap>
</Strong>
)
}

const LinkWrapper = styled.span`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export function RemoveTaskDialog(props: ReturnType<typeof useRemoveTask>) {
>
<Stack space={3}>
<Text as="p">{t('dialog.remove-task.body')}</Text>
<Text as="p">{t('dialog.remove-task.body2')}</Text>
</Stack>
</Dialog>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const taskSchema = (mode: FormMode) =>
type: 'string',
name: 'assignedTo',
title: 'Assign to',
placeholder: 'Select username',
placeholder: 'Select assignee',
components: {
field: FieldWrapper,
input: AssigneeCreateFormField,
Expand All @@ -114,7 +114,7 @@ export const taskSchema = (mode: FormMode) =>
type: 'date',
name: 'dueBy',
title: 'Deadline',
placeholder: 'Select date',
placeholder: 'yyyy-mm-dd',
components: {
field: FieldWrapper,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ export function DateEditFormField(props: {
text={dueDateIsThisYear ? dueByeDisplayValue.short : dueByeDisplayValue.full}
onClick={handleClick}
ref={buttonRef}
tooltipProps={{
content: value
? t('form.input.date.buttons.tooltip')
: t('form.input.date.buttons.empty.tooltip'),
}}
/>
</Popover>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@ import {
type Path,
set,
type TitledListValue,
useTranslation,
} from 'sanity'

import {Button, MenuButton, MenuItem} from '../../../../../../ui-components'
import {tasksLocaleNamespace} from '../../../../../i18n'
import {TASK_STATUS} from '../../../constants/TaskStatus'

export const StatusMenuButton = forwardRef(function StatusMenuButton(
props: {value: string | undefined; options: TitledListValue<string>[]},
ref: ForwardedRef<HTMLButtonElement>,
) {
const {t} = useTranslation(tasksLocaleNamespace)
const {value, options, ...rest} = props
const selectedOption = options.find((option) => option.value === value)
const icon = TASK_STATUS.find((status) => status.value === value)?.icon
return (
<Button
{...rest}
ref={ref}
tooltipProps={null}
tooltipProps={{content: t('form.input.status.button.tooltip')}}
icon={icon}
text={selectedOption?.title || value}
tone="default"
Expand Down
Loading

0 comments on commit d45b210

Please sign in to comment.