@sanity/hierarchical-document-list 2.1.2 → 3.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/LICENSE +1 -1
- package/README.md +30 -30
- package/dist/index.d.ts +170 -195
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +835 -6031
- package/dist/index.js.map +1 -1
- package/package.json +38 -77
- package/dist/index.d.mts +0 -240
- package/dist/index.mjs +0 -6433
- package/dist/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/TreeDeskStructure.tsx +0 -80
- package/src/TreeInputComponent.tsx +0 -41
- package/src/components/DeskWarning.tsx +0 -40
- package/src/components/DocumentInNode.tsx +0 -133
- package/src/components/DocumentPreviewStatus.tsx +0 -70
- package/src/components/NodeActions.tsx +0 -85
- package/src/components/NodeContentRenderer.tsx +0 -141
- package/src/components/PlaceholderDropzone.tsx +0 -45
- package/src/components/TreeEditor.tsx +0 -184
- package/src/components/TreeEditorErrorBoundary.tsx +0 -14
- package/src/components/TreeNodeRenderer.tsx +0 -37
- package/src/components/TreeNodeRendererScaffold.tsx +0 -193
- package/src/createDeskHierarchy.tsx +0 -110
- package/src/createHierarchicalSchemas.tsx +0 -151
- package/src/hooks/useAllItems.ts +0 -119
- package/src/hooks/useLocalTree.ts +0 -40
- package/src/hooks/useTreeOperations.ts +0 -25
- package/src/hooks/useTreeOperationsProvider.ts +0 -86
- package/src/index.ts +0 -25
- package/src/schemas/hierarchy.tree.ts +0 -19
- package/src/types.ts +0 -148
- package/src/utils/flatDataToTree.ts +0 -20
- package/src/utils/getAdjescentNodes.ts +0 -30
- package/src/utils/getCommonTreeProps.tsx +0 -28
- package/src/utils/getTreeHeight.ts +0 -8
- package/src/utils/gradientPatchAdapter.ts +0 -43
- package/src/utils/idUtils.ts +0 -7
- package/src/utils/injectNodeTypeInPatches.ts +0 -60
- package/src/utils/moveItemInArray.ts +0 -26
- package/src/utils/throwError.ts +0 -9
- package/src/utils/treeData.tsx +0 -119
- package/src/utils/treePatches.ts +0 -171
- package/v2-incompatible.js +0 -11
package/sanity.json
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import {Box, Flex, Spinner} from '@sanity/ui'
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
import {PatchEvent, useDocumentOperation, useEditState} from 'sanity'
|
|
4
|
-
import DeskWarning from './components/DeskWarning'
|
|
5
|
-
import TreeEditor from './components/TreeEditor'
|
|
6
|
-
import {DocumentOperations, StoredTreeItem, TreeDeskStructureProps} from './types'
|
|
7
|
-
import {toGradient} from './utils/gradientPatchAdapter'
|
|
8
|
-
import injectNodeTypeInPatches, {DEFAULT_DOC_TYPE} from './utils/injectNodeTypeInPatches'
|
|
9
|
-
|
|
10
|
-
interface ComponentProps {
|
|
11
|
-
options: TreeDeskStructureProps
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const DEFAULT_FIELD_KEY = 'tree'
|
|
15
|
-
|
|
16
|
-
const TreeDeskStructure: React.FC<ComponentProps> = (props) => {
|
|
17
|
-
const treeDocType = props.options.documentType || DEFAULT_DOC_TYPE
|
|
18
|
-
const treeFieldKey = props.options.fieldKeyInDocument || DEFAULT_FIELD_KEY
|
|
19
|
-
const {published, draft, liveEdit} = useEditState(props.options.documentId, treeDocType)
|
|
20
|
-
const {patch} = useDocumentOperation(props.options.documentId, treeDocType) as DocumentOperations
|
|
21
|
-
|
|
22
|
-
const treeValue = (published?.[treeFieldKey] || []) as StoredTreeItem[]
|
|
23
|
-
|
|
24
|
-
const handleChange = React.useCallback(
|
|
25
|
-
(patchEvent: PatchEvent) => {
|
|
26
|
-
if (!patch?.execute) {
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
patch.execute(toGradient(injectNodeTypeInPatches(patchEvent.patches, treeDocType)))
|
|
30
|
-
},
|
|
31
|
-
[patch]
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
React.useEffect(() => {
|
|
35
|
-
if (!published?._id && patch?.execute && !patch?.disabled) {
|
|
36
|
-
// If no published document, create it
|
|
37
|
-
patch.execute([{setIfMissing: {[treeFieldKey]: []}}])
|
|
38
|
-
}
|
|
39
|
-
}, [published?._id, patch])
|
|
40
|
-
|
|
41
|
-
if (!liveEdit) {
|
|
42
|
-
return (
|
|
43
|
-
<DeskWarning
|
|
44
|
-
title="Invalid configuration"
|
|
45
|
-
subtitle="The `documentType` passed to `createDeskHiearchy` isn't live editable. \nTo continue using this plugin, add `liveEdit: true` to your custom schema type or unset `documentType` in your hierarchy configuration."
|
|
46
|
-
/>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (draft?._id) {
|
|
51
|
-
return (
|
|
52
|
-
<DeskWarning
|
|
53
|
-
title="This hierarchy tree contains a draft"
|
|
54
|
-
subtitle="Click on the button below to publish your draft in order to continue editing the live
|
|
55
|
-
published document."
|
|
56
|
-
/>
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!published?._id) {
|
|
61
|
-
return (
|
|
62
|
-
<Flex padding={5} align={'center'} justify={'center'} height={'fill'}>
|
|
63
|
-
<Spinner width={4} muted />
|
|
64
|
-
</Flex>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<Box paddingBottom={5} paddingRight={2}>
|
|
70
|
-
<TreeEditor
|
|
71
|
-
options={props.options}
|
|
72
|
-
tree={treeValue}
|
|
73
|
-
onChange={handleChange}
|
|
74
|
-
patchPrefix={treeFieldKey}
|
|
75
|
-
/>
|
|
76
|
-
</Box>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export default TreeDeskStructure
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import {FormField, FormNodePresence, PatchEvent, Path} from 'sanity'
|
|
3
|
-
import TreeEditor from './components/TreeEditor'
|
|
4
|
-
import {StoredTreeItem, TreeFieldSchema} from './types'
|
|
5
|
-
import injectNodeTypeInPatches, {DEFAULT_DOC_TYPE} from './utils/injectNodeTypeInPatches'
|
|
6
|
-
|
|
7
|
-
export interface TreeInputComponentProps {
|
|
8
|
-
type: TreeFieldSchema
|
|
9
|
-
value: StoredTreeItem[]
|
|
10
|
-
level: number
|
|
11
|
-
onChange: (event: unknown) => void
|
|
12
|
-
onFocus: (path: Path) => void
|
|
13
|
-
onBlur: () => void
|
|
14
|
-
focusPath: Path
|
|
15
|
-
readOnly: boolean
|
|
16
|
-
presence: FormNodePresence[]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const TreeInputComponent: React.FC<TreeInputComponentProps> = (props) => {
|
|
20
|
-
const documentType = props.type.options.documentType || DEFAULT_DOC_TYPE
|
|
21
|
-
|
|
22
|
-
const onChange = React.useCallback(
|
|
23
|
-
(patch: any) => {
|
|
24
|
-
const patches = injectNodeTypeInPatches(patch?.patches, documentType)
|
|
25
|
-
props.onChange(new PatchEvent(patches))
|
|
26
|
-
},
|
|
27
|
-
[props.onChange]
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<FormField
|
|
32
|
-
description={props.type.description} // Creates description from schema
|
|
33
|
-
title={props.type.title} // Creates label from schema title
|
|
34
|
-
__unstable_presence={props.presence} // Handles presence avatars
|
|
35
|
-
>
|
|
36
|
-
<TreeEditor options={props.type.options} tree={props.value || []} onChange={onChange} />
|
|
37
|
-
</FormField>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default TreeInputComponent
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import {Box, Card, Container, Heading, Stack, Text} from '@sanity/ui'
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
|
|
4
|
-
// React component that wraps text between two delimiters in a <pre> tag
|
|
5
|
-
|
|
6
|
-
const WrapCodeBlocks: React.FC<{text: string}> = ({text}) => {
|
|
7
|
-
return (
|
|
8
|
-
<>
|
|
9
|
-
{text.split('`').map((part, i) => (
|
|
10
|
-
<React.Fragment key={i}>{i % 2 === 0 ? part : <code>{part}</code>}</React.Fragment>
|
|
11
|
-
))}
|
|
12
|
-
</>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const DeskWarning: React.FC<
|
|
17
|
-
React.PropsWithChildren<{
|
|
18
|
-
title: string
|
|
19
|
-
subtitle?: string
|
|
20
|
-
}>
|
|
21
|
-
> = (props) => {
|
|
22
|
-
return (
|
|
23
|
-
<Container padding={5} style={{maxWidth: '25rem'}} sizing={'content'}>
|
|
24
|
-
<Card padding={4} border radius={2} width={0} tone="caution">
|
|
25
|
-
<Stack space={3}>
|
|
26
|
-
<Heading size={1}>{props.title}</Heading>
|
|
27
|
-
{props.subtitle &&
|
|
28
|
-
props.subtitle.split('\\n').map((line: string) => (
|
|
29
|
-
<Text size={1}>
|
|
30
|
-
<WrapCodeBlocks text={line} />
|
|
31
|
-
</Text>
|
|
32
|
-
))}
|
|
33
|
-
{props.children && <Box marginTop={2}>{props.children}</Box>}
|
|
34
|
-
</Stack>
|
|
35
|
-
</Card>
|
|
36
|
-
</Container>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export default DeskWarning
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import {HelpCircleIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Card, Flex, Stack, Text, Tooltip} from '@sanity/ui'
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import {Preview, SanityDocument, SchemaType, TextWithTone, useSchema} from 'sanity'
|
|
5
|
-
import {RouterPaneGroup, usePaneRouter} from 'sanity/desk'
|
|
6
|
-
import useTreeOperations from '../hooks/useTreeOperations'
|
|
7
|
-
import {LocalTreeItem} from '../types'
|
|
8
|
-
import DocumentPreviewStatus from './DocumentPreviewStatus'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Renders a preview for each referenced document.
|
|
12
|
-
* Nested inside TreeNode.tsx
|
|
13
|
-
*/
|
|
14
|
-
const DocumentInNode: React.FC<{
|
|
15
|
-
item: LocalTreeItem
|
|
16
|
-
action?: React.ReactNode
|
|
17
|
-
}> = (props) => {
|
|
18
|
-
const {value: {reference, docType} = {}, draftId, publishedId} = props.item
|
|
19
|
-
const {routerPanesState, ChildLink} = usePaneRouter()
|
|
20
|
-
const {allItemsStatus} = useTreeOperations()
|
|
21
|
-
const schema = useSchema()
|
|
22
|
-
const isActive = React.useMemo(() => {
|
|
23
|
-
// If some pane is active with the current document `_id`, it's active
|
|
24
|
-
return routerPanesState.some((pane: RouterPaneGroup) =>
|
|
25
|
-
pane.some((group) => group.id === reference?._ref)
|
|
26
|
-
)
|
|
27
|
-
}, [routerPanesState])
|
|
28
|
-
|
|
29
|
-
const type = React.useMemo(() => {
|
|
30
|
-
return docType ? schema.get(docType) : undefined
|
|
31
|
-
}, [docType])
|
|
32
|
-
|
|
33
|
-
const LinkComponent = React.useMemo(
|
|
34
|
-
() =>
|
|
35
|
-
React.forwardRef((linkProps: any, ref: any) => (
|
|
36
|
-
<ChildLink
|
|
37
|
-
{...linkProps}
|
|
38
|
-
childId={reference?._ref}
|
|
39
|
-
ref={ref}
|
|
40
|
-
childParameters={{
|
|
41
|
-
type: docType
|
|
42
|
-
}}
|
|
43
|
-
/>
|
|
44
|
-
)),
|
|
45
|
-
[ChildLink, reference?._ref]
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
if (!reference?._ref) {
|
|
49
|
-
return null
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<Flex gap={2} align="center" style={{flex: 1}}>
|
|
54
|
-
{/* Show loading preview while allItems aren't ready */}
|
|
55
|
-
{publishedId || allItemsStatus !== 'success' ? (
|
|
56
|
-
/* Card loosely copied from @sanity/desk-tool's PaneItem.tsx */
|
|
57
|
-
<Card
|
|
58
|
-
__unstable_focusRing
|
|
59
|
-
as={LinkComponent}
|
|
60
|
-
tone={isActive ? 'primary' : 'default'}
|
|
61
|
-
padding={1}
|
|
62
|
-
radius={2}
|
|
63
|
-
flex={1}
|
|
64
|
-
data-as="a"
|
|
65
|
-
data-ui="PaneItem"
|
|
66
|
-
>
|
|
67
|
-
<Preview
|
|
68
|
-
layout="default"
|
|
69
|
-
schemaType={type as SchemaType}
|
|
70
|
-
value={{_ref: draftId || reference?._ref}}
|
|
71
|
-
status={
|
|
72
|
-
<DocumentPreviewStatus
|
|
73
|
-
draft={
|
|
74
|
-
draftId
|
|
75
|
-
? ({
|
|
76
|
-
_id: draftId,
|
|
77
|
-
_type: docType,
|
|
78
|
-
_updatedAt: props.item.draftUpdatedAt
|
|
79
|
-
} as SanityDocument)
|
|
80
|
-
: undefined
|
|
81
|
-
}
|
|
82
|
-
published={
|
|
83
|
-
{
|
|
84
|
-
_id: reference?._ref,
|
|
85
|
-
_type: docType,
|
|
86
|
-
_updatedAt: props.item.publishedUpdatedAt
|
|
87
|
-
} as SanityDocument
|
|
88
|
-
}
|
|
89
|
-
/>
|
|
90
|
-
}
|
|
91
|
-
/>
|
|
92
|
-
</Card>
|
|
93
|
-
) : (
|
|
94
|
-
<Card padding={3} radius={1} flex={1}>
|
|
95
|
-
<Flex align="center">
|
|
96
|
-
<Text size={2} muted style={{flex: 1}}>
|
|
97
|
-
Invalid document
|
|
98
|
-
</Text>
|
|
99
|
-
<Tooltip
|
|
100
|
-
placement="left"
|
|
101
|
-
portal
|
|
102
|
-
content={
|
|
103
|
-
<Box padding={3}>
|
|
104
|
-
<Flex align="flex-start" gap={3}>
|
|
105
|
-
<TextWithTone tone="default" size={3}>
|
|
106
|
-
<HelpCircleIcon />
|
|
107
|
-
</TextWithTone>
|
|
108
|
-
<Stack space={3}>
|
|
109
|
-
<Text as="h2" size={1} weight="semibold">
|
|
110
|
-
This document is not valid
|
|
111
|
-
</Text>
|
|
112
|
-
{/* <Text size={1}>
|
|
113
|
-
It was deleted or it doesn't match the filters set by this hierarchy.
|
|
114
|
-
</Text> */}
|
|
115
|
-
<Text size={1}>ID: {reference?._ref}</Text>
|
|
116
|
-
</Stack>
|
|
117
|
-
</Flex>
|
|
118
|
-
</Box>
|
|
119
|
-
}
|
|
120
|
-
>
|
|
121
|
-
<TextWithTone tone="default" size={2}>
|
|
122
|
-
<HelpCircleIcon />
|
|
123
|
-
</TextWithTone>
|
|
124
|
-
</Tooltip>
|
|
125
|
-
</Flex>
|
|
126
|
-
</Card>
|
|
127
|
-
)}
|
|
128
|
-
{props.action}
|
|
129
|
-
</Flex>
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export default DocumentInNode
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import {EditIcon, PublishIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Inline, Text, Tooltip} from '@sanity/ui'
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import {SanityDocument, TextWithTone, useTimeAgo} from 'sanity'
|
|
5
|
-
import {DocumentPair} from '../types'
|
|
6
|
-
|
|
7
|
-
export function TimeAgo({time}: {time: string | Date}) {
|
|
8
|
-
const timeAgo = useTimeAgo(time)
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<span title={timeAgo}>
|
|
12
|
-
{timeAgo}
|
|
13
|
-
{timeAgo.toLowerCase().trim().startsWith('just now') ? '' : ' ago'}
|
|
14
|
-
</span>
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const PublishedStatus = ({document}: {document?: SanityDocument | null}) => (
|
|
19
|
-
<Tooltip
|
|
20
|
-
portal
|
|
21
|
-
content={
|
|
22
|
-
<Box padding={2}>
|
|
23
|
-
<Text size={1}>
|
|
24
|
-
{document ? (
|
|
25
|
-
<>Published {document._updatedAt && <TimeAgo time={document._updatedAt} />}</>
|
|
26
|
-
) : (
|
|
27
|
-
<>Not published</>
|
|
28
|
-
)}
|
|
29
|
-
</Text>
|
|
30
|
-
</Box>
|
|
31
|
-
}
|
|
32
|
-
>
|
|
33
|
-
<TextWithTone tone="positive" dimmed={!document} muted={!document} size={1}>
|
|
34
|
-
<PublishIcon />
|
|
35
|
-
</TextWithTone>
|
|
36
|
-
</Tooltip>
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
const DraftStatus = ({document}: {document?: SanityDocument | null}) => (
|
|
40
|
-
<Tooltip
|
|
41
|
-
portal
|
|
42
|
-
content={
|
|
43
|
-
<Box padding={2}>
|
|
44
|
-
<Text size={1}>
|
|
45
|
-
{document ? (
|
|
46
|
-
<>Edited {document?._updatedAt && <TimeAgo time={document?._updatedAt} />}</>
|
|
47
|
-
) : (
|
|
48
|
-
<>No unpublished edits</>
|
|
49
|
-
)}
|
|
50
|
-
</Text>
|
|
51
|
-
</Box>
|
|
52
|
-
}
|
|
53
|
-
>
|
|
54
|
-
<TextWithTone tone="caution" dimmed={!document} muted={!document} size={1}>
|
|
55
|
-
<EditIcon />
|
|
56
|
-
</TextWithTone>
|
|
57
|
-
</Tooltip>
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
// Adapted from @sanity\desk-tool\src\components\paneItem\helpers.tsx
|
|
61
|
-
const DocumentPreviewStatus: React.FC<DocumentPair> = ({draft, published}) => {
|
|
62
|
-
return (
|
|
63
|
-
<Inline space={4}>
|
|
64
|
-
<PublishedStatus document={published} />
|
|
65
|
-
<DraftStatus document={draft} />
|
|
66
|
-
</Inline>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export default DocumentPreviewStatus
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import {CopyIcon, EllipsisVerticalIcon, LaunchIcon, RemoveCircleIcon} from '@sanity/icons'
|
|
2
|
-
import {Button, Menu, MenuButton, MenuDivider, MenuItem} from '@sanity/ui'
|
|
3
|
-
import * as React from 'react'
|
|
4
|
-
import {IntentButton as IntentLink} from 'sanity'
|
|
5
|
-
import useTreeOperations from '../hooks/useTreeOperations'
|
|
6
|
-
import {NodeProps} from '../types'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Applicable only to nodes inside the main tree.
|
|
10
|
-
* Unadded items have their actions defined in TreeEditor.
|
|
11
|
-
*/
|
|
12
|
-
const NodeActions: React.FC<{nodeProps: NodeProps}> = ({nodeProps}) => {
|
|
13
|
-
const operations = useTreeOperations()
|
|
14
|
-
const {node} = nodeProps
|
|
15
|
-
const {reference, docType} = node?.value || {}
|
|
16
|
-
|
|
17
|
-
// Adapted from @sanity\form-builder\src\inputs\ReferenceInput\ArrayItemReferenceInput.tsx
|
|
18
|
-
const OpenLink = React.useMemo(
|
|
19
|
-
() =>
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
21
|
-
React.forwardRef(function OpenLinkInner(
|
|
22
|
-
restProps: React.ComponentProps<typeof IntentLink>,
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
24
|
-
_ref: React.ForwardedRef<HTMLAnchorElement>
|
|
25
|
-
) {
|
|
26
|
-
return (
|
|
27
|
-
<IntentLink
|
|
28
|
-
{...restProps}
|
|
29
|
-
intent="edit"
|
|
30
|
-
params={{id: reference?._ref, type: docType}}
|
|
31
|
-
target="_blank"
|
|
32
|
-
rel="noopener noreferrer"
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}),
|
|
36
|
-
[reference?._ref, docType]
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
const isValid = !!node.publishedId
|
|
40
|
-
return (
|
|
41
|
-
<MenuButton
|
|
42
|
-
button={<Button padding={2} mode="bleed" icon={EllipsisVerticalIcon} />}
|
|
43
|
-
id={`hiearchical-doc-list--${node._key}-menuButton`}
|
|
44
|
-
menu={
|
|
45
|
-
<Menu>
|
|
46
|
-
<MenuItem
|
|
47
|
-
text="Remove from list"
|
|
48
|
-
tone="critical"
|
|
49
|
-
icon={RemoveCircleIcon}
|
|
50
|
-
onClick={() => operations.removeItem(nodeProps)}
|
|
51
|
-
/>
|
|
52
|
-
<MenuItem
|
|
53
|
-
text="Duplicate item"
|
|
54
|
-
icon={CopyIcon}
|
|
55
|
-
disabled={!isValid}
|
|
56
|
-
onClick={() => operations.duplicateItem(nodeProps)}
|
|
57
|
-
/>
|
|
58
|
-
{/* <MenuItem
|
|
59
|
-
text="Move up"
|
|
60
|
-
icon={ArrowUpIcon}
|
|
61
|
-
disabled={!isValid}
|
|
62
|
-
onClick={() => operations.moveItemUp(nodeProps)}
|
|
63
|
-
/>
|
|
64
|
-
<MenuItem
|
|
65
|
-
text="Move down"
|
|
66
|
-
icon={ArrowDownIcon}
|
|
67
|
-
disabled={!isValid}
|
|
68
|
-
onClick={() => operations.moveItemDown(nodeProps)}
|
|
69
|
-
/> */}
|
|
70
|
-
<MenuDivider />
|
|
71
|
-
<MenuItem
|
|
72
|
-
text="Open in new tab"
|
|
73
|
-
icon={LaunchIcon}
|
|
74
|
-
disabled={!isValid}
|
|
75
|
-
as={OpenLink}
|
|
76
|
-
data-as="a"
|
|
77
|
-
/>
|
|
78
|
-
</Menu>
|
|
79
|
-
}
|
|
80
|
-
popover={{portal: true, tone: 'default', placement: 'right'}}
|
|
81
|
-
/>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export default NodeActions
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import {isDescendant} from '@nosferatu500/react-sortable-tree'
|
|
2
|
-
import {cyan, gray, red} from '@sanity/color'
|
|
3
|
-
import {ChevronDownIcon, ChevronRightIcon, DragHandleIcon} from '@sanity/icons'
|
|
4
|
-
import {Box, Button, Flex, Spinner} from '@sanity/ui'
|
|
5
|
-
import * as React from 'react'
|
|
6
|
-
import styled from 'styled-components'
|
|
7
|
-
|
|
8
|
-
const Root = styled.div`
|
|
9
|
-
// Adapted from react-sortable-tree/style.css
|
|
10
|
-
&[data-landing='true'] > *,
|
|
11
|
-
&[data-cancel='true'] > * {
|
|
12
|
-
opacity: 0 !important;
|
|
13
|
-
}
|
|
14
|
-
&[data-landing='true']::before,
|
|
15
|
-
&[data-cancel='true']::before {
|
|
16
|
-
background-color: ${cyan[50].hex};
|
|
17
|
-
border: 2px dashed ${gray[400].hex};
|
|
18
|
-
border-radius: 3px;
|
|
19
|
-
content: '';
|
|
20
|
-
position: absolute;
|
|
21
|
-
top: 0;
|
|
22
|
-
right: 0;
|
|
23
|
-
bottom: 0;
|
|
24
|
-
left: 0;
|
|
25
|
-
z-index: -1;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
&[data-cancel='true']::before {
|
|
29
|
-
background-color: ${red[50].hex};
|
|
30
|
-
}
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Customization of react-sortable-tree's default node.
|
|
35
|
-
* Created in order to use Sanity UI for styles.
|
|
36
|
-
* Reference: https://github.com/frontend-collective/react-sortable-tree/blob/master/src/node-renderer-default.js
|
|
37
|
-
*/
|
|
38
|
-
const NodeContentRenderer: any = (props: any) => {
|
|
39
|
-
const {node, path, treeIndex, canDrag = false} = props
|
|
40
|
-
const nodeTitle = node.title
|
|
41
|
-
const Handle = React.useMemo(() => {
|
|
42
|
-
if (!canDrag) {
|
|
43
|
-
return null
|
|
44
|
-
}
|
|
45
|
-
if (typeof node.children === 'function' && node.expanded) {
|
|
46
|
-
// Show a loading symbol on the handle when the children are expanded
|
|
47
|
-
// and yet still defined by a function (a callback to fetch the children)
|
|
48
|
-
return <Spinner />
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const BtnElement = (
|
|
52
|
-
<div>
|
|
53
|
-
<Button
|
|
54
|
-
mode="bleed"
|
|
55
|
-
paddingX={0}
|
|
56
|
-
paddingY={1}
|
|
57
|
-
style={{
|
|
58
|
-
cursor: node.publishedId ? 'grab' : 'default',
|
|
59
|
-
fontSize: '1.5625rem'
|
|
60
|
-
}}
|
|
61
|
-
data-ui="DragHandleButton"
|
|
62
|
-
data-drag-handle={canDrag}
|
|
63
|
-
disabled={!node.publishedId}
|
|
64
|
-
>
|
|
65
|
-
<DragHandleIcon style={{marginBottom: '-0.1em'}} />
|
|
66
|
-
</Button>
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
// Don't allow editors to drag invalid documents
|
|
71
|
-
if (!node.publishedId) {
|
|
72
|
-
return BtnElement
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Show the handle used to initiate a drag-and-drop
|
|
76
|
-
return props.connectDragSource(BtnElement, {
|
|
77
|
-
dropEffect: 'copy'
|
|
78
|
-
})
|
|
79
|
-
}, [canDrag, node, typeof node.children === 'function'])
|
|
80
|
-
|
|
81
|
-
const isDraggedDescendant = props.draggedNode && isDescendant(props.draggedNode, node)
|
|
82
|
-
const isLandingPadActive = !props.didDrop && props.isDragging
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<Box style={{position: 'relative'}}>
|
|
86
|
-
{props.toggleChildrenVisibility &&
|
|
87
|
-
node.children &&
|
|
88
|
-
(node.children.length > 0 || typeof node.children === 'function') && (
|
|
89
|
-
<div
|
|
90
|
-
style={{
|
|
91
|
-
position: 'absolute',
|
|
92
|
-
left: '-2px',
|
|
93
|
-
top: '40%',
|
|
94
|
-
transform: 'translate(-100%, -50%)'
|
|
95
|
-
}}
|
|
96
|
-
>
|
|
97
|
-
<Button
|
|
98
|
-
aria-label={node.expanded ? 'Collapse' : 'Expand'}
|
|
99
|
-
icon={
|
|
100
|
-
node.expanded ? (
|
|
101
|
-
<ChevronDownIcon color={gray[200].hex} />
|
|
102
|
-
) : (
|
|
103
|
-
<ChevronRightIcon color={gray[200].hex} />
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
mode="bleed"
|
|
107
|
-
fontSize={2}
|
|
108
|
-
padding={1}
|
|
109
|
-
type="button"
|
|
110
|
-
onClick={() =>
|
|
111
|
-
props.toggleChildrenVisibility?.({
|
|
112
|
-
node,
|
|
113
|
-
path,
|
|
114
|
-
treeIndex
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
|
-
|
|
121
|
-
{props.connectDragPreview(
|
|
122
|
-
<div>
|
|
123
|
-
<Root
|
|
124
|
-
data-landing={isLandingPadActive}
|
|
125
|
-
data-cancel={isLandingPadActive && !props.canDrop}
|
|
126
|
-
style={{
|
|
127
|
-
opacity: isDraggedDescendant ? 0.5 : 1
|
|
128
|
-
}}
|
|
129
|
-
>
|
|
130
|
-
<Flex align="center">
|
|
131
|
-
{Handle}
|
|
132
|
-
{typeof nodeTitle === 'function' ? nodeTitle(props) : nodeTitle}
|
|
133
|
-
</Flex>
|
|
134
|
-
</Root>
|
|
135
|
-
</div>
|
|
136
|
-
)}
|
|
137
|
-
</Box>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export default NodeContentRenderer
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {Box, Card, CardTone, Stack, Text} from '@sanity/ui'
|
|
2
|
-
import * as React from 'react'
|
|
3
|
-
|
|
4
|
-
const PlaceholderDropzone: React.FC<
|
|
5
|
-
{
|
|
6
|
-
tone?: CardTone
|
|
7
|
-
title: string
|
|
8
|
-
subtitle?: string
|
|
9
|
-
} & any
|
|
10
|
-
> = (props) => {
|
|
11
|
-
const isValid = props.isOver && props.canDrop
|
|
12
|
-
const isInvalid = props.isOver && !props.canDrop
|
|
13
|
-
let tone: CardTone = 'transparent'
|
|
14
|
-
if (isValid) {
|
|
15
|
-
tone = 'positive'
|
|
16
|
-
}
|
|
17
|
-
if (isInvalid) {
|
|
18
|
-
tone = 'caution'
|
|
19
|
-
}
|
|
20
|
-
return (
|
|
21
|
-
<Box padding={3}>
|
|
22
|
-
<Card
|
|
23
|
-
padding={5}
|
|
24
|
-
radius={2}
|
|
25
|
-
border
|
|
26
|
-
tone={tone}
|
|
27
|
-
style={{
|
|
28
|
-
borderStyle: props.isOver ? undefined : 'dashed',
|
|
29
|
-
}}
|
|
30
|
-
>
|
|
31
|
-
<Stack space={2} style={{textAlign: 'center'}}>
|
|
32
|
-
<Text size={2} as="h2" muted>
|
|
33
|
-
{!props.isOver && props.title}
|
|
34
|
-
{isValid && 'Drop here'}
|
|
35
|
-
{isInvalid && 'Invalid location or element'}
|
|
36
|
-
</Text>
|
|
37
|
-
{props.subtitle && <Text size={1}>{props.subtitle}</Text>}
|
|
38
|
-
{props.children}
|
|
39
|
-
</Stack>
|
|
40
|
-
</Card>
|
|
41
|
-
</Box>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export default PlaceholderDropzone
|