@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.
Files changed (44) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -30
  3. package/dist/index.d.ts +170 -195
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +835 -6031
  6. package/dist/index.js.map +1 -1
  7. package/package.json +38 -77
  8. package/dist/index.d.mts +0 -240
  9. package/dist/index.mjs +0 -6433
  10. package/dist/index.mjs.map +0 -1
  11. package/sanity.json +0 -8
  12. package/src/TreeDeskStructure.tsx +0 -80
  13. package/src/TreeInputComponent.tsx +0 -41
  14. package/src/components/DeskWarning.tsx +0 -40
  15. package/src/components/DocumentInNode.tsx +0 -133
  16. package/src/components/DocumentPreviewStatus.tsx +0 -70
  17. package/src/components/NodeActions.tsx +0 -85
  18. package/src/components/NodeContentRenderer.tsx +0 -141
  19. package/src/components/PlaceholderDropzone.tsx +0 -45
  20. package/src/components/TreeEditor.tsx +0 -184
  21. package/src/components/TreeEditorErrorBoundary.tsx +0 -14
  22. package/src/components/TreeNodeRenderer.tsx +0 -37
  23. package/src/components/TreeNodeRendererScaffold.tsx +0 -193
  24. package/src/createDeskHierarchy.tsx +0 -110
  25. package/src/createHierarchicalSchemas.tsx +0 -151
  26. package/src/hooks/useAllItems.ts +0 -119
  27. package/src/hooks/useLocalTree.ts +0 -40
  28. package/src/hooks/useTreeOperations.ts +0 -25
  29. package/src/hooks/useTreeOperationsProvider.ts +0 -86
  30. package/src/index.ts +0 -25
  31. package/src/schemas/hierarchy.tree.ts +0 -19
  32. package/src/types.ts +0 -148
  33. package/src/utils/flatDataToTree.ts +0 -20
  34. package/src/utils/getAdjescentNodes.ts +0 -30
  35. package/src/utils/getCommonTreeProps.tsx +0 -28
  36. package/src/utils/getTreeHeight.ts +0 -8
  37. package/src/utils/gradientPatchAdapter.ts +0 -43
  38. package/src/utils/idUtils.ts +0 -7
  39. package/src/utils/injectNodeTypeInPatches.ts +0 -60
  40. package/src/utils/moveItemInArray.ts +0 -26
  41. package/src/utils/throwError.ts +0 -9
  42. package/src/utils/treeData.tsx +0 -119
  43. package/src/utils/treePatches.ts +0 -171
  44. package/v2-incompatible.js +0 -11
package/sanity.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "parts": [
3
- {
4
- "implements": "part:@sanity/base/sanity-root",
5
- "path": "./v2-incompatible.js"
6
- }
7
- ]
8
- }
@@ -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