sanity-plugin-workflow 1.0.0-beta.1 → 1.0.0-beta.4
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/LICENSE +1 -1
- package/README.md +73 -12
- package/lib/{src/index.d.ts → index.d.ts} +3 -3
- package/lib/index.esm.js +1800 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1813 -1
- package/lib/index.js.map +1 -1
- package/package.json +51 -40
- package/src/actions/AssignWorkflow.tsx +48 -0
- package/src/actions/BeginWorkflow.tsx +68 -0
- package/src/actions/CompleteWorkflow.tsx +41 -0
- package/src/actions/RequestReviewAction.js +1 -7
- package/src/actions/UpdateWorkflow.tsx +142 -0
- package/src/badges/AssigneesBadge.tsx +52 -0
- package/src/badges/{index.tsx → StateBadge.tsx} +4 -8
- package/src/components/DocumentCard/AvatarGroup.tsx +12 -8
- package/src/components/DocumentCard/CompleteButton.tsx +53 -0
- package/src/components/DocumentCard/EditButton.tsx +3 -2
- package/src/components/DocumentCard/Field.tsx +38 -0
- package/src/components/DocumentCard/ValidationStatus.tsx +37 -0
- package/src/components/DocumentCard/core/DraftStatus.tsx +32 -0
- package/src/components/DocumentCard/core/PublishedStatus.tsx +39 -0
- package/src/components/DocumentCard/core/TimeAgo.tsx +11 -0
- package/src/components/DocumentCard/index.tsx +156 -50
- package/src/components/DocumentList.tsx +122 -0
- package/src/components/Filters.tsx +168 -0
- package/src/components/FloatingCard.tsx +29 -0
- package/src/components/StateTitle/Status.tsx +27 -0
- package/src/components/StateTitle/index.tsx +73 -0
- package/src/components/UserAssignment.tsx +57 -75
- package/src/components/UserAssignmentInput.tsx +27 -0
- package/src/components/UserDisplay.tsx +57 -0
- package/src/components/Validators.tsx +229 -0
- package/src/components/WorkflowTool.tsx +302 -163
- package/src/constants/index.ts +31 -0
- package/src/helpers/arraysContainMatchingString.ts +6 -0
- package/src/helpers/filterItemsAndSort.ts +41 -0
- package/src/helpers/initialRank.ts +13 -0
- package/src/hooks/useWorkflowDocuments.tsx +62 -70
- package/src/hooks/useWorkflowMetadata.tsx +0 -1
- package/src/index.ts +38 -58
- package/src/schema/workflow/workflow.metadata.ts +68 -0
- package/src/tools/index.ts +15 -0
- package/src/types/index.ts +27 -6
- package/src/actions/DemoteAction.tsx +0 -62
- package/src/actions/PromoteAction.tsx +0 -62
- package/src/components/Mutate.tsx +0 -54
- package/src/components/StateTimeline.tsx +0 -98
- package/src/components/UserSelectInput.tsx +0 -43
- package/src/schema/workflow/metadata.ts +0 -38
|
@@ -1,88 +1,194 @@
|
|
|
1
1
|
/* eslint-disable react/prop-types */
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Flex,
|
|
7
|
-
Popover,
|
|
8
|
-
Stack,
|
|
9
|
-
useClickOutside,
|
|
10
|
-
useTheme,
|
|
11
|
-
} from '@sanity/ui'
|
|
12
|
-
import {AddIcon, DragHandleIcon} from '@sanity/icons'
|
|
13
|
-
import React, {useState} from 'react'
|
|
14
|
-
import {useSchema, SchemaType} from 'sanity'
|
|
15
|
-
import {UserSelectMenu} from 'sanity-plugin-utils'
|
|
2
|
+
import {DragHandleIcon} from '@sanity/icons'
|
|
3
|
+
import {Box, Card, CardTone, Flex, Stack, useTheme} from '@sanity/ui'
|
|
4
|
+
import {useEffect, useMemo} from 'react'
|
|
5
|
+
import {SchemaType, useSchema, useValidationStatus} from 'sanity'
|
|
16
6
|
import {Preview} from 'sanity'
|
|
17
7
|
|
|
8
|
+
import {SanityDocumentWithMetadata, State, User} from '../../types'
|
|
9
|
+
import UserDisplay from '../UserDisplay'
|
|
10
|
+
import CompleteButton from './CompleteButton'
|
|
11
|
+
import {DraftStatus} from './core/DraftStatus'
|
|
12
|
+
import {PublishedStatus} from './core/PublishedStatus'
|
|
18
13
|
import EditButton from './EditButton'
|
|
19
|
-
import {
|
|
20
|
-
import AvatarGroup from './AvatarGroup'
|
|
21
|
-
import UserAssignment from '../UserAssignment'
|
|
14
|
+
import {ValidationStatus} from './ValidationStatus'
|
|
22
15
|
|
|
23
16
|
type DocumentCardProps = {
|
|
24
|
-
|
|
17
|
+
isDragDisabled: boolean
|
|
18
|
+
userRoleCanDrop: boolean
|
|
25
19
|
isDragging: boolean
|
|
26
20
|
item: SanityDocumentWithMetadata
|
|
21
|
+
states: State[]
|
|
22
|
+
toggleInvalidDocumentId: (
|
|
23
|
+
documentId: string,
|
|
24
|
+
action: 'ADD' | 'REMOVE'
|
|
25
|
+
) => void
|
|
26
|
+
userList: User[]
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function DocumentCard(props: DocumentCardProps) {
|
|
30
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
isDragDisabled,
|
|
32
|
+
userRoleCanDrop,
|
|
33
|
+
isDragging,
|
|
34
|
+
item,
|
|
35
|
+
states,
|
|
36
|
+
toggleInvalidDocumentId,
|
|
37
|
+
userList,
|
|
38
|
+
} = props
|
|
31
39
|
const {assignees = [], documentId} = item._metadata ?? {}
|
|
32
40
|
const schema = useSchema()
|
|
33
41
|
|
|
42
|
+
// Perform document operations after State changes
|
|
43
|
+
// If State has changed and the document needs to be un/published
|
|
44
|
+
// This functionality was deemed too dangerous / unexpected
|
|
45
|
+
// Revisit with improved UX
|
|
46
|
+
// const currentState = useMemo(
|
|
47
|
+
// () => states.find((state) => state.id === item._metadata?.state),
|
|
48
|
+
// [states, item]
|
|
49
|
+
// )
|
|
50
|
+
// const ops = useDocumentOperation(documentId ?? ``, item._type)
|
|
51
|
+
// const toast = useToast()
|
|
52
|
+
|
|
53
|
+
// useEffect(() => {
|
|
54
|
+
// const isDraft = item._id.startsWith('drafts.')
|
|
55
|
+
|
|
56
|
+
// if (isDraft && currentState?.operation === 'publish' && !item?._metadata?.optimistic) {
|
|
57
|
+
// if (!ops.publish.disabled) {
|
|
58
|
+
// ops.publish.execute()
|
|
59
|
+
// toast.push({
|
|
60
|
+
// title: 'Published Document',
|
|
61
|
+
// description: documentId,
|
|
62
|
+
// status: 'success',
|
|
63
|
+
// })
|
|
64
|
+
// }
|
|
65
|
+
// } else if (
|
|
66
|
+
// !isDraft &&
|
|
67
|
+
// currentState?.operation === 'unpublish' &&
|
|
68
|
+
// !item?._metadata?.optimistic
|
|
69
|
+
// ) {
|
|
70
|
+
// if (!ops.unpublish.disabled) {
|
|
71
|
+
// ops.unpublish.execute()
|
|
72
|
+
// toast.push({
|
|
73
|
+
// title: 'Unpublished Document',
|
|
74
|
+
// description: documentId,
|
|
75
|
+
// status: 'success',
|
|
76
|
+
// })
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
// }, [currentState, documentId, item, ops, toast])
|
|
80
|
+
|
|
34
81
|
const isDarkMode = useTheme().sanity.color.dark
|
|
35
|
-
const defaultCardTone = isDarkMode ?
|
|
82
|
+
const defaultCardTone = isDarkMode ? `transparent` : `default`
|
|
83
|
+
const {validation = [], isValidating} = useValidationStatus(
|
|
84
|
+
documentId ?? ``,
|
|
85
|
+
item._type
|
|
86
|
+
)
|
|
36
87
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// const [openId, setOpenId] = useState<string | undefined>(``)
|
|
88
|
+
const cardTone = useMemo(() => {
|
|
89
|
+
let tone: CardTone = defaultCardTone
|
|
40
90
|
|
|
41
|
-
|
|
91
|
+
if (!userRoleCanDrop) return isDarkMode ? `default` : `transparent`
|
|
92
|
+
if (!documentId) return tone
|
|
93
|
+
if (isDragging) tone = `positive`
|
|
42
94
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
95
|
+
if (!isValidating && validation.length > 0) {
|
|
96
|
+
if (validation.some((v) => v.level === 'error')) {
|
|
97
|
+
tone = `critical`
|
|
98
|
+
} else {
|
|
99
|
+
tone = `caution`
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return tone
|
|
104
|
+
}, [
|
|
105
|
+
isDarkMode,
|
|
106
|
+
userRoleCanDrop,
|
|
107
|
+
defaultCardTone,
|
|
108
|
+
documentId,
|
|
109
|
+
isDragging,
|
|
110
|
+
validation,
|
|
111
|
+
isValidating,
|
|
112
|
+
])
|
|
113
|
+
|
|
114
|
+
// Update validation status
|
|
115
|
+
// Cannot be done in the above memo because it would set state during render
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!isValidating && validation.length > 0) {
|
|
118
|
+
if (validation.some((v) => v.level === 'error')) {
|
|
119
|
+
toggleInvalidDocumentId(documentId, 'ADD')
|
|
120
|
+
} else {
|
|
121
|
+
toggleInvalidDocumentId(documentId, 'REMOVE')
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
toggleInvalidDocumentId(documentId, 'REMOVE')
|
|
125
|
+
}
|
|
126
|
+
}, [documentId, isValidating, toggleInvalidDocumentId, validation])
|
|
127
|
+
|
|
128
|
+
const hasError = useMemo(
|
|
129
|
+
() => (isValidating ? false : validation.some((v) => v.level === 'error')),
|
|
130
|
+
[isValidating, validation]
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const isLastState = useMemo(
|
|
134
|
+
() => states[states.length - 1].id === item._metadata?.state,
|
|
135
|
+
[states, item._metadata.state]
|
|
136
|
+
)
|
|
48
137
|
|
|
49
138
|
return (
|
|
50
|
-
<Box
|
|
51
|
-
<Card
|
|
52
|
-
radius={2}
|
|
53
|
-
shadow={isDragging ? 3 : 1}
|
|
54
|
-
tone={isDragging ? 'positive' : defaultCardTone}
|
|
55
|
-
>
|
|
139
|
+
<Box paddingBottom={3} paddingX={3}>
|
|
140
|
+
<Card radius={2} shadow={isDragging ? 3 : 1} tone={cardTone}>
|
|
56
141
|
<Stack>
|
|
57
142
|
<Card
|
|
58
143
|
borderBottom
|
|
59
144
|
radius={2}
|
|
60
145
|
padding={3}
|
|
61
146
|
paddingLeft={2}
|
|
62
|
-
tone=
|
|
147
|
+
tone={cardTone}
|
|
63
148
|
style={{pointerEvents: 'none'}}
|
|
64
149
|
>
|
|
65
150
|
<Flex align="center" justify="space-between" gap={1}>
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
151
|
+
<Box flex={1}>
|
|
152
|
+
<Preview
|
|
153
|
+
layout="default"
|
|
154
|
+
value={item}
|
|
155
|
+
schemaType={schema.get(item._type) as SchemaType}
|
|
156
|
+
/>
|
|
157
|
+
</Box>
|
|
158
|
+
<Box style={{flexShrink: 0}}>
|
|
159
|
+
{hasError || isDragDisabled ? null : <DragHandleIcon />}
|
|
160
|
+
</Box>
|
|
72
161
|
</Flex>
|
|
73
162
|
</Card>
|
|
74
163
|
|
|
75
164
|
<Card padding={2} radius={2} tone="inherit">
|
|
76
|
-
<Flex align="center" justify="space-between" gap={
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
165
|
+
<Flex align="center" justify="space-between" gap={3}>
|
|
166
|
+
<Box flex={1}>
|
|
167
|
+
{documentId && (
|
|
168
|
+
<UserDisplay
|
|
169
|
+
userList={userList}
|
|
170
|
+
assignees={assignees}
|
|
171
|
+
documentId={documentId}
|
|
172
|
+
disabled={!userRoleCanDrop}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
</Box>
|
|
176
|
+
{validation.length > 0 ? (
|
|
177
|
+
<ValidationStatus validation={validation} />
|
|
178
|
+
) : null}
|
|
179
|
+
<DraftStatus document={item} />
|
|
180
|
+
<PublishedStatus document={item} />
|
|
181
|
+
<EditButton
|
|
182
|
+
id={item._id}
|
|
183
|
+
type={item._type}
|
|
184
|
+
disabled={!userRoleCanDrop}
|
|
185
|
+
/>
|
|
186
|
+
{isLastState ? (
|
|
187
|
+
<CompleteButton
|
|
81
188
|
documentId={documentId}
|
|
189
|
+
disabled={!userRoleCanDrop}
|
|
82
190
|
/>
|
|
83
|
-
)}
|
|
84
|
-
|
|
85
|
-
<EditButton id={item._id} type={item._type} />
|
|
191
|
+
) : null}
|
|
86
192
|
</Flex>
|
|
87
193
|
</Card>
|
|
88
194
|
</Stack>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {Draggable} from '@hello-pangea/dnd'
|
|
2
|
+
import {useVirtualizer} from '@tanstack/react-virtual'
|
|
3
|
+
import {useMemo, useRef} from 'react'
|
|
4
|
+
import {CurrentUser} from 'sanity'
|
|
5
|
+
import {UserExtended} from 'sanity-plugin-utils'
|
|
6
|
+
|
|
7
|
+
import {filterItemsAndSort} from '../helpers/filterItemsAndSort'
|
|
8
|
+
import {SanityDocumentWithMetadata, State} from '../types'
|
|
9
|
+
import {DocumentCard} from './DocumentCard'
|
|
10
|
+
|
|
11
|
+
type DocumentListProps = {
|
|
12
|
+
data: SanityDocumentWithMetadata[]
|
|
13
|
+
invalidDocumentIds: string[]
|
|
14
|
+
selectedSchemaTypes: string[]
|
|
15
|
+
selectedUserIds: string[]
|
|
16
|
+
state: State
|
|
17
|
+
states: State[]
|
|
18
|
+
toggleInvalidDocumentId: (
|
|
19
|
+
documentId: string,
|
|
20
|
+
action: 'ADD' | 'REMOVE'
|
|
21
|
+
) => void
|
|
22
|
+
user: CurrentUser | null
|
|
23
|
+
userList: UserExtended[]
|
|
24
|
+
userRoleCanDrop: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function DocumentList(props: DocumentListProps) {
|
|
28
|
+
const {
|
|
29
|
+
data = [],
|
|
30
|
+
invalidDocumentIds,
|
|
31
|
+
selectedSchemaTypes,
|
|
32
|
+
selectedUserIds,
|
|
33
|
+
state,
|
|
34
|
+
states,
|
|
35
|
+
toggleInvalidDocumentId,
|
|
36
|
+
user,
|
|
37
|
+
userList,
|
|
38
|
+
userRoleCanDrop,
|
|
39
|
+
} = props
|
|
40
|
+
|
|
41
|
+
const dataFiltered = useMemo(() => {
|
|
42
|
+
return data.length
|
|
43
|
+
? filterItemsAndSort(data, state.id, selectedUserIds, selectedSchemaTypes)
|
|
44
|
+
: []
|
|
45
|
+
}, [data, selectedSchemaTypes, selectedUserIds, state.id])
|
|
46
|
+
|
|
47
|
+
const parentRef = useRef(null)
|
|
48
|
+
|
|
49
|
+
const rowVirtualizer = useVirtualizer({
|
|
50
|
+
count: data.length,
|
|
51
|
+
getScrollElement: () => parentRef.current,
|
|
52
|
+
getItemKey: (index) => dataFiltered[index]?._metadata?.documentId ?? index,
|
|
53
|
+
estimateSize: () => 113,
|
|
54
|
+
overscan: 5,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (!data.length) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
ref={parentRef}
|
|
64
|
+
style={{
|
|
65
|
+
height: `100%`,
|
|
66
|
+
overflow: 'auto',
|
|
67
|
+
paddingTop: 1,
|
|
68
|
+
// Smooths scrollbar behaviour
|
|
69
|
+
overflowAnchor: 'none',
|
|
70
|
+
scrollBehavior: 'auto',
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
{/* {dataFiltered.map((item, itemIndex) => { */}
|
|
74
|
+
{rowVirtualizer.getVirtualItems().map((virtualItem) => {
|
|
75
|
+
const item = dataFiltered[virtualItem.index]
|
|
76
|
+
|
|
77
|
+
const {documentId, assignees} = item?._metadata ?? {}
|
|
78
|
+
|
|
79
|
+
if (!documentId) {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isInvalid = invalidDocumentIds.includes(documentId)
|
|
84
|
+
const meInAssignees = user?.id ? assignees?.includes(user.id) : false
|
|
85
|
+
const isDragDisabled =
|
|
86
|
+
!userRoleCanDrop ||
|
|
87
|
+
isInvalid ||
|
|
88
|
+
!(state.requireAssignment
|
|
89
|
+
? state.requireAssignment && meInAssignees
|
|
90
|
+
: true)
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Draggable
|
|
94
|
+
// The metadata's documentId is always the published one to avoid rerendering
|
|
95
|
+
key={documentId}
|
|
96
|
+
draggableId={documentId}
|
|
97
|
+
index={virtualItem.index}
|
|
98
|
+
isDragDisabled={isDragDisabled}
|
|
99
|
+
>
|
|
100
|
+
{(draggableProvided, draggableSnapshot) => (
|
|
101
|
+
<div
|
|
102
|
+
ref={draggableProvided.innerRef}
|
|
103
|
+
{...draggableProvided.draggableProps}
|
|
104
|
+
{...draggableProvided.dragHandleProps}
|
|
105
|
+
>
|
|
106
|
+
<DocumentCard
|
|
107
|
+
userRoleCanDrop={userRoleCanDrop}
|
|
108
|
+
isDragDisabled={isDragDisabled}
|
|
109
|
+
isDragging={draggableSnapshot.isDragging}
|
|
110
|
+
item={item}
|
|
111
|
+
toggleInvalidDocumentId={toggleInvalidDocumentId}
|
|
112
|
+
userList={userList}
|
|
113
|
+
states={states}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</Draggable>
|
|
118
|
+
)
|
|
119
|
+
})}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {ResetIcon, UserIcon} from '@sanity/icons'
|
|
2
|
+
import {Button, Card, Flex, Menu, MenuButton} from '@sanity/ui'
|
|
3
|
+
import {useCallback} from 'react'
|
|
4
|
+
import {useCurrentUser, UserAvatar, useSchema} from 'sanity'
|
|
5
|
+
import {UserExtended, UserSelectMenu} from 'sanity-plugin-utils'
|
|
6
|
+
|
|
7
|
+
type FiltersProps = {
|
|
8
|
+
uniqueAssignedUsers: UserExtended[]
|
|
9
|
+
selectedUserIds: string[]
|
|
10
|
+
schemaTypes: string[]
|
|
11
|
+
selectedSchemaTypes: string[]
|
|
12
|
+
toggleSelectedUser: (userId: string) => void
|
|
13
|
+
resetSelectedUsers: () => void
|
|
14
|
+
toggleSelectedSchemaType: (schemaType: string) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function Filters(props: FiltersProps) {
|
|
18
|
+
const {
|
|
19
|
+
uniqueAssignedUsers = [],
|
|
20
|
+
selectedUserIds,
|
|
21
|
+
schemaTypes,
|
|
22
|
+
selectedSchemaTypes,
|
|
23
|
+
toggleSelectedUser,
|
|
24
|
+
resetSelectedUsers,
|
|
25
|
+
toggleSelectedSchemaType,
|
|
26
|
+
} = props
|
|
27
|
+
|
|
28
|
+
const currentUser = useCurrentUser()
|
|
29
|
+
const schema = useSchema()
|
|
30
|
+
|
|
31
|
+
const onAdd = useCallback(
|
|
32
|
+
(id: string) => {
|
|
33
|
+
if (!selectedUserIds.includes(id)) {
|
|
34
|
+
toggleSelectedUser(id)
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[selectedUserIds, toggleSelectedUser]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const onRemove = useCallback(
|
|
41
|
+
(id: string) => {
|
|
42
|
+
if (selectedUserIds.includes(id)) {
|
|
43
|
+
toggleSelectedUser(id)
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[selectedUserIds, toggleSelectedUser]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const onClear = useCallback(() => {
|
|
50
|
+
resetSelectedUsers()
|
|
51
|
+
}, [resetSelectedUsers])
|
|
52
|
+
|
|
53
|
+
if (uniqueAssignedUsers.length === 0 && schemaTypes.length < 2) {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const meInUniqueAssignees =
|
|
58
|
+
currentUser?.id && uniqueAssignedUsers.find((u) => u.id === currentUser.id)
|
|
59
|
+
const uniqueAssigneesNotMe = uniqueAssignedUsers.filter(
|
|
60
|
+
(u) => u.id !== currentUser?.id
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Card tone="primary" padding={2} borderBottom style={{overflowX: 'hidden'}}>
|
|
65
|
+
<Flex align="center">
|
|
66
|
+
<Flex align="center" gap={1} flex={1}>
|
|
67
|
+
{uniqueAssignedUsers.length > 5 ? (
|
|
68
|
+
<Card tone="default">
|
|
69
|
+
<MenuButton
|
|
70
|
+
button={
|
|
71
|
+
<Button
|
|
72
|
+
text="Filter Assignees"
|
|
73
|
+
tone="primary"
|
|
74
|
+
icon={UserIcon}
|
|
75
|
+
/>
|
|
76
|
+
}
|
|
77
|
+
id="user-filters"
|
|
78
|
+
menu={
|
|
79
|
+
<Menu>
|
|
80
|
+
<UserSelectMenu
|
|
81
|
+
value={selectedUserIds}
|
|
82
|
+
userList={uniqueAssignedUsers}
|
|
83
|
+
onAdd={onAdd}
|
|
84
|
+
onRemove={onRemove}
|
|
85
|
+
onClear={onClear}
|
|
86
|
+
labels={{
|
|
87
|
+
addMe: 'Filter mine',
|
|
88
|
+
removeMe: 'Clear mine',
|
|
89
|
+
clear: 'Clear filters',
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
</Menu>
|
|
93
|
+
}
|
|
94
|
+
popover={{portal: true}}
|
|
95
|
+
/>
|
|
96
|
+
</Card>
|
|
97
|
+
) : (
|
|
98
|
+
<>
|
|
99
|
+
{meInUniqueAssignees ? (
|
|
100
|
+
<>
|
|
101
|
+
<Button
|
|
102
|
+
padding={0}
|
|
103
|
+
mode={
|
|
104
|
+
selectedUserIds.includes(currentUser.id)
|
|
105
|
+
? `default`
|
|
106
|
+
: `bleed`
|
|
107
|
+
}
|
|
108
|
+
onClick={() => toggleSelectedUser(currentUser.id)}
|
|
109
|
+
>
|
|
110
|
+
<Flex padding={1} align="center" justify="center">
|
|
111
|
+
<UserAvatar user={currentUser.id} size={1} withTooltip />
|
|
112
|
+
</Flex>
|
|
113
|
+
</Button>
|
|
114
|
+
<Card borderRight style={{height: 30}} tone="inherit" />
|
|
115
|
+
</>
|
|
116
|
+
) : null}
|
|
117
|
+
{uniqueAssigneesNotMe.map((user) => (
|
|
118
|
+
<Button
|
|
119
|
+
key={user.id}
|
|
120
|
+
padding={0}
|
|
121
|
+
mode={selectedUserIds.includes(user.id) ? `default` : `bleed`}
|
|
122
|
+
onClick={() => toggleSelectedUser(user.id)}
|
|
123
|
+
>
|
|
124
|
+
<Flex padding={1} align="center" justify="center">
|
|
125
|
+
<UserAvatar user={user} size={1} withTooltip />
|
|
126
|
+
</Flex>
|
|
127
|
+
</Button>
|
|
128
|
+
))}
|
|
129
|
+
|
|
130
|
+
{selectedUserIds.length > 0 ? (
|
|
131
|
+
<Button
|
|
132
|
+
text="Clear"
|
|
133
|
+
onClick={resetSelectedUsers}
|
|
134
|
+
mode="ghost"
|
|
135
|
+
icon={ResetIcon}
|
|
136
|
+
/>
|
|
137
|
+
) : null}
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
</Flex>
|
|
141
|
+
|
|
142
|
+
{schemaTypes.length > 0 ? (
|
|
143
|
+
<Flex align="center" gap={1}>
|
|
144
|
+
{schemaTypes.map((typeName) => {
|
|
145
|
+
const schemaType = schema.get(typeName)
|
|
146
|
+
|
|
147
|
+
if (!schemaType) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Button
|
|
153
|
+
key={typeName}
|
|
154
|
+
text={schemaType?.title ?? typeName}
|
|
155
|
+
icon={schemaType?.icon ?? undefined}
|
|
156
|
+
mode={
|
|
157
|
+
selectedSchemaTypes.includes(typeName) ? `default` : `ghost`
|
|
158
|
+
}
|
|
159
|
+
onClick={() => toggleSelectedSchemaType(typeName)}
|
|
160
|
+
/>
|
|
161
|
+
)
|
|
162
|
+
})}
|
|
163
|
+
</Flex>
|
|
164
|
+
) : null}
|
|
165
|
+
</Flex>
|
|
166
|
+
</Card>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {PropsWithChildren} from 'react'
|
|
2
|
+
import styled, {css} from 'styled-components'
|
|
3
|
+
import {Card, Grid} from '@sanity/ui'
|
|
4
|
+
import {motion, AnimatePresence} from 'framer-motion'
|
|
5
|
+
|
|
6
|
+
const StyledFloatingCard = styled(Card)(
|
|
7
|
+
() => css`
|
|
8
|
+
position: fixed;
|
|
9
|
+
bottom: 0;
|
|
10
|
+
left: 0;
|
|
11
|
+
z-index: 1000;
|
|
12
|
+
`
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
export default function FloatingCard({children}: PropsWithChildren) {
|
|
16
|
+
const childrenHaveValues = Array.isArray(children) ? children.some(Boolean) : Boolean(children)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<AnimatePresence>
|
|
20
|
+
{childrenHaveValues ? (
|
|
21
|
+
<motion.div key="floater" initial={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0}}>
|
|
22
|
+
<StyledFloatingCard shadow={3} padding={3} margin={3} radius={3}>
|
|
23
|
+
<Grid gap={2}>{children}</Grid>
|
|
24
|
+
</StyledFloatingCard>
|
|
25
|
+
</motion.div>
|
|
26
|
+
) : null}
|
|
27
|
+
</AnimatePresence>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Box, Text, Tooltip} from '@sanity/ui'
|
|
3
|
+
|
|
4
|
+
type StatusProps = {
|
|
5
|
+
text: string
|
|
6
|
+
icon: React.ComponentType
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Status(props: StatusProps) {
|
|
10
|
+
const {text, icon} = props
|
|
11
|
+
const Icon = icon
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Tooltip
|
|
15
|
+
portal
|
|
16
|
+
content={
|
|
17
|
+
<Box padding={2}>
|
|
18
|
+
<Text size={1}>{text}</Text>
|
|
19
|
+
</Box>
|
|
20
|
+
}
|
|
21
|
+
>
|
|
22
|
+
<Text size={1}>
|
|
23
|
+
<Icon />
|
|
24
|
+
</Text>
|
|
25
|
+
</Tooltip>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {Flex, Card, Badge, BadgeTone} from '@sanity/ui'
|
|
2
|
+
import {InfoOutlineIcon, UserIcon} from '@sanity/icons'
|
|
3
|
+
import styled, {css} from 'styled-components'
|
|
4
|
+
|
|
5
|
+
import {Status} from './Status'
|
|
6
|
+
import {
|
|
7
|
+
// Operation,
|
|
8
|
+
State,
|
|
9
|
+
} from '../../types'
|
|
10
|
+
|
|
11
|
+
type StateTitleProps = {
|
|
12
|
+
state: State
|
|
13
|
+
requireAssignment: boolean
|
|
14
|
+
userRoleCanDrop: boolean
|
|
15
|
+
isDropDisabled: boolean
|
|
16
|
+
draggingFrom: string
|
|
17
|
+
// operation?: Operation
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const StyledStickyCard = styled(Card)(
|
|
21
|
+
() => css`
|
|
22
|
+
position: sticky;
|
|
23
|
+
top: 0;
|
|
24
|
+
z-index: 1;
|
|
25
|
+
`
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
export default function StateTitle(props: StateTitleProps) {
|
|
29
|
+
const {state, requireAssignment, userRoleCanDrop, isDropDisabled, draggingFrom} = props
|
|
30
|
+
|
|
31
|
+
let tone: BadgeTone = 'default'
|
|
32
|
+
const isSource = draggingFrom === state.id
|
|
33
|
+
|
|
34
|
+
if (draggingFrom) {
|
|
35
|
+
tone = isDropDisabled || isSource ? 'default' : 'positive'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<StyledStickyCard paddingY={4} padding={3} tone="inherit">
|
|
40
|
+
<Flex gap={3} align="center">
|
|
41
|
+
<Badge
|
|
42
|
+
mode={(draggingFrom && !isDropDisabled) || isSource ? 'default' : 'outline'}
|
|
43
|
+
tone={tone}
|
|
44
|
+
muted={!userRoleCanDrop || isDropDisabled}
|
|
45
|
+
>
|
|
46
|
+
{state.title}
|
|
47
|
+
</Badge>
|
|
48
|
+
{userRoleCanDrop ? null : (
|
|
49
|
+
<Status
|
|
50
|
+
text="You do not have permissions to move documents to this State"
|
|
51
|
+
icon={InfoOutlineIcon}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
{requireAssignment ? (
|
|
55
|
+
<Status
|
|
56
|
+
text="You must be assigned to the document to move documents to this State"
|
|
57
|
+
icon={UserIcon}
|
|
58
|
+
/>
|
|
59
|
+
) : null}
|
|
60
|
+
{/* {operation ? (
|
|
61
|
+
<Status
|
|
62
|
+
text={
|
|
63
|
+
operation === 'publish'
|
|
64
|
+
? `A document moved to this State will also publish the current Draft`
|
|
65
|
+
: `A document moved to this State will also unpublish the current Published version`
|
|
66
|
+
}
|
|
67
|
+
icon={operation === 'publish' ? PublishIcon : UnpublishIcon}
|
|
68
|
+
/>
|
|
69
|
+
) : null} */}
|
|
70
|
+
</Flex>
|
|
71
|
+
</StyledStickyCard>
|
|
72
|
+
)
|
|
73
|
+
}
|