diff --git a/examples/studio/schemas/index.ts b/examples/studio/schemas/index.ts index e9a770b..04700d2 100644 --- a/examples/studio/schemas/index.ts +++ b/examples/studio/schemas/index.ts @@ -1,5 +1,5 @@ import { ObjectRule, defineArrayMember, defineField, defineType } from 'sanity'; -import { PageTreeField, definePageType } from '@q42/sanity-plugin-page-tree'; +import { PageTreeField, PageTreeInput, definePageType } from '@q42/sanity-plugin-page-tree'; import { pageTreeConfig } from '../page-tree.config'; const _homePageType = defineType({ @@ -18,6 +18,20 @@ const _homePageType = defineType({ type: 'text', validation: Rule => Rule.required(), }), + defineField({ + name: 'links', + title: 'Links', + type: 'array', + of: [ + defineArrayMember({ + type: 'reference', + to: [{ type: 'contentPage' }, { type: 'homePage' }], + components: { + input: props => PageTreeInput({ ...props, config: pageTreeConfig }), + }, + }), + ], + }), defineField({ name: 'link', title: 'Link', diff --git a/src/components/PageTreeField.tsx b/src/components/PageTreeField.tsx index 3df4fb6..3b5c036 100644 --- a/src/components/PageTreeField.tsx +++ b/src/components/PageTreeField.tsx @@ -1,15 +1,7 @@ -import { Stack, Flex, Spinner, Card, Dialog, Box } from '@sanity/ui'; -import { useMemo, useState } from 'react'; -import { ObjectFieldProps, ReferenceValue, FormField, set, useFormValue, SanityDocument } from 'sanity'; -import styled from 'styled-components'; +import { ObjectFieldProps, ReferenceValue, FormField } from 'sanity'; -import { PageTreeEditor } from './PageTreeEditor'; -import { findPageTreeItemById, flatMapPageTree } from '../helpers/page-tree'; -import { useOptimisticState } from '../hooks/useOptimisticState'; -import { usePageTree } from '../hooks/usePageTree'; -import { PageTreeConfigProvider } from '../hooks/usePageTreeConfig'; -import { PageTreeConfig, PageTreeItem } from '../types'; -import { getSanityDocumentId } from '../utils/sanity'; +import { PageTreeConfig } from '../types'; +import { PageTreeInput } from './PageTreeInput'; export const PageTreeField = ( props: ObjectFieldProps & { @@ -18,99 +10,15 @@ export const PageTreeField = ( inputProps: { schemaType: { to?: { name: string }[] } }; }, ) => { - const mode = props.mode ?? 'select-page'; - const form = useFormValue([]) as SanityDocument; - const { pageTree } = usePageTree(props.config); - - const allowedPageTypes = props.inputProps.schemaType.to?.map(t => t.name); - - const [isPageTreeDialogOpen, setIsPageTreeDialogOpen] = useState(false); - - const parentId = props.inputProps.value?._ref; - const pageId = getSanityDocumentId(form._id); - - const fieldPage = useMemo(() => (pageTree ? findPageTreeItemById(pageTree, pageId) : undefined), [pageTree, pageId]); - const parentPage = useMemo( - () => (pageTree && parentId ? findPageTreeItemById(pageTree, parentId) : undefined), - [pageTree, parentId], - ); - - const flatFieldPages = useMemo(() => (fieldPage ? flatMapPageTree([fieldPage]) : []), [fieldPage]); - - const [parentPath, setOptimisticParentPath] = useOptimisticState(parentPage?.path); - - // Some page tree items are not suitable options for a new parent reference. - // Disable the current parent page, the current page and all of its children. - const disabledParentIds = - mode !== 'select-parent' ? [] : [...(parentId ? [parentId] : []), ...flatFieldPages.map(page => page._id)]; - // Initially open the current page and all of its parents - const openItemIds = fieldPage?._id ? [fieldPage?._id] : undefined; - - const openDialog = () => { - setIsPageTreeDialogOpen(true); - }; - - const closeDialog = () => { - setIsPageTreeDialogOpen(false); - }; - - const selectParentPage = (page: PageTreeItem) => { - props.inputProps.onChange( - set({ - _ref: page._id, - _type: 'reference', - _weak: page.isDraft, - ...(page.isDraft ? { _strengthenOnPublish: { type: page._type } } : {}), - }), - ); - setOptimisticParentPath(page.path); - closeDialog(); + const inputProps = { + config: props.config, + mode: props.mode, + ...props.inputProps, }; return ( - - - - {!pageTree ? ( - - - - ) : ( - - - {parentId ? parentPath ?? 'Select page' : 'Select page'} - - - )} - - {pageTree && isPageTreeDialogOpen && ( - - - - - - )} - - + + + ); }; - -const SelectedItemCard = styled(Card)` - cursor: pointer; - - &:hover { - background-color: ${({ theme }) => theme.sanity.color.card.hovered.bg}; - } -`; diff --git a/src/components/PageTreeInput.tsx b/src/components/PageTreeInput.tsx new file mode 100644 index 0000000..1792753 --- /dev/null +++ b/src/components/PageTreeInput.tsx @@ -0,0 +1,119 @@ +import { Stack, Flex, Spinner, Card, Dialog, Box, Text } from '@sanity/ui'; +import { useMemo, useState } from 'react'; +import { ReferenceValue, set, useFormValue, SanityDocument, ObjectInputProps } from 'sanity'; +import styled from 'styled-components'; + +import { PageTreeEditor } from './PageTreeEditor'; +import { findPageTreeItemById, flatMapPageTree } from '../helpers/page-tree'; +import { useOptimisticState } from '../hooks/useOptimisticState'; +import { usePageTree } from '../hooks/usePageTree'; +import { PageTreeConfigProvider } from '../hooks/usePageTreeConfig'; +import { PageTreeConfig, PageTreeItem } from '../types'; +import { getSanityDocumentId } from '../utils/sanity'; + +export const PageTreeInput = ( + props: ObjectInputProps & { + config: PageTreeConfig; + mode?: 'select-parent' | 'select-page'; + schemaType: { to?: { name: string }[] }; + }, +) => { + const mode = props.mode ?? 'select-page'; + const form = useFormValue([]) as SanityDocument; + const { pageTree } = usePageTree(props.config); + + const allowedPageTypes = props.schemaType.to?.map(t => t.name); + + const [isPageTreeDialogOpen, setIsPageTreeDialogOpen] = useState(false); + + const parentId = props.value?._ref; + const pageId = getSanityDocumentId(form._id); + + const fieldPage = useMemo(() => (pageTree ? findPageTreeItemById(pageTree, pageId) : undefined), [pageTree, pageId]); + const parentPage = useMemo( + () => (pageTree && parentId ? findPageTreeItemById(pageTree, parentId) : undefined), + [pageTree, parentId], + ); + + const flatFieldPages = useMemo(() => (fieldPage ? flatMapPageTree([fieldPage]) : []), [fieldPage]); + + const [parentPath, setOptimisticParentPath] = useOptimisticState(parentPage?.path); + + // Some page tree items are not suitable options for a new parent reference. + // Disable the current parent page, the current page and all of its children. + const disabledParentIds = + mode !== 'select-parent' ? [] : [...(parentId ? [parentId] : []), ...flatFieldPages.map(page => page._id)]; + // Initially open the current page and all of its parents + const openItemIds = fieldPage?._id ? [fieldPage?._id] : undefined; + + const openDialog = () => { + setIsPageTreeDialogOpen(true); + }; + + const closeDialog = () => { + setIsPageTreeDialogOpen(false); + }; + + const selectParentPage = (page: PageTreeItem) => { + // In the case of an array of references, we need to find the last path in the array and extract the _key + const lastPath = props.path[props.path.length - 1]; + const _key = typeof lastPath === 'object' && '_key' in lastPath ? lastPath._key : undefined; + + props.onChange( + set({ + ...(_key ? { _key } : {}), + _ref: page._id, + _type: 'reference', + _weak: page.isDraft, + ...(page.isDraft ? { _strengthenOnPublish: { type: page._type } } : {}), + }), + ); + setOptimisticParentPath(page.path); + closeDialog(); + }; + + return ( + + + {!pageTree ? ( + + + + ) : ( + + + {parentId ? parentPath ?? 'Select page' : 'Select page'} + + + )} + + {pageTree && isPageTreeDialogOpen && ( + + + + + + )} + + ); +}; + +const SelectedItemCard = styled(Card)` + cursor: pointer; + + &:hover { + background-color: ${({ theme }) => theme.sanity.color.card.hovered.bg}; + } +`; diff --git a/src/index.ts b/src/index.ts index 12408a4..3ae4afc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { createPageTreeView } from './components/PageTreeView'; export { definePageType } from './schema/definePageType'; export { PageTreeField } from './components/PageTreeField'; +export { PageTreeInput } from './components/PageTreeInput'; export type { PageTreeConfig, PageTreeDocumentListOptions } from './types';