diff --git a/packages/sanity/src/_singletons/core/form/studio/TreeEditingEnabledContext.ts b/packages/sanity/src/_singletons/core/form/studio/TreeEditingEnabledContext.ts index 39a632e1ceee..7c6fa776c082 100644 --- a/packages/sanity/src/_singletons/core/form/studio/TreeEditingEnabledContext.ts +++ b/packages/sanity/src/_singletons/core/form/studio/TreeEditingEnabledContext.ts @@ -1,6 +1,6 @@ import {createContext} from 'react' -import type {TreeEditingEnabledContextValue} from '../../../../core/form/studio/tree-editing/context/enabled/useTreeEditingEnabled' +import type {TreeEditingEnabledContextValue} from '../../../../core/form/studio/array-editing/context/enabled/useTreeEditingEnabled' /** * @internal diff --git a/packages/sanity/src/core/form/inputs/PortableText/Compositor.tsx b/packages/sanity/src/core/form/inputs/PortableText/Compositor.tsx index ad7d13e41926..51bf7ea2869c 100644 --- a/packages/sanity/src/core/form/inputs/PortableText/Compositor.tsx +++ b/packages/sanity/src/core/form/inputs/PortableText/Compositor.tsx @@ -16,7 +16,7 @@ import {type ReactNode, useCallback, useMemo, useState} from 'react' import {ChangeIndicator} from '../../../changeIndicators' import {EMPTY_ARRAY} from '../../../util' import {ActivateOnFocus} from '../../components/ActivateOnFocus/ActivateOnFocus' -import {TreeEditingEnabledProvider} from '../../studio/tree-editing' +import {TreeEditingEnabledProvider} from '../../studio/array-editing' import { type ArrayOfObjectsInputProps, type PortableTextInputProps, diff --git a/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/Grid/GridItem.tsx b/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/Grid/GridItem.tsx index f5dd62b77cfa..555aafcd8a75 100644 --- a/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/Grid/GridItem.tsx +++ b/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/Grid/GridItem.tsx @@ -16,9 +16,9 @@ import {FormFieldValidationStatus} from '../../../../components' import {EditPortal} from '../../../../components/EditPortal' import {useDidUpdate} from '../../../../hooks/useDidUpdate' import {useScrollIntoViewOnFocusWithin} from '../../../../hooks/useScrollIntoViewOnFocusWithin' +import {TreeEditingEnabledProvider, useTreeEditingEnabled} from '../../../../studio/array-editing' import {useChildPresence} from '../../../../studio/contexts/Presence' import {useChildValidation} from '../../../../studio/contexts/Validation' -import {TreeEditingEnabledProvider, useTreeEditingEnabled} from '../../../../studio/tree-editing' import {type ObjectItem, type ObjectItemProps} from '../../../../types' import {randomKey} from '../../../../utils/randomKey' import {CellLayout} from '../../layouts/CellLayout' diff --git a/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/List/PreviewItem.tsx b/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/List/PreviewItem.tsx index be7349207551..9fb22464a16d 100644 --- a/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/List/PreviewItem.tsx +++ b/packages/sanity/src/core/form/inputs/arrays/ArrayOfObjectsInput/List/PreviewItem.tsx @@ -15,9 +15,9 @@ import {FormFieldValidationStatus} from '../../../../components' import {EditPortal} from '../../../../components/EditPortal' import {useDidUpdate} from '../../../../hooks/useDidUpdate' import {useScrollIntoViewOnFocusWithin} from '../../../../hooks/useScrollIntoViewOnFocusWithin' +import {TreeEditingEnabledProvider, useTreeEditingEnabled} from '../../../../studio/array-editing' import {useChildPresence} from '../../../../studio/contexts/Presence' import {useChildValidation} from '../../../../studio/contexts/Validation' -import {TreeEditingEnabledProvider, useTreeEditingEnabled} from '../../../../studio/tree-editing' import {type ObjectItem, type ObjectItemProps} from '../../../../types' import {randomKey} from '../../../../utils/randomKey' import {RowLayout} from '../../layouts/RowLayout' diff --git a/packages/sanity/src/core/form/studio/FormBuilder.test.tsx b/packages/sanity/src/core/form/studio/FormBuilder.test.tsx index f486e994f4de..2ff0c892bc7b 100644 --- a/packages/sanity/src/core/form/studio/FormBuilder.test.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilder.test.tsx @@ -12,8 +12,8 @@ import {EMPTY_ARRAY} from '../../util' import {createPatchChannel} from '../patch' import {useFormState} from '../store/useFormState' import {type FormDocumentValue} from '../types' +import {useTreeEditingEnabled} from './array-editing' import {FormBuilder, type FormBuilderProps} from './FormBuilder' -import {useTreeEditingEnabled} from './tree-editing' const schemaTypes = [ defineType({ diff --git a/packages/sanity/src/core/form/studio/FormBuilder.tsx b/packages/sanity/src/core/form/studio/FormBuilder.tsx index 98a744aefbc9..8760a093cdfb 100644 --- a/packages/sanity/src/core/form/studio/FormBuilder.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilder.tsx @@ -29,15 +29,15 @@ import { type ObjectInputProps, type RenderPreviewCallbackProps, } from '../types' -import {DocumentFieldActionsProvider} from './contexts/DocumentFieldActions' -import {FormBuilderInputErrorBoundary} from './FormBuilderInputErrorBoundary' -import {FormProvider} from './FormProvider' import { + ArrayEditingDialog, shouldArrayDialogOpen, - TreeEditingDialog, TreeEditingEnabledProvider, useTreeEditingEnabled, -} from './tree-editing' +} from './array-editing' +import {DocumentFieldActionsProvider} from './contexts/DocumentFieldActions' +import {FormBuilderInputErrorBoundary} from './FormBuilderInputErrorBoundary' +import {FormProvider} from './FormProvider' /** * @alpha @@ -316,7 +316,7 @@ function RootInput(props: RootInputProps) { const isRoot = rootInputProps.id === 'root' const arrayEditingModal = treeEditing.enabled && isRoot && open && ( - { +describe('tree-editing: buildArrayEditingState', () => { test('should build tree editing state for an array of objects', () => { // implement @@ -205,7 +205,7 @@ describe('tree-editing: buildTreeEditingState', () => { ], } - const result = buildTreeEditingState({ + const result = buildArrayEditingState({ documentValue, openPath: ['array1', {_key: '123'}], schemaType: schema.get('testDocument'), @@ -230,7 +230,7 @@ describe('tree-editing: buildTreeEditingState', () => { }, } // Path to the array item - const result1 = buildTreeEditingState({ + const result1 = buildArrayEditingState({ documentValue, openPath: ['objectWithArray', 'myArray', {_key: 'key1'}], schemaType: schema.get('testDocument'), @@ -238,7 +238,7 @@ describe('tree-editing: buildTreeEditingState', () => { // Path to a primitive field in the array item object should // result in the same state as the array item itself - const result2 = buildTreeEditingState({ + const result2 = buildArrayEditingState({ documentValue, openPath: ['objectWithArray', 'myArray', {_key: 'key1'}, 'myString'], schemaType: schema.get('testDocument'), @@ -267,7 +267,7 @@ describe('tree-editing: buildTreeEditingState', () => { ], } - const result = buildTreeEditingState({ + const result = buildArrayEditingState({ documentValue, openPath: ['mixedArray', {_key: 'key1'}], schemaType: schema.get('testDocument'), @@ -290,7 +290,7 @@ describe('tree-editing: buildTreeEditingState', () => { ], } - const result = buildTreeEditingState({ + const result = buildArrayEditingState({ documentValue, openPath: ['legacyArrayEditingArray', {_key: '123'}], schemaType: schema.get('testDocument'), @@ -327,7 +327,7 @@ describe('tree-editing: buildTreeEditingState', () => { ], } - const result = buildTreeEditingState({ + const result = buildArrayEditingState({ documentValue, openPath: [ 'arrayWithArrayFieldInNestedObjects', diff --git a/packages/sanity/src/core/form/studio/tree-editing/__tests__/findArrayTypePaths.test.ts b/packages/sanity/src/core/form/studio/array-editing/__tests__/findArrayTypePaths.test.ts similarity index 100% rename from packages/sanity/src/core/form/studio/tree-editing/__tests__/findArrayTypePaths.test.ts rename to packages/sanity/src/core/form/studio/array-editing/__tests__/findArrayTypePaths.test.ts diff --git a/packages/sanity/src/core/form/studio/tree-editing/__tests__/shouldArrayDialogOpen.test.ts b/packages/sanity/src/core/form/studio/array-editing/__tests__/shouldArrayDialogOpen.test.ts similarity index 100% rename from packages/sanity/src/core/form/studio/tree-editing/__tests__/shouldArrayDialogOpen.test.ts rename to packages/sanity/src/core/form/studio/array-editing/__tests__/shouldArrayDialogOpen.test.ts diff --git a/packages/sanity/src/core/form/studio/tree-editing/__tests__/useTreeEditingEnabled.test.tsx b/packages/sanity/src/core/form/studio/array-editing/__tests__/useTreeEditingEnabled.test.tsx similarity index 100% rename from packages/sanity/src/core/form/studio/tree-editing/__tests__/useTreeEditingEnabled.test.tsx rename to packages/sanity/src/core/form/studio/array-editing/__tests__/useTreeEditingEnabled.test.tsx diff --git a/packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingBreadcrumbsMenuButtonStory.tsx b/packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingBreadcrumbsMenuButtonStory.tsx similarity index 67% rename from packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingBreadcrumbsMenuButtonStory.tsx rename to packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingBreadcrumbsMenuButtonStory.tsx index 37ed2e3f8d46..70c071613c2f 100644 --- a/packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingBreadcrumbsMenuButtonStory.tsx +++ b/packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingBreadcrumbsMenuButtonStory.tsx @@ -3,8 +3,8 @@ import {Flex} from '@sanity/ui' import {useState} from 'react' import {type Path} from 'sanity' -import {TreeEditingBreadcrumbsMenuButton} from '../components' -import {type TreeEditingBreadcrumb} from '../types' +import {ArrayEditingBreadcrumbsMenuButton} from '../components' +import {type ArrayEditingBreadcrumb} from '../types' const schema = Schema.compile({ name: 'default', @@ -31,20 +31,10 @@ const schema = Schema.compile({ ], }) -const ITEM: TreeEditingBreadcrumb = { +const ITEM: ArrayEditingBreadcrumb = { path: ['first-item'], schemaType: schema.get('testDocument').fields[0].type, value: {_key: 'first-item', title: `${0}-item`}, - parentSchemaType: schema.get('testDocument'), - children: [...Array(100).keys()].map((index) => ({ - path: [`${index}-item`], - schemaType: schema.get('testDocument').fields[0].type, - parentSchemaType: schema.get('testDocument'), - value: { - _key: `${index}-item`, - title: `${index}-item`, - }, - })), } export default function TreeEditingBreadcrumbsMenuButtonStory(): JSX.Element { @@ -52,12 +42,12 @@ export default function TreeEditingBreadcrumbsMenuButtonStory(): JSX.Element { return ( - Click me} - items={ITEM.children || []} onPathSelect={setSelectedPath} parentElement={document.body} selectedPath={selectedPath} + items={[ITEM]} /> ) diff --git a/packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingBreadcrumbsMenuStory.tsx b/packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingBreadcrumbsMenuStory.tsx similarity index 74% rename from packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingBreadcrumbsMenuStory.tsx rename to packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingBreadcrumbsMenuStory.tsx index d7da89325b1a..088fe028d920 100644 --- a/packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingBreadcrumbsMenuStory.tsx +++ b/packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingBreadcrumbsMenuStory.tsx @@ -3,8 +3,8 @@ import {Container, Flex} from '@sanity/ui' import {useState} from 'react' import {type Path} from 'sanity' -import {TreeEditingBreadcrumbsMenu} from '../components' -import {type TreeEditingBreadcrumb} from '../types' +import {ArrayEditingBreadcrumbsMenu} from '../components' +import {type ArrayEditingBreadcrumb} from '../types' const schema = Schema.compile({ name: 'default', @@ -31,37 +31,31 @@ const schema = Schema.compile({ ], }) -const items: TreeEditingBreadcrumb[] = [ +const items: ArrayEditingBreadcrumb[] = [ { path: ['first-item'], - children: [], schemaType: schema.get('testDocument').fields[0].type, value: {_key: 'first-item', title: 'First item'}, - parentSchemaType: schema.get('testDocument'), }, { path: ['second-item'], - children: [], schemaType: schema.get('testDocument').fields[0].type, value: {_key: 'second-item', title: 'Second item'}, - parentSchemaType: schema.get('testDocument'), }, { path: ['third-item'], - children: [], schemaType: schema.get('testDocument').fields[0].type, value: {_key: 'third-item', title: 'Third item'}, - parentSchemaType: schema.get('testDocument'), }, ] -export default function TreeEditingBreadcrumbsMenuStory(): JSX.Element { +export default function ArrayEditingBreadcrumbsMenuStory(): JSX.Element { const [selectedPath, setSelectedPath] = useState(['second-item']) return ( - (['myArrayOfObjects', {_key: 'item-1'}]) - const {menuItems} = useMemo((): TreeEditingState => { - return buildTreeEditingState({ + const {breadcrumbs} = useMemo((): ArrayEditingState => { + return buildArrayEditingState({ schemaType: schema.get('testDocument'), documentValue: DOCUMENT_VALUE, openPath: selectedPath, @@ -132,8 +132,8 @@ export default function TreeEditingBreadcrumbsStory(): JSX.Element { return ( - diff --git a/packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingLayoutStory.tsx b/packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingLayoutStory.tsx similarity index 93% rename from packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingLayoutStory.tsx rename to packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingLayoutStory.tsx index 5435d87cb58a..c2f56c8ab5ea 100644 --- a/packages/sanity/src/core/form/studio/tree-editing/__workshop__/TreeEditingLayoutStory.tsx +++ b/packages/sanity/src/core/form/studio/array-editing/__workshop__/ArrayEditingLayoutStory.tsx @@ -3,8 +3,8 @@ import {Card, Code, Stack} from '@sanity/ui' import {useCallback, useMemo, useState} from 'react' import {type Path} from 'sanity' -import {TreeEditingLayout} from '../components' -import {buildTreeEditingState} from '../utils' +import {ArrayEditingLayout} from '../components' +import {buildArrayEditingState} from '../utils' const schema = Schema.compile({ name: 'default', @@ -118,11 +118,11 @@ const DOCUMENT_VALUE = { ], } -export default function TreeEditingLayoutStory(): JSX.Element { +export default function ArrayEditingLayoutStory(): JSX.Element { const [selectedPath, setSelectedPath] = useState(['myArrayOfObjects', {_key: 'first-item'}]) const state = useMemo(() => { - return buildTreeEditingState({ + return buildArrayEditingState({ documentValue: DOCUMENT_VALUE, schemaType: schema.get('testDocument'), openPath: selectedPath, @@ -134,12 +134,10 @@ export default function TreeEditingLayoutStory(): JSX.Element { }, []) return ( - @@ -148,6 +146,6 @@ export default function TreeEditingLayoutStory(): JSX.Element { - + ) } diff --git a/packages/sanity/src/core/form/studio/array-editing/__workshop__/index.ts b/packages/sanity/src/core/form/studio/array-editing/__workshop__/index.ts new file mode 100644 index 000000000000..81eac612557f --- /dev/null +++ b/packages/sanity/src/core/form/studio/array-editing/__workshop__/index.ts @@ -0,0 +1,29 @@ +import {defineScope} from '@sanity/ui-workshop' +import {lazy} from 'react' + +export default defineScope({ + name: 'core/form/array-editing', + title: 'Array editing', + stories: [ + { + name: 'array-editing-layout', + title: 'ArrayEditingLayout', + component: lazy(() => import('./ArrayEditingLayoutStory')), + }, + { + name: 'array-editing-breadcrumbs', + title: 'ArrayEditingBreadcrumbs', + component: lazy(() => import('./ArrayEditingBreadcrumbsStory')), + }, + { + name: 'array-editing-breadcrumbs-menu', + title: 'ArrayEditingBreadcrumbsMenu', + component: lazy(() => import('./ArrayEditingBreadcrumbsMenuStory')), + }, + { + name: 'array-editing-breadcrumbs-menu-button', + title: 'ArrayEditingBreadcrumbsMenuButtonStory', + component: lazy(() => import('./ArrayEditingBreadcrumbsMenuButtonStory')), + }, + ], +}) diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/TreeEditingDialog.tsx b/packages/sanity/src/core/form/studio/array-editing/components/ArrayEditingDialog.tsx similarity index 72% rename from packages/sanity/src/core/form/studio/tree-editing/components/TreeEditingDialog.tsx rename to packages/sanity/src/core/form/studio/array-editing/components/ArrayEditingDialog.tsx index 69f62808a130..155f04810595 100644 --- a/packages/sanity/src/core/form/studio/tree-editing/components/TreeEditingDialog.tsx +++ b/packages/sanity/src/core/form/studio/array-editing/components/ArrayEditingDialog.tsx @@ -17,13 +17,13 @@ import { import {css, styled} from 'styled-components' import { - buildTreeEditingState, - type BuildTreeEditingStateProps, - EMPTY_TREE_STATE, - type TreeEditingState, + type ArrayEditingState, + buildArrayEditingState, + type BuildArrayEditingStateProps, + EMPTY_STATE, } from '../utils' -import {isArrayItemPath} from '../utils/build-tree-editing-state/utils' -import {TreeEditingLayout} from './layout' +import {isArrayItemPath} from '../utils/build-array-editing-state/utils' +import {ArrayEditingLayout} from './layout' const EMPTY_ARRAY: [] = [] @@ -57,7 +57,7 @@ const StyledDialog = styled(Dialog)(({theme}: {theme: Theme}) => { const MotionFlex = motion(Flex) -interface TreeEditingDialogProps { +interface ArrayEditingDialogProps { onPathFocus: (path: Path) => void onPathOpen: (path: Path) => void openPath: Path @@ -65,12 +65,12 @@ interface TreeEditingDialogProps { schemaType: ObjectSchemaType } -export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | null { +export function ArrayEditingDialog(props: ArrayEditingDialogProps): JSX.Element | null { const {onPathFocus, onPathOpen, openPath, rootInputProps, schemaType} = props const {value} = rootInputProps const {t} = useTranslation() - const [treeState, setTreeState] = useState(EMPTY_TREE_STATE) + const [state, setState] = useState(EMPTY_STATE) const [layoutScrollElement, setLayoutScrollElement] = useState(null) const openPathRef = useRef(undefined) @@ -83,11 +83,11 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | layoutScrollElement?.scrollTo(0, 0) }, [layoutScrollElement]) - const handleBuildTreeEditingState = useCallback( - (opts: BuildTreeEditingStateProps) => { - const nextState = buildTreeEditingState(opts) + const handleBuildState = useCallback( + (opts: BuildArrayEditingStateProps) => { + const nextState = buildArrayEditingState(opts) - if (isEqual(nextState, treeState)) return + if (isEqual(nextState, state)) return const builtRelativePath = nextState.relativePath const len = builtRelativePath.length @@ -98,31 +98,28 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | // current relative path. This is to avoid changing the fields being displayed in the form // when the path is not an array item path. const useCurrentRelativePath = hasNoRelativePath || !isArrayItemPath(builtRelativePath) - const nextRelativePath = useCurrentRelativePath ? treeState.relativePath : builtRelativePath + const nextRelativePath = useCurrentRelativePath ? state.relativePath : builtRelativePath - setTreeState({...nextState, relativePath: nextRelativePath}) + setState({...nextState, relativePath: nextRelativePath}) }, - [treeState], + [state], ) - const debouncedBuildTreeEditingState = useMemo( - () => debounce(handleBuildTreeEditingState, 1000), - [handleBuildTreeEditingState], - ) + const debouncedBuildState = useMemo(() => debounce(handleBuildState, 1000), [handleBuildState]) const onClose = useCallback(() => { // Cancel any debounced state building when closing the dialog. - debouncedBuildTreeEditingState.cancel() + debouncedBuildState.cancel() // Reset the `openPath` onPathOpen(EMPTY_ARRAY) - // Reset the tree state when closing the dialog. - setTreeState(EMPTY_TREE_STATE) + // Reset the state when closing the dialog. + setState(EMPTY_STATE) // Reset the stored value and openPath to undefined. // This is important since the next time the dialog is opened, - // we want to build the tree editing state from scratch and + // we want to build the editing state from scratch and // don't prevent the state from being built by comparing the // previous stored values. valueRef.current = undefined @@ -132,12 +129,12 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | const firstKeySegmentIndex = openPath.findIndex(isKeySegment) const rootFocusPath = openPath.slice(0, firstKeySegmentIndex + 1) onPathFocus(rootFocusPath) - }, [debouncedBuildTreeEditingState, onPathFocus, onPathOpen, openPath]) + }, [debouncedBuildState, onPathFocus, onPathOpen, openPath]) const onHandlePathSelect = useCallback( (path: Path) => { // Cancel any debounced state building when navigating. - debouncedBuildTreeEditingState.cancel() + debouncedBuildState.cancel() onPathOpen(path) @@ -148,7 +145,7 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | onPathFocus(path) } }, - [debouncedBuildTreeEditingState, onPathFocus, onPathOpen], + [debouncedBuildState, onPathFocus, onPathOpen], ) useEffect(() => { @@ -157,13 +154,13 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | const isInitialRender = valueRef.current === undefined && openPathRef.current === undefined // If the value has not changed but the openPath has changed, or - // if it is the initial render, build the tree editing state + // if it is the initial render, build the editing state // without debouncing. We do this to make sure that the UI is // updated immediately when the openPath changes. // We only want to debounce the state building when the value changes // as that might happen frequently when the user is editing the document. if (isInitialRender || openPathChanged) { - handleBuildTreeEditingState({ + handleBuildState({ schemaType, documentValue: value, openPath, @@ -174,7 +171,7 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | return undefined } - // Don't proceed with building the tree editing state if the + // Don't proceed with building t editing state if the // openPath and value has not changed. if (!valueChanged && !openPathChanged) return undefined @@ -183,7 +180,7 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | valueRef.current = value openPathRef.current = openPath - debouncedBuildTreeEditingState({ + debouncedBuildState({ schemaType, documentValue: value, openPath, @@ -191,36 +188,34 @@ export function TreeEditingDialog(props: TreeEditingDialogProps): JSX.Element | return () => { // Cancel any debounced state building on unmount. - debouncedBuildTreeEditingState.cancel() + debouncedBuildState.cancel() } - }, [schemaType, value, debouncedBuildTreeEditingState, openPath, handleBuildTreeEditingState]) + }, [schemaType, value, debouncedBuildState, openPath, handleBuildState]) - if (treeState.relativePath.length === 0) return null + if (state.relativePath.length === 0) return null return ( - - - ) -} - -interface TreeEditingSearchMenuProps { - items: TreeEditingMenuItem[] - onPathSelect: (path: Path) => void - textInputElement: HTMLInputElement | null -} - -export function TreeEditingSearchMenu(props: TreeEditingSearchMenuProps): JSX.Element { - const {items, onPathSelect, textInputElement} = props - const {t} = useTranslation() - - const renderItem = useCallback( - (item: TreeEditingMenuItem) => { - const isFirst = isEqual(item.path, items[0].path) - - return - }, - [items, onPathSelect], - ) - - return ( - - ) -} diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/search/constants.ts b/packages/sanity/src/core/form/studio/tree-editing/components/search/constants.ts deleted file mode 100644 index 3c00502456e2..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/search/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const ITEM_HEIGHT = 51 -export const MAX_DISPLAYED_ITEMS = 10 diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/search/index.ts b/packages/sanity/src/core/form/studio/tree-editing/components/search/index.ts deleted file mode 100644 index f6da56fa7d34..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TreeEditingSearch' diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/search/types.ts b/packages/sanity/src/core/form/studio/tree-editing/components/search/types.ts deleted file mode 100644 index 7c97177f3c7c..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/search/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {type TreeEditingMenuItem} from '../../types' - -export interface SearchableTreeEditingMenuItem extends TreeEditingMenuItem { - title: string | undefined - children?: SearchableTreeEditingMenuItem[] -} diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/search/utils.ts b/packages/sanity/src/core/form/studio/tree-editing/components/search/utils.ts deleted file mode 100644 index 64b664882f3a..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/search/utils.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {deburr} from 'lodash' - -import {type TreeEditingMenuItem} from '../../types' -import {type SearchableTreeEditingMenuItem} from './types' - -/** - * Flattens a list of items and their children into a single list. - */ -export function flattenItems(items: TreeEditingMenuItem[]): TreeEditingMenuItem[] { - const result: TreeEditingMenuItem[] = items.reduce( - (acc: TreeEditingMenuItem[], item: TreeEditingMenuItem) => { - if (item?.children) { - return [...acc, item, ...flattenItems(item.children)] - } - - return [...acc, item] - }, - [], - ) - - // Remove the children property from the items - // as we only want to return the items themselves - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return result.map(({children, ...item}) => item) -} - -/** - * Returns a list of items that match the search query. - */ -export function treeEditingSearch( - items: SearchableTreeEditingMenuItem[], - query: string, -): TreeEditingMenuItem[] { - // Flatten the items list so we can search through all items and their children - const flattenItemsList = flattenItems(items) as SearchableTreeEditingMenuItem[] - - // We use deburr to remove diacritics from the query and the item titles. This way we can - // search for "nino" and get results for "niƱo" as well. - const deburredQuery = deburr(query).toLocaleLowerCase() - - const filtered = flattenItemsList - ?.filter((option) => { - const deburredTitle = deburr(option.title || '').toLocaleLowerCase() - - return deburredTitle.includes(deburredQuery) - }) - // Sort the most relevant results first - ?.sort((a, b) => { - const matchA = a.title?.startsWith(deburredQuery) - const matchB = b.title?.startsWith(deburredQuery) - - if (matchA && !matchB) return -1 - if (!matchA && matchB) return 1 - - return 0 - }) - - return filtered -} diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/TreeEditingMenu.tsx b/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/TreeEditingMenu.tsx deleted file mode 100644 index 5abd95bd4972..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/TreeEditingMenu.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import {Stack} from '@sanity/ui' -import {toString} from '@sanity/util/paths' -import {memo} from 'react' -import {type Path} from 'sanity' - -import {type TreeEditingMenuItem as TreeEditingMenuItemType} from '../../types' -import {TreeEditingMenuItem} from './TreeEditingMenuItem' -import {getSiblingHasChildren} from './utils' - -interface TreeEditingMenuProps { - items: TreeEditingMenuItemType[] - onPathSelect: (path: Path) => void - selectedPath: Path | null -} - -export const TreeEditingMenu = memo(function TreeEditingMenu( - props: TreeEditingMenuProps, -): JSX.Element { - const {items, onPathSelect, selectedPath} = props - - return ( - - {items.map((item) => { - const siblingHasChildren = getSiblingHasChildren(items) - - return ( - - ) - })} - - ) -}) diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/TreeEditingMenuItem.tsx b/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/TreeEditingMenuItem.tsx deleted file mode 100644 index a45fe41ccfd3..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/TreeEditingMenuItem.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import {hues} from '@sanity/color' -import {ChevronRightIcon, StackCompactIcon} from '@sanity/icons' -import {Button, Card, Flex, Stack, Text} from '@sanity/ui' -// eslint-disable-next-line camelcase -import {getTheme_v2} from '@sanity/ui/theme' -import {toString} from '@sanity/util/paths' -import {isEqual} from 'lodash' -import {useCallback, useEffect, useMemo, useState} from 'react' -import {useTranslation} from 'react-i18next' -import {getSchemaTypeTitle, type Path, SanityDefaultPreview} from 'sanity' -import scrollIntoViewIfNeeded, {type StandardBehaviorOptions} from 'scroll-into-view-if-needed' -import {css, styled} from 'styled-components' - -import {useValuePreviewWithFallback} from '../../hooks' -import {type TreeEditingMenuItem as TreeEditingMenuItemType} from '../../types' -import {isArrayItemPath} from '../../utils/build-tree-editing-state/utils' -import {getSiblingHasChildren} from './utils' - -function hasOpenChild(item: TreeEditingMenuItemType, selectedPath: Path | null): boolean { - return ( - item.children?.some( - (child) => isEqual(child.path, selectedPath) || hasOpenChild(child, selectedPath), - ) || false - ) -} - -const SCROLL_BEHAVIOR_OPTIONS: StandardBehaviorOptions = { - block: 'center', - behavior: 'smooth', - scrollMode: 'if-needed', -} - -const AnimateChevronIcon = styled(ChevronRightIcon)` - transition: transform 0.2s ease; - - &[data-expanded='true'] { - transform: rotate(90deg); - } -` - -// This component is used to keep buttons aligned in the tree menu -// when the expand button is not present on an item. -// The width should match the width of the expand button. -const Spacer = styled.div` - min-width: 23px; - max-width: 23px; -` - -const ChildStack = styled(Stack)(({theme}) => { - const space = getTheme_v2(theme)?.space[3] || 0 - const isDark = getTheme_v2(theme)?.color._dark - const borderColor = hues.gray[isDark ? 900 : 200].hex - - return css` - margin-left: ${space + 2}px; - box-sizing: border-box; - border-left: 1px solid ${borderColor}; - ` -}) - -const ItemFlex = styled(Flex)(({theme}) => { - const defaultHoverBg = getTheme_v2(theme)?.color.button.bleed.default.hovered.bg - const selectedHoverBg = getTheme_v2(theme)?.color.button.bleed.default.pressed.bg - const selectedBg = getTheme_v2(theme)?.color.button.bleed.default.selected.bg - - return css` - padding: 2px; - padding-right: 3px; - box-sizing: border-box; - transition: inherit; - border-radius: ${getTheme_v2(theme).radius[2]}px; - - &[data-selected='true'] { - background-color: ${selectedBg}; - } - - [data-ui='ExpandButton'], - [data-ui='NavigateButton'] { - transition: inherit; - background-color: inherit; - box-shadow: unset; - } - - @media (hover: hover) { - &:hover { - &[data-selected='false'] { - background-color: ${defaultHoverBg}; - border-radius: ${getTheme_v2(theme).radius[2]}px; - - [data-ui='ExpandButton']:hover { - background-color: ${selectedHoverBg}; - } - } - - [data-ui='ExpandButton']:hover { - background-color: ${defaultHoverBg}; - } - } - } - ` -}) - -interface TreeEditingMenuItemProps { - item: TreeEditingMenuItemType - onPathSelect: (path: Path) => void - selectedPath: Path | null - siblingHasChildren?: boolean -} - -export function TreeEditingMenuItem(props: TreeEditingMenuItemProps): JSX.Element { - const {item, onPathSelect, selectedPath, siblingHasChildren} = props - const {children} = item - const hasChildren = children && children.length > 0 - - const [open, setOpen] = useState(false) - const [rootElement, setRootElement] = useState(null) - - const {t} = useTranslation() - - const {value} = useValuePreviewWithFallback({ - schemaType: item.schemaType, - value: item.value, - }) - - const selected = useMemo(() => isEqual(item.path, selectedPath), [item.path, selectedPath]) - const isArrayParent = useMemo(() => !isArrayItemPath(item.path), [item.path]) - const stringPath = useMemo(() => toString(item.path), [item.path]) - - const title = useMemo(() => { - // If the item is an array parent, we want to show the schema type title - if (isArrayParent) { - return getSchemaTypeTitle(item.schemaType) - } - - // Else, we show the preview title - return value.title - }, [isArrayParent, item.schemaType, value.title]) - - const icon = useMemo(() => { - if (!hasChildren) return null - - return - }, [hasChildren, open]) - - const media = useMemo(() => { - if (isArrayParent) { - return - } - - return value.media - }, [isArrayParent, value.media]) - - const handleClick = useCallback(() => onPathSelect(item.path), [item.path, onPathSelect]) - - const handleExpandClick = useCallback(() => setOpen((v) => !v), []) - - useEffect(() => { - const hasOpen = hasOpenChild(item, selectedPath) - - if (hasOpen) { - setOpen(true) - } - }, [item, selectedPath]) - - // Scroll to the selected item - useEffect(() => { - if (!rootElement || !selected) return - - scrollIntoViewIfNeeded(rootElement, SCROLL_BEHAVIOR_OPTIONS) - }, [rootElement, selected]) - - return ( - - - - {icon && ( - - )} - - {!icon && siblingHasChildren && } - - - - - - - - {open && hasChildren && ( - - {children.map((child) => { - const childSiblingHasChildren = getSiblingHasChildren(children) - - return ( - - ) - })} - - )} - - ) -} diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/index.ts b/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/index.ts deleted file mode 100644 index 097b0470c7ef..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TreeEditingMenu' diff --git a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/utils.ts b/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/utils.ts deleted file mode 100644 index 928ffc703b2b..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/components/tree-menu/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {type TreeEditingMenuItem} from '../../types' - -export function getSiblingHasChildren(items: TreeEditingMenuItem[]): boolean { - return items.some((sibling) => sibling.children && sibling.children.length > 0) -} diff --git a/packages/sanity/src/core/form/studio/tree-editing/hooks/useSearchableList.ts b/packages/sanity/src/core/form/studio/tree-editing/hooks/useSearchableList.ts deleted file mode 100644 index 95dff16a7ba2..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/hooks/useSearchableList.ts +++ /dev/null @@ -1,73 +0,0 @@ -import {isEqual} from 'lodash' -import {useCallback, useEffect, useMemo, useState} from 'react' -import {map} from 'rxjs' -import {type Previewable, useDocumentPreviewStore} from 'sanity' - -import {flattenItems} from '../components/search/utils' -import {type TreeEditingMenuItem} from '../types' - -export interface SearchableTreeEditingMenuItem extends TreeEditingMenuItem { - title: string | undefined - children?: SearchableTreeEditingMenuItem[] -} - -/** - * A hook that takes a list of items and returns a flat list of - * items that are searchable by adding the title to the item. - */ -export function useSearchableList(items: TreeEditingMenuItem[]): SearchableTreeEditingMenuItem[] { - const [searchableList, setSearchableList] = useState([]) - const {observeForPreview} = useDocumentPreviewStore() - - const flatList = useMemo(() => flattenItems(items), [items]) - - const handleResult = useCallback( - (item: SearchableTreeEditingMenuItem) => { - const path = item.path - - setSearchableList((prev) => { - const exists = prev.some((prevItem) => isEqual(prevItem.path, path)) - - if (exists) { - return prev.map((prevItem) => { - if (isEqual(prevItem.path, path)) { - return { - ...item, - title: item?.title, - } - } - - return prevItem - }) - } - - return prev.concat(item) - }) - }, - [setSearchableList], - ) - - useEffect(() => { - // clear the searchableList when items change - setSearchableList([]) - - flatList.forEach((item) => { - const sub$ = observeForPreview(item.value as Previewable, item.schemaType).pipe( - map((event) => { - const searchableItem = { - ...item, - - // Add the title to the item to make it searchable - title: event.snapshot?.title, - } - - return searchableItem as SearchableTreeEditingMenuItem - }), - ) - - sub$.subscribe(handleResult).unsubscribe() - }) - }, [flatList, handleResult, observeForPreview]) - - return searchableList -} diff --git a/packages/sanity/src/core/form/studio/tree-editing/types.ts b/packages/sanity/src/core/form/studio/tree-editing/types.ts deleted file mode 100644 index 5ae4c2a0eed4..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {type Path, type SchemaType} from 'sanity' - -export interface TreeEditingMenuItem { - children?: TreeEditingMenuItem[] - parentSchemaType: SchemaType - path: Path - schemaType: SchemaType - value: unknown | undefined -} - -export type TreeEditingBreadcrumb = TreeEditingMenuItem diff --git a/packages/sanity/src/core/form/studio/tree-editing/utils/build-tree-editing-state/index.ts b/packages/sanity/src/core/form/studio/tree-editing/utils/build-tree-editing-state/index.ts deleted file mode 100644 index 3d73142d896d..000000000000 --- a/packages/sanity/src/core/form/studio/tree-editing/utils/build-tree-editing-state/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './buildTreeEditingState'