Skip to content

Commit

Permalink
feat(tasks): use FormBuilder to create and edit tasks. (#5897)
Browse files Browse the repository at this point in the history
* feat(tasks): add tasks creation action

* feat(tasks): WIP - use FormBuilder in tasks

* feat(tasks): use FormBuilder in tasks, fix remount issues

* feat(tasks): add missing fields to tasks

* feat(tasks): add custom title field

* feat(tasks): add mention options and fix assigned to field

* feat(tasks): wip implementation - use tasks reference with search

* fix: fix build issues

* fix(tasks): styling tasks create form

* fix(tasks): assign target content when a task is created with a document in view

* fix(tasks): update title styling

* fix(tasks): update focus visible on intent link

* fix(tasks): use commentInput in tasks description

* fix(tasks): update style for targetField placeholder

* {wip} saved 2024-02-27 15:41

* feat(tasks): start form edit work

* fix(tasks): move selected document logic

* feat(tasks): add wip activity log

* fix(tasks): update tasks ui

* feat(tasks): integrate remove tasks into the new edit form

* feat(tasks): add support for drafts and tasks duplication

* fix(tasks): updates post rebase with base branch

* fix(tasks): update tasks workspace provider, small fixes

* fix: remove unnecessary import

* fix(tasks): update assignee form field

* fix(tasks): add subscriptions to tasks

* fix(tasks): code cleanup

* fix(tasks): show subscribed tasks in subscribe tab

* fix(tasks): set creator of task as subscriber

* fix(tasks): not show avatars if no subscribers

* feat(tasks): add subscribers when editing and creating a task

* fix(tasks): update assignee fields, and add animation to subscribers

* chore(tasks): move target content field to the top

* fix(tasks): add assigned user to subscribers

* feat(tasks): add listener for query params, handle copy link

* fix(tasks): fix issue in which the tasks created were not saving the created value

* fix(tasks): rename mention user folder

* fix(tasks): add date field in edit form

* fix(tasks): add discard draft action

* feat(tasks): add comments

* fix(tasks): add activities to the activity log (#5929)

* fix(tasks): add activities to the activity log

* fix(tasks): fix string logic of activities

* fix(tasks): update activity components

* fix(tasks): remove activity log dummy data

* fix(tasks): update activity logs to work with comments

---------

Co-authored-by: Pedro Bonamin <[email protected]>

* fix(tasks): update tasks activity log UI

* chore(tasks): simplify form, reorganise files

* chore(tasks): update tasks header

* fix(tasks): ui tweaks (#5947)

* feat(tasks): add create task action from document (#5950)

* feat(tasks): responsive sidebar (#5949)

* fix(tasks): enable check for document action (#5958)

* fix(tasks): update remove task dialog text, update task list item ui

* fix(tasks): use TaskIcon from @sanity/icons

* fix(tasks): support for disabling tasks, add default value to useProviders, move useTasksOperations

* fix(tasks): hide active tab navigation when it's empty

* fix(tasks): sidebar z-index

* fix(tasks): turn on by default tasks config flag

* fix(tasks): add support for mobile footer action

---------

Co-authored-by: Nina Andal Aarvik <[email protected]>
Co-authored-by: Herman Wikner <[email protected]>
  • Loading branch information
3 people authored Mar 12, 2024
1 parent be49900 commit 908577e
Show file tree
Hide file tree
Showing 82 changed files with 3,400 additions and 955 deletions.
2 changes: 1 addition & 1 deletion packages/sanity/src/core/config/prepareConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export function prepareConfig(
__internal: {
sources: resolvedSources,
},
tasks: rawWorkspace.unstable_tasks,
tasks: rawWorkspace.unstable_tasks ?? {enabled: true},
}
preparedWorkspaces.set(rawWorkspace, workspaceSummary)
return workspaceSummary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,12 @@ export function FormFieldBaseHeader(props: FormFieldBaseHeaderProps) {
$floatingCardWidth={floatingCardWidth}
$slotWidth={slotWidth}
>
<ContentBox flex={1} paddingY={2} $presenceMaxWidth={calcAvatarStackWidth(MAX_AVATARS)}>
<ContentBox
data-ui="fieldHeaderContentBox"
flex={1}
paddingY={2}
$presenceMaxWidth={calcAvatarStackWidth(MAX_AVATARS)}
>
{content}
</ContentBox>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`FormBuilder should render a studio form 1`] = `"<div data-as=\\"div\\" data-ui=\\"Stack\\" data-testid=\\"field-title\\" data-level=\\"0\\"><div data-as=\\"div\\" data-ui=\\"Flex\\"><div data-as=\\"div\\" data-ui=\\"Box\\"><div data-as=\\"div\\" data-ui=\\"Stack\\"><div data-as=\\"div\\" data-ui=\\"Flex\\"><label data-ui=\\"Text\\" for=\\"title\\"><span>Title</span></label></div></div></div></div><div><div><div data-testid=\\"change-bar-wrapper\\"><div data-testid=\\"change-bar__field-wrapper\\"><span data-as=\\"span\\" data-ui=\\"TextInput\\" data-scheme=\\"light\\" data-tone=\\"default\\"><span><input data-as=\\"input\\" data-scheme=\\"light\\" data-tone=\\"default\\" id=\\"title\\" type=\\"text\\" value=\\"\\"><span data-border=\\"\\" data-scheme=\\"light\\" data-tone=\\"default\\"></span></span></span></div></div></div></div></div>"`;
exports[`FormBuilder should render a studio form 1`] = `"<div data-as=\\"div\\" data-ui=\\"Stack\\" data-testid=\\"field-title\\" data-level=\\"0\\"><div data-as=\\"div\\" data-ui=\\"Flex\\"><div data-as=\\"div\\" data-ui=\\"fieldHeaderContentBox\\\"><div data-as=\\"div\\" data-ui=\\"Stack\\"><div data-as=\\"div\\" data-ui=\\"Flex\\"><label data-ui=\\"Text\\" for=\\"title\\"><span>Title</span></label></div></div></div></div><div><div><div data-testid=\\"change-bar-wrapper\\"><div data-testid=\\"change-bar__field-wrapper\\"><span data-as=\\"span\\" data-ui=\\"TextInput\\" data-scheme=\\"light\\" data-tone=\\"default\\"><span><input data-as=\\"input\\" data-scheme=\\"light\\" data-tone=\\"default\\" id=\\"title\\" type=\\"text\\" value=\\"\\"><span data-border=\\"\\" data-scheme=\\"light\\" data-tone=\\"default\\"></span></span></span></div></div></div></div></div>"`;
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function AddonDatasetProvider(props: AddonDatasetSetupProviderProps) {
const originalClient = useClient(DEFAULT_STUDIO_CLIENT_OPTIONS)
const [addonDatasetClient, setAddonDatasetClient] = useState<SanityClient | null>(null)
const [isCreatingDataset, setIsCreatingDataset] = useState<boolean>(false)
const [ready, setReady] = useState<boolean>(false)

const getAddonDatasetName = useCallback(async (): Promise<string | undefined> => {
const res = await originalClient.withConfig({apiVersion: API_VERSION}).request({
Expand Down Expand Up @@ -114,6 +115,7 @@ export function AddonDatasetProvider(props: AddonDatasetSetupProviderProps) {
if (!addonDatasetName) return
const client = handleCreateClient(addonDatasetName)
setAddonDatasetClient(client)
setReady(true)
})
}, [getAddonDatasetName, handleCreateClient])

Expand All @@ -122,8 +124,9 @@ export function AddonDatasetProvider(props: AddonDatasetSetupProviderProps) {
client: addonDatasetClient,
createAddonDataset: handleCreateAddonDataset,
isCreatingDataset,
ready,
}),
[addonDatasetClient, handleCreateAddonDataset, isCreatingDataset],
[addonDatasetClient, handleCreateAddonDataset, isCreatingDataset, ready],
)

return <AddonDatasetContext.Provider value={ctxValue}>{children}</AddonDatasetContext.Provider>
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/src/core/studio/addonDataset/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface AddonDatasetContextValue {
* Function to create the addon dataset if it does not exist.
*/
createAddonDataset: () => Promise<SanityClient | null>
ready: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface SearchResultItemPreviewProps {
layout?: GeneralPreviewLayoutKey
presence?: DocumentPresence[]
schemaType: SchemaType
showBadge?: boolean
}

/**
Expand All @@ -43,6 +44,7 @@ export function SearchResultItemPreview({
layout,
presence,
schemaType,
showBadge = true,
}: SearchResultItemPreviewProps) {
const documentPreviewStore = useDocumentPreviewStore()

Expand All @@ -65,11 +67,11 @@ export function SearchResultItemPreview({
return (
<Flex align="center" gap={3}>
{presence && presence.length > 0 && <DocumentPreviewPresence presence={presence} />}
<Badge>{schemaType.title}</Badge>
{showBadge && <Badge>{schemaType.title}</Badge>}
<DocumentStatusIndicator draft={draft} published={published} />
</Flex>
)
}, [draft, isLoading, presence, published, schemaType.title])
}, [draft, isLoading, presence, published, schemaType.title, showBadge])

const tooltip = <DocumentStatus draft={draft} published={published} />

Expand Down
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 {type AvatarSize, Flex, Stack, useLayer} from '@sanity/ui'
import {type AvatarSize, Flex, Stack, type StackProps, 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 @@ -81,7 +81,7 @@ const GhostButton = styled.button`
left: 0;
`

interface CommentsListItemProps {
export interface CommentsListItemProps {
avatarConfig?: {
avatarSize: AvatarSize
parentCommentAvatar: boolean
Expand All @@ -91,6 +91,7 @@ interface CommentsListItemProps {
canReply?: boolean
currentUser: CurrentUser
hasReferencedValue?: boolean
innerPadding?: StackProps['padding']
isSelected: boolean
mentionOptions: UserListWithPermissionsHookValue
mode: CommentsUIMode
Expand All @@ -114,6 +115,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
canReply,
currentUser,
hasReferencedValue,
innerPadding,
isSelected,
mentionOptions,
mode,
Expand Down Expand Up @@ -311,6 +313,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm

<Stack
as="ul"
padding={innerPadding}
// Add some extra padding to the bottom if there is no reply input.
// This is to make the UI look more balanced.
paddingBottom={canReply ? undefined : 1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ const RootCard = styled(Card)(({theme}) => {
border-radius: ${radii}px;
box-shadow: var(--input-box-shadow);
--input-box-shadow: inset 0 0 0 ${input.border.width}px ${color.input.default.enabled.border};
--input-box-shadow: ${focusRingBorderStyle({
color: color.input.default.enabled.border,
width: input.border.width,
})};
&:not([data-expand-on-focus='false'], :focus-within) {
background: transparent;
Expand All @@ -46,7 +49,12 @@ const RootCard = styled(Card)(({theme}) => {
${EditableWrap} {
min-height: 1em;
}
--input-box-shadow: inset 0 0 0 ${input.border.width}px var(--card-focus-ring-color);
/* box-shadow: inset 0 0 0 1px var(--card-focus-ring-color); */
--input-box-shadow: ${focusRingBorderStyle({
color: 'var(--card-focus-ring-color)',
width: input.border.width,
})};
}
&:focus-within {
Expand Down Expand Up @@ -140,6 +148,7 @@ export function CommentInputInner(props: CommentInputInnerProps) {
{avatar}

<RootCard
id="comment-input-root"
data-expand-on-focus={expandOnFocus && !canSubmit ? 'true' : 'false'}
data-focused={focused ? 'true' : 'false'}
flex={1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,19 @@ export const DocumentPanelHeader = memo(
<PaneContextMenuButton
nodes={contextMenuNodes}
key="context-menu"
actionsNodes={states?.map((actionState, actionIndex) => (
<ActionMenuListItem
key={actionState.label}
actionState={actionState}
disabled={Boolean(actionState.disabled)}
index={actionIndex}
onAction={handleAction}
/>
))}
actionsNodes={
states.length > 0
? states.map((actionState, actionIndex) => (
<ActionMenuListItem
key={actionState.label}
actionState={actionState}
disabled={Boolean(actionState.disabled)}
index={actionIndex}
onAction={handleAction}
/>
))
: undefined
}
/>
</div>
)}
Expand Down
24 changes: 19 additions & 5 deletions packages/sanity/src/tasks/__workshop__/TasksCreateStory.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import {AddonDatasetProvider} from 'sanity'

import {TasksForm, TasksProvider} from '../src'
import {TasksFormBuilder, TasksNavigationContext, TasksProvider} from '../src'

function noop() {
return null
}
export default function TasksCreateStory() {
return (
<AddonDatasetProvider>
<TasksProvider>
<TasksForm onCancel={noop} mode="create" />
<TasksNavigationContext.Provider
value={{
state: {
viewMode: 'create',
activeTabId: 'assigned',
selectedTask: '123',
duplicateTaskValues: null,
isOpen: true,
},
setViewMode: () => null,
setActiveTab: () => null,
handleCloseTasks: () => null,
handleOpenTasks: () => null,
handleCopyLinkToTask: () => null,
}}
>
<TasksFormBuilder />
</TasksNavigationContext.Provider>
</TasksProvider>
</AddonDatasetProvider>
)
Expand Down
25 changes: 25 additions & 0 deletions packages/sanity/src/tasks/plugin/TaskCreateAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {TaskIcon} from '@sanity/icons'
import {useCallback} from 'react'
import {type DocumentActionDescription} from 'sanity'

import {useTasksEnabled, useTasksNavigation} from '../src'

export function TaskCreateAction(): DocumentActionDescription | null {
const {handleOpenTasks, setViewMode} = useTasksNavigation()
const {enabled} = useTasksEnabled()

const handleCreateTaskFromDocument = useCallback(() => {
handleOpenTasks()
setViewMode({type: 'create'})
}, [handleOpenTasks, setViewMode])

if (!enabled) return null

return {
icon: TaskIcon,
label: 'Create new task',
title: 'Create new task',
group: ['paneActions'],
onHandle: handleCreateTaskFromDocument,
}
}
54 changes: 43 additions & 11 deletions packages/sanity/src/tasks/plugin/TasksFooterOpenTasks.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,70 @@
import {TaskIcon} from '@sanity/icons'
import {Badge, useMediaIndex} from '@sanity/ui'
import {useCallback, useMemo} from 'react'
import styled from 'styled-components'

import {Button} from '../../ui-components'
import {useTasks, useTasksEnabled} from '../src'
import {useTasks, useTasksEnabled, useTasksNavigation} from '../src'

const ButtonContainer = styled.div`
position: relative;
[data-ui='Badge'] {
position: absolute;
top: -2px;
right: -2px;
}
`

/**
* Button that shows how many pending tasks are assigned to the current document.
* Clicking it will open the task sidebar, showing the open tasks related to the document.
*
* todo: just show the tab with tasks related to the document
* @internal
*/
export function TasksFooterOpenTasks() {
const {data, activeDocument, toggleOpen, isOpen} = useTasks()
const {data, activeDocument} = useTasks()
const {handleOpenTasks, setActiveTab} = useTasksNavigation()
const {enabled} = useTasksEnabled()

const mediaIndex = useMediaIndex()

const pendingTasks = useMemo(
() =>
data.filter((item) => {
return item.target?.document._ref === activeDocument?.documentId && item.status === 'open'
return (
item.target?.document._ref === activeDocument?.documentId &&
item.status === 'open' &&
item.createdByUser
)
}),
[activeDocument, data],
)

const handleOnClick = useCallback(() => {
if (isOpen) {
return
}
toggleOpen()
}, [isOpen, toggleOpen])
handleOpenTasks()
setActiveTab('document')
}, [handleOpenTasks, setActiveTab])

if (pendingTasks.length === 0 || !enabled) return null

const pluralizedTask = `task${pendingTasks.length > 1 ? 's' : ''}`

if (mediaIndex < 3) {
return (
<ButtonContainer>
<Button
mode="bleed"
icon={TaskIcon}
size={'large'}
onClick={handleOnClick}
tooltipProps={{
content: `Open ${pluralizedTask}`,
}}
/>
<Badge tone="primary" fontSize={0}>
{pendingTasks.length}
</Badge>
</ButtonContainer>
)
}
return (
<Button
mode="bleed"
Expand Down
Loading

0 comments on commit 908577e

Please sign in to comment.