sanity-plugin-workflow 1.0.0-beta.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-workflow",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0",
4
4
  "description": "A demonstration of a custom content publishing workflow using Sanity.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -58,7 +58,7 @@
58
58
  "groq": "^3.3.1",
59
59
  "lexorank": "^1.0.5",
60
60
  "react-fast-compare": "^3.2.1",
61
- "sanity-plugin-utils": "^1.3.0"
61
+ "sanity-plugin-utils": "^1.6.1"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@commitlint/cli": "^17.4.4",
@@ -85,8 +85,8 @@
85
85
  "react-dom": "^18.2.0",
86
86
  "react-is": "^18.2.0",
87
87
  "rimraf": "^4.1.2",
88
- "sanity": "^3.3.1",
89
- "semantic-release": "^20.1.0",
88
+ "sanity": "^3.9.1",
89
+ "semantic-release": "^20.1.3",
90
90
  "typescript": "^5.0.0"
91
91
  },
92
92
  "peerDependencies": {
@@ -1,33 +1,32 @@
1
1
  import {UsersIcon} from '@sanity/icons'
2
- import {DocumentActionProps} from 'sanity'
3
2
  import {useState} from 'react'
3
+ import {DocumentActionProps} from 'sanity'
4
4
  import {useProjectUsers} from 'sanity-plugin-utils'
5
5
 
6
- import {API_VERSION} from '../constants'
7
6
  import UserAssignment from '../components/UserAssignment'
8
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
9
- import {State} from '../types'
7
+ import {useWorkflowContext} from '../components/WorkflowContext'
8
+ import {API_VERSION} from '../constants'
10
9
 
11
- export function AssignWorkflow(props: DocumentActionProps, states: State[]) {
10
+ export function AssignWorkflow(props: DocumentActionProps) {
12
11
  const {id} = props
12
+ const {metadata, loading, error} = useWorkflowContext(id)
13
13
  const [isDialogOpen, setDialogOpen] = useState(false)
14
14
  const userList = useProjectUsers({apiVersion: API_VERSION})
15
- const {data, loading, error} = useWorkflowMetadata(id, states)
16
15
 
17
16
  if (error) {
18
17
  console.error(error)
19
18
  }
20
19
 
21
- if (!data?.metadata) {
20
+ if (!metadata) {
22
21
  return null
23
22
  }
24
23
 
25
24
  return {
26
25
  icon: UsersIcon,
27
26
  type: 'dialog',
28
- disabled: !data || loading || error,
27
+ disabled: !metadata || loading || error,
29
28
  label: `Assign`,
30
- title: data ? null : `Document is not in Workflow`,
29
+ title: metadata ? null : `Document is not in Workflow`,
31
30
  dialog: isDialogOpen && {
32
31
  type: 'popover',
33
32
  onClose: () => {
@@ -36,7 +35,7 @@ export function AssignWorkflow(props: DocumentActionProps, states: State[]) {
36
35
  content: (
37
36
  <UserAssignment
38
37
  userList={userList}
39
- assignees={data.metadata?.assignees ?? []}
38
+ assignees={metadata?.assignees?.length > 0 ? metadata.assignees : []}
40
39
  documentId={id}
41
40
  />
42
41
  ),
@@ -1,16 +1,15 @@
1
- import {useCallback, useState} from 'react'
2
1
  import {SplitVerticalIcon} from '@sanity/icons'
3
- import {DocumentActionProps, useClient} from 'sanity'
4
2
  import {useToast} from '@sanity/ui'
5
3
  import {LexoRank} from 'lexorank'
4
+ import {useCallback, useState} from 'react'
5
+ import {DocumentActionProps, useClient} from 'sanity'
6
6
 
7
+ import {useWorkflowContext} from '../components/WorkflowContext'
7
8
  import {API_VERSION} from '../constants'
8
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
9
- import {State} from '../types'
10
9
 
11
- export function BeginWorkflow(props: DocumentActionProps, states: State[]) {
10
+ export function BeginWorkflow(props: DocumentActionProps) {
12
11
  const {id, draft} = props
13
- const {data, loading, error} = useWorkflowMetadata(id, states)
12
+ const {metadata, loading, error, states} = useWorkflowContext(id)
14
13
  const client = useClient({apiVersion: API_VERSION})
15
14
  const toast = useToast()
16
15
  const [beginning, setBeginning] = useState(false)
@@ -27,19 +26,15 @@ export function BeginWorkflow(props: DocumentActionProps, states: State[]) {
27
26
  {state: states[0].id}
28
27
  )
29
28
  client
30
- .createIfNotExists(
31
- {
32
- _id: `workflow-metadata.${id}`,
33
- _type: `workflow.metadata`,
34
- documentId: id,
35
- state: states[0].id,
36
- orderRank: lowestOrderFirstState
37
- ? LexoRank.parse(lowestOrderFirstState).genNext().toString()
38
- : LexoRank.min().toString(),
39
- },
40
- // Faster!
41
- {visibility: 'async'}
42
- )
29
+ .createIfNotExists({
30
+ _id: `workflow-metadata.${id}`,
31
+ _type: `workflow.metadata`,
32
+ documentId: id,
33
+ state: states[0].id,
34
+ orderRank: lowestOrderFirstState
35
+ ? LexoRank.parse(lowestOrderFirstState).genNext().toString()
36
+ : LexoRank.min().toString(),
37
+ })
43
38
  .then(() => {
44
39
  toast.push({
45
40
  status: 'success',
@@ -52,14 +47,14 @@ export function BeginWorkflow(props: DocumentActionProps, states: State[]) {
52
47
  })
53
48
  }, [id, states, client, toast])
54
49
 
55
- if (!draft || complete || data.metadata) {
50
+ if (!draft || complete || metadata) {
56
51
  return null
57
52
  }
58
53
 
59
54
  return {
60
55
  icon: SplitVerticalIcon,
61
56
  type: 'dialog',
62
- disabled: data?.metadata || loading || error || beginning || complete,
57
+ disabled: metadata || loading || error || beginning || complete,
63
58
  label: beginning ? `Beginning...` : `Begin Workflow`,
64
59
  onHandle: () => {
65
60
  handle()
@@ -1,14 +1,13 @@
1
- import {useCallback} from 'react'
2
1
  import {CheckmarkIcon} from '@sanity/icons'
2
+ import {useCallback} from 'react'
3
3
  import {DocumentActionProps, useClient} from 'sanity'
4
4
 
5
+ import {useWorkflowContext} from '../components/WorkflowContext'
5
6
  import {API_VERSION} from '../constants'
6
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
7
- import {State} from '../types'
8
7
 
9
- export function CompleteWorkflow(props: DocumentActionProps, states: State[]) {
8
+ export function CompleteWorkflow(props: DocumentActionProps) {
10
9
  const {id} = props
11
- const {data, loading, error} = useWorkflowMetadata(id, states)
10
+ const {metadata, loading, error, states} = useWorkflowContext(id)
12
11
  const client = useClient({apiVersion: API_VERSION})
13
12
 
14
13
  if (error) {
@@ -19,12 +18,13 @@ export function CompleteWorkflow(props: DocumentActionProps, states: State[]) {
19
18
  client.delete(`workflow-metadata.${id}`)
20
19
  }, [id, client])
21
20
 
22
- const isLastState = data?.state?.id === states[states.length - 1].id
23
-
24
- if (!data.metadata) {
21
+ if (!metadata) {
25
22
  return null
26
23
  }
27
24
 
25
+ const state = states.find((s) => s.id === metadata.state)
26
+ const isLastState = state?.id === states[states.length - 1].id
27
+
28
28
  return {
29
29
  icon: CheckmarkIcon,
30
30
  type: 'dialog',
@@ -3,17 +3,13 @@ import {useToast} from '@sanity/ui'
3
3
  import {useCurrentUser, useValidationStatus} from 'sanity'
4
4
  import {DocumentActionProps, useClient} from 'sanity'
5
5
 
6
+ import {useWorkflowContext} from '../components/WorkflowContext'
6
7
  import {API_VERSION} from '../constants'
7
8
  import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
8
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
9
9
  import {State} from '../types'
10
10
 
11
11
  // eslint-disable-next-line complexity
12
- export function UpdateWorkflow(
13
- props: DocumentActionProps,
14
- allStates: State[],
15
- actionState: State
16
- ) {
12
+ export function UpdateWorkflow(props: DocumentActionProps, actionState: State) {
17
13
  const {id, type} = props
18
14
 
19
15
  const user = useCurrentUser()
@@ -21,10 +17,11 @@ export function UpdateWorkflow(
21
17
  const toast = useToast()
22
18
  const currentUser = useCurrentUser()
23
19
 
24
- const {data, loading, error} = useWorkflowMetadata(id, allStates)
25
- const {state: currentState} = data
26
- const {assignees = []} = data?.metadata ?? {}
20
+ const {metadata, loading, error, states} = useWorkflowContext(id)
21
+ const currentState = states.find((s) => s.id === metadata?.state)
22
+ const {assignees = []} = metadata ?? {}
27
23
 
24
+ // TODO: Shouldn't the document action props contain this?
28
25
  const {validation, isValidating} = useValidationStatus(id, type)
29
26
  const hasValidationErrors =
30
27
  currentState?.requireValidation &&
@@ -47,15 +44,6 @@ export function UpdateWorkflow(
47
44
  status: 'success',
48
45
  title: `Document state now "${newState.title}"`,
49
46
  })
50
- // Perform document operations after State changes
51
- // If State has changed and the document needs to be un/published
52
- // This functionality was deemed too dangerous / unexpected
53
- // Revisit with improved UX
54
- // if (!ops.publish.disabled && nextOperation === 'publish') {
55
- // ops.publish.execute()
56
- // } else if (!ops.unpublish.disabled && nextOperation === 'unpublish') {
57
- // ops.unpublish.execute()
58
- // }
59
47
  })
60
48
  .catch((err) => {
61
49
  props.onComplete()
@@ -70,14 +58,12 @@ export function UpdateWorkflow(
70
58
  // Remove button if:
71
59
  // Document is not in Workflow OR
72
60
  // The current State is the same as this actions State
73
- if (!data.metadata || (currentState && currentState.id === actionState.id)) {
61
+ if (!metadata || (currentState && currentState.id === actionState.id)) {
74
62
  return null
75
63
  }
76
64
 
77
- const currentStateIndex = allStates.findIndex(
78
- (s) => s.id === currentState?.id
79
- )
80
- const actionStateIndex = allStates.findIndex((s) => s.id === actionState.id)
65
+ const currentStateIndex = states.findIndex((s) => s.id === currentState?.id)
66
+ const actionStateIndex = states.findIndex((s) => s.id === actionState.id)
81
67
  const direction = actionStateIndex > currentStateIndex ? 'promote' : 'demote'
82
68
  const DirectionIcon = direction === 'promote' ? ArrowRightIcon : ArrowLeftIcon
83
69
  const directionLabel = direction === 'promote' ? 'Promote' : 'Demote'
@@ -94,7 +80,7 @@ export function UpdateWorkflow(
94
80
  actionState?.roles?.length !== 0
95
81
 
96
82
  const actionStateIsAValidTransition =
97
- currentState?.id && currentState.transitions.length
83
+ currentState?.id && currentState?.transitions?.length
98
84
  ? // If the Current State limits transitions to specific States
99
85
  // Check that the Action State is in Current State's transitions array
100
86
  currentState.transitions.includes(actionState.id)
@@ -104,7 +90,7 @@ export function UpdateWorkflow(
104
90
  const userAssignmentCanUpdateState = actionState.requireAssignment
105
91
  ? // If the Action State requires assigned users
106
92
  // Check the current user ID is in the assignees array
107
- currentUser && assignees.length && assignees.includes(currentUser.id)
93
+ currentUser && assignees?.length && assignees.includes(currentUser.id)
108
94
  : // Otherwise this isn't a problem
109
95
  true
110
96
 
@@ -1,17 +1,14 @@
1
1
  import {CurrentUser, DocumentBadgeDescription} from 'sanity'
2
2
  import {useProjectUsers} from 'sanity-plugin-utils'
3
- import {API_VERSION} from '../constants'
4
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
5
3
 
6
- import {State} from '../types'
4
+ import {useWorkflowContext} from '../components/WorkflowContext'
5
+ import {API_VERSION} from '../constants'
7
6
 
8
7
  export function AssigneesBadge(
9
- states: State[],
10
8
  documentId: string,
11
9
  currentUser: CurrentUser | null
12
10
  ): DocumentBadgeDescription | null {
13
- const {data, loading, error} = useWorkflowMetadata(documentId, states)
14
- const {metadata} = data
11
+ const {metadata, loading, error} = useWorkflowContext(documentId)
15
12
  const userList = useProjectUsers({apiVersion: API_VERSION})
16
13
 
17
14
  if (loading || error || !metadata) {
@@ -29,7 +26,9 @@ export function AssigneesBadge(
29
26
  }
30
27
 
31
28
  const {assignees} = metadata ?? []
32
- const hasMe = currentUser ? assignees.some((assignee) => assignee === currentUser.id) : false
29
+ const hasMe = currentUser
30
+ ? assignees.some((assignee) => assignee === currentUser.id)
31
+ : false
33
32
  const assigneesCount = hasMe ? assignees.length - 1 : assignees.length
34
33
  const assigneeUsers = userList.filter((user) => assignees.includes(user.id))
35
34
  const title = assigneeUsers.map((user) => user.displayName).join(', ')
@@ -39,7 +38,9 @@ export function AssigneesBadge(
39
38
  if (hasMe && assigneesCount === 0) {
40
39
  label = 'Assigned to Me'
41
40
  } else if (hasMe && assigneesCount > 0) {
42
- label = `Me and ${assigneesCount} ${assigneesCount === 1 ? 'other' : 'others'}`
41
+ label = `Me and ${assigneesCount} ${
42
+ assigneesCount === 1 ? 'other' : 'others'
43
+ }`
43
44
  } else {
44
45
  label = `${assigneesCount} assigned`
45
46
  }
@@ -1,11 +1,12 @@
1
1
  import {DocumentBadgeDescription} from 'sanity'
2
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
3
2
 
4
- import {State} from '../types'
3
+ import {useWorkflowContext} from '../components/WorkflowContext'
5
4
 
6
- export function StateBadge(states: State[], documentId: string): DocumentBadgeDescription | null {
7
- const {data, loading, error} = useWorkflowMetadata(documentId, states)
8
- const {state} = data
5
+ export function StateBadge(
6
+ documentId: string
7
+ ): DocumentBadgeDescription | null {
8
+ const {metadata, loading, error, states} = useWorkflowContext(documentId)
9
+ const state = states.find((s) => s.id === metadata?.state)
9
10
 
10
11
  if (loading || error) {
11
12
  if (error) {
@@ -141,10 +141,8 @@ export function DocumentCard(props: DocumentCardProps) {
141
141
  <Flex align="center" justify="space-between" gap={1}>
142
142
  <Box flex={1}>
143
143
  <Preview
144
- // Like as in desk lists, except it has an intermittent loading state
145
- // layout="default"
146
- // Like in the PTE, with no loading state
147
- layout="block"
144
+ layout="default"
145
+ skipVisibilityCheck
148
146
  value={item}
149
147
  schemaType={schema.get(item._type) as SchemaType}
150
148
  />
@@ -1,6 +1,6 @@
1
- import {Draggable} from '@hello-pangea/dnd'
2
- import {useVirtualizer} from '@tanstack/react-virtual'
3
- import {useMemo, useRef} from 'react'
1
+ import {Draggable, DraggableStyle} from '@hello-pangea/dnd'
2
+ import {useVirtualizer, VirtualItem} from '@tanstack/react-virtual'
3
+ import {CSSProperties, useMemo, useRef} from 'react'
4
4
  import {CurrentUser} from 'sanity'
5
5
  import {UserExtended} from 'sanity-plugin-utils'
6
6
 
@@ -25,6 +25,35 @@ type DocumentListProps = {
25
25
  userRoleCanDrop: boolean
26
26
  }
27
27
 
28
+ function getStyle(
29
+ draggableStyle: DraggableStyle | undefined,
30
+ virtualItem: VirtualItem
31
+ ): CSSProperties {
32
+ // Default transform required by tanstack virtual for positioning
33
+ let transform = `translateY(${virtualItem.start}px)`
34
+
35
+ // If a card is being dragged over, this card needs to move up or down
36
+ if (draggableStyle && draggableStyle.transform) {
37
+ // So get the transform value from beautiful-dnd
38
+ const draggableTransformY = parseInt(
39
+ draggableStyle.transform.split(',')[1].split('px')[0],
40
+ 10
41
+ )
42
+
43
+ // And apply it to the card
44
+ transform = `translateY(${virtualItem.start + draggableTransformY}px)`
45
+ }
46
+
47
+ return {
48
+ position: 'absolute',
49
+ top: 0,
50
+ left: 0,
51
+ width: '100%',
52
+ height: `${virtualItem.size}px`,
53
+ transform,
54
+ }
55
+ }
56
+
28
57
  export default function DocumentList(props: DocumentListProps) {
29
58
  const {
30
59
  data = [],
@@ -48,15 +77,18 @@ export default function DocumentList(props: DocumentListProps) {
48
77
 
49
78
  const parentRef = useRef(null)
50
79
 
51
- const rowVirtualizer = useVirtualizer({
52
- count: data.length,
80
+ const virtualizer = useVirtualizer({
81
+ count: dataFiltered.length,
53
82
  getScrollElement: () => parentRef.current,
54
83
  getItemKey: (index) => dataFiltered[index]?._metadata?.documentId ?? index,
55
- estimateSize: () => 113,
56
- overscan: 10,
84
+ estimateSize: () => 115,
85
+ overscan: 7,
86
+ measureElement: (element) => {
87
+ return element.getBoundingClientRect().height || 115
88
+ },
57
89
  })
58
90
 
59
- if (!data.length) {
91
+ if (!data.length || !dataFiltered.length) {
60
92
  return null
61
93
  }
62
94
 
@@ -66,61 +98,72 @@ export default function DocumentList(props: DocumentListProps) {
66
98
  style={{
67
99
  height: `100%`,
68
100
  overflow: 'auto',
69
- paddingTop: 1,
70
101
  // Smooths scrollbar behaviour
71
102
  overflowAnchor: 'none',
72
103
  scrollBehavior: 'auto',
104
+ paddingTop: 1,
73
105
  }}
74
106
  >
75
- {rowVirtualizer.getVirtualItems().map((virtualItem) => {
76
- const item = dataFiltered[virtualItem.index]
77
-
78
- const {documentId, assignees} = item?._metadata ?? {}
107
+ <div
108
+ style={{
109
+ height: `${virtualizer.getTotalSize()}px`,
110
+ width: '100%',
111
+ position: 'relative',
112
+ }}
113
+ >
114
+ {virtualizer.getVirtualItems().map((virtualItem) => {
115
+ const item = dataFiltered[virtualItem.index]
79
116
 
80
- if (!documentId) {
81
- return null
82
- }
117
+ const {documentId, assignees} = item?._metadata ?? {}
83
118
 
84
- const isInvalid = invalidDocumentIds.includes(documentId)
85
- const meInAssignees = user?.id ? assignees?.includes(user.id) : false
86
- const isDragDisabled =
87
- patchingIds.includes(documentId) ||
88
- !userRoleCanDrop ||
89
- isInvalid ||
90
- !(state.requireAssignment
91
- ? state.requireAssignment && meInAssignees
92
- : true)
119
+ const isInvalid = invalidDocumentIds.includes(documentId)
120
+ const meInAssignees = user?.id ? assignees?.includes(user.id) : false
121
+ const isDragDisabled =
122
+ patchingIds.includes(documentId) ||
123
+ !userRoleCanDrop ||
124
+ isInvalid ||
125
+ !(state.requireAssignment
126
+ ? state.requireAssignment && meInAssignees
127
+ : true)
93
128
 
94
- return (
95
- <Draggable
96
- // The metadata's documentId is always the published one to avoid rerendering
97
- // key={documentId}
98
- key={virtualItem.key}
99
- draggableId={documentId}
100
- index={virtualItem.index}
101
- isDragDisabled={isDragDisabled}
102
- >
103
- {(draggableProvided, draggableSnapshot) => (
104
- <div
105
- ref={draggableProvided.innerRef}
106
- {...draggableProvided.draggableProps}
107
- {...draggableProvided.dragHandleProps}
108
- >
109
- <DocumentCard
110
- userRoleCanDrop={userRoleCanDrop}
111
- isDragDisabled={isDragDisabled}
112
- isPatching={patchingIds.includes(documentId)}
113
- isDragging={draggableSnapshot.isDragging}
114
- item={item}
115
- toggleInvalidDocumentId={toggleInvalidDocumentId}
116
- userList={userList}
117
- states={states}
118
- />
119
- </div>
120
- )}
121
- </Draggable>
122
- )
123
- })}
129
+ return (
130
+ <Draggable
131
+ key={virtualItem.key}
132
+ draggableId={documentId}
133
+ index={virtualItem.index}
134
+ isDragDisabled={isDragDisabled}
135
+ >
136
+ {(draggableProvided, draggableSnapshot) => (
137
+ <div
138
+ ref={draggableProvided.innerRef}
139
+ {...draggableProvided.draggableProps}
140
+ {...draggableProvided.dragHandleProps}
141
+ style={getStyle(
142
+ draggableProvided.draggableProps.style,
143
+ virtualItem
144
+ )}
145
+ >
146
+ <div
147
+ ref={virtualizer.measureElement}
148
+ data-index={virtualItem.index}
149
+ >
150
+ <DocumentCard
151
+ userRoleCanDrop={userRoleCanDrop}
152
+ isDragDisabled={isDragDisabled}
153
+ isPatching={patchingIds.includes(documentId)}
154
+ isDragging={draggableSnapshot.isDragging}
155
+ item={item}
156
+ toggleInvalidDocumentId={toggleInvalidDocumentId}
157
+ userList={userList}
158
+ states={states}
159
+ />
160
+ </div>
161
+ </div>
162
+ )}
163
+ </Draggable>
164
+ )
165
+ })}
166
+ </div>
124
167
  </div>
125
168
  )
126
169
  }
@@ -0,0 +1,71 @@
1
+ import {useCallback, useContext, useState} from 'react'
2
+ import {createContext} from 'react'
3
+ import {LayoutProps} from 'sanity'
4
+
5
+ import {DEFAULT_CONFIG} from '../constants'
6
+ import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
7
+ import {KeyedMetadata, WorkflowConfig} from '../types'
8
+
9
+ export type WorkflowContextValue = Required<WorkflowConfig> & {
10
+ data: KeyedMetadata
11
+ loading: boolean
12
+ error: boolean | unknown | ProgressEvent
13
+ ids: string[]
14
+ addId: (id: string) => void
15
+ removeId: (id: string) => void
16
+ }
17
+
18
+ const WorkflowContext = createContext<WorkflowContextValue>({
19
+ data: {},
20
+ loading: false,
21
+ error: false,
22
+ ids: [],
23
+ addId: () => null,
24
+ removeId: () => null,
25
+ ...DEFAULT_CONFIG,
26
+ })
27
+
28
+ export function useWorkflowContext(id?: string) {
29
+ const current = useContext(WorkflowContext)
30
+
31
+ return {...current, metadata: id ? current.data[id] : null}
32
+ }
33
+
34
+ type WorkflowProviderProps = LayoutProps & {workflow: Required<WorkflowConfig>}
35
+
36
+ /**
37
+ * This Provider wraps the Studio and provides the workflow context to document actions and badges.
38
+ * This is so individual actions and badges do not need to all register their own listeners.
39
+ * Instead, each document "signals" its ID up to the provider, which then registers a single listener
40
+ * This is performed inside of a component loaded at the root level of the Document Form
41
+ */
42
+ export function WorkflowProvider(props: WorkflowProviderProps) {
43
+ const [ids, setIds] = useState<string[]>([])
44
+ const addId = useCallback(
45
+ (id: string) =>
46
+ setIds((current) => (current.includes(id) ? current : [...current, id])),
47
+ []
48
+ )
49
+ const removeId = useCallback(
50
+ (id: string) => setIds((current) => current.filter((i) => i !== id)),
51
+ []
52
+ )
53
+ const {data, loading, error} = useWorkflowMetadata(ids)
54
+
55
+ return (
56
+ <WorkflowContext.Provider
57
+ value={{
58
+ data,
59
+ loading,
60
+ error,
61
+ ids,
62
+ addId,
63
+ removeId,
64
+ states: props.workflow.states,
65
+ schemaTypes: props.workflow.schemaTypes,
66
+ }}
67
+ >
68
+ {props.renderDefault(props)}
69
+ </WorkflowContext.Provider>
70
+ )
71
+ }
@@ -0,0 +1,30 @@
1
+ import {useEffect} from 'react'
2
+ import {ObjectInputProps} from 'sanity'
3
+
4
+ import {useWorkflowContext} from './WorkflowContext'
5
+
6
+ // This component is loaded at the root level of the Document Form
7
+ // It is used to signal the document ID to the WorkflowProvider
8
+ export default function WorkflowSignal(props: ObjectInputProps) {
9
+ const documentId = props?.value?._id
10
+ ? props.value._id.replace(`drafts.`, ``)
11
+ : null
12
+
13
+ const {addId, removeId} = useWorkflowContext()
14
+
15
+ useEffect(() => {
16
+ // On mount, add to the query of listening documents
17
+ if (documentId) {
18
+ addId(documentId)
19
+ }
20
+
21
+ // On unmount, remove from the query of listening documents
22
+ return () => {
23
+ if (documentId) {
24
+ removeId(documentId)
25
+ }
26
+ }
27
+ }, [documentId, addId, removeId])
28
+
29
+ return props.renderDefault(props)
30
+ }
@@ -401,7 +401,6 @@ export default function WorkflowTool(props: WorkflowToolProps) {
401
401
  : defaultCardTone
402
402
  }
403
403
  height="fill"
404
- paddingTop={1}
405
404
  >
406
405
  {loading ? (
407
406
  <Flex padding={5} align="center" justify="center">
@@ -2,7 +2,7 @@ import {defineStates, WorkflowConfig} from '../types'
2
2
 
3
3
  export const API_VERSION = `2023-01-01`
4
4
 
5
- export const DEFAULT_CONFIG: WorkflowConfig = {
5
+ export const DEFAULT_CONFIG: Required<WorkflowConfig> = {
6
6
  schemaTypes: [],
7
7
  states: defineStates([
8
8
  {
@@ -30,7 +30,7 @@ type WorkflowDocuments = {
30
30
  workflowData: {
31
31
  data: SanityDocumentWithMetadata[]
32
32
  loading: boolean
33
- error: boolean
33
+ error: boolean | unknown | ProgressEvent
34
34
  }
35
35
  operations: {
36
36
  move: (