Skip to content

Commit

Permalink
feat(form): add support for disabling various array input capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
bjoerge committed Oct 14, 2024
1 parent 6d23434 commit 8e079c5
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 46 deletions.
6 changes: 5 additions & 1 deletion dev/test-studio/schema/debug/simpleArrayOfObjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export const simpleArrayOfObjects = {
},
{
name: 'arrayWithObjects',
options: {collapsible: true, collapsed: true},
options: {
collapsible: true,
collapsed: true,
disableActions: ['add'],
},
title: 'Array with named objects',
description: 'This array contains objects of type as defined inline',
type: 'array',
Expand Down
39 changes: 39 additions & 0 deletions packages/@sanity/types/src/schema/definition/type/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,38 @@ import {type BaseSchemaDefinition, type SearchConfiguration, type TitledListValu

export type {InsertMenuOptions}

/**
* Types of array actions that can be performed
* @beta
*/
export type ArrayActionName =
/**
* Add any item to the array at any position
*/
| 'add'
/**
* Add item after an existing item
*/
| 'addBefore'

/**
* Add item after an existing item
*/
| 'addAfter'
/**
* Remove any item
*/
| 'remove'
/**
* Duplicate item
*/
| 'duplicate'

/**
* Copy item
*/
| 'copy'

/** @public */
export interface ArrayOptions<V = unknown> extends SearchConfiguration {
list?: TitledListValue<V>[] | V[]
Expand All @@ -30,6 +62,13 @@ export interface ArrayOptions<V = unknown> extends SearchConfiguration {
* @deprecated tree editing beta feature has been disabled
*/
treeEditing?: boolean

/**
* A list of array actions to disable
* Possible options are defined by {@link ArrayActionName}
* @beta
*/
disableActions?: ArrayActionName[]
}

/** @public */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export function ArrayOfObjectsFunctions<
},
})

if (schemaType.options?.disableActions?.includes('add')) {
return null
}

if (readOnly) {
return (
<Tooltip portal content={t('inputs.array.read-only-label')}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const InsertMenuGroups = memo(function InsertMenuGroups(props: Props) {
)
})

function InsertMenuGroup(
export function InsertMenuGroup(
props: Props & {
pos: 'before' | 'after'
text: ComponentProps<typeof MenuItem>['text']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function useInsertMenuMenuItems(props: InsertMenuItemsProps) {
() =>
types ? (
<MenuItem
key="insertBefore"
text={
types.length === 1
? t('inputs.array.action.add-before')
Expand All @@ -93,6 +94,7 @@ export function useInsertMenuMenuItems(props: InsertMenuItemsProps) {
() =>
types ? (
<MenuItem
key="insertAfter"
text={
types.length === 1
? t('inputs.array.action.add-after')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function getTone({
const MENU_POPOVER_PROPS = {portal: true, tone: 'default'} as const

const BUTTON_CARD_STYLE = {position: 'relative'} as const

const EMPTY_ARRAY: never[] = []
export function PreviewItem<Item extends ObjectItem = ObjectItem>(props: PreviewItemProps<Item>) {
const {
schemaType,
Expand Down Expand Up @@ -144,9 +144,55 @@ export function PreviewItem<Item extends ObjectItem = ObjectItem>(props: Preview
referenceElement: contextMenuButtonElement,
})

const disableActions = parentSchemaType.options?.disableActions || EMPTY_ARRAY

const menuItems = useMemo(() => {
return [
!disableActions.includes('remove') && (
<MenuItem
key="remove"
text={t('inputs.array.action.remove')}
tone="critical"
icon={TrashIcon}
onClick={onRemove}
/>
),
!disableActions.includes('copy') && (
<MenuItem
key="copy"
text={t('inputs.array.action.copy')}
icon={CopyIcon}
onClick={handleCopy}
/>
),
!disableActions.includes('duplicate') && (
<MenuItem
key="duplicate"
text={t('inputs.array.action.duplicate')}
icon={AddDocumentIcon}
onClick={handleDuplicate}
/>
),
!disableActions.includes('add') &&
!disableActions.includes('addBefore') &&
insertBefore.menuItem,
!disableActions.includes('add') &&
!disableActions.includes('addAfter') &&
insertAfter.menuItem,
].filter(Boolean)
}, [
disableActions,
handleCopy,
handleDuplicate,
insertAfter.menuItem,
insertBefore.menuItem,
onRemove,
t,
])

const menu = useMemo(
() =>
readOnly ? null : (
readOnly || menuItems.length === 0 ? null : (
<>
<MenuButton
ref={setContextMenuButtonElement}
Expand All @@ -160,35 +206,14 @@ export function PreviewItem<Item extends ObjectItem = ObjectItem>(props: Preview
/>
}
id={`${props.inputId}-menuButton`}
menu={
<Menu>
<MenuItem
text={t('inputs.array.action.remove')}
tone="critical"
icon={TrashIcon}
onClick={onRemove}
/>
<MenuItem
text={t('inputs.array.action.copy')}
icon={CopyIcon}
onClick={handleCopy}
/>
<MenuItem
text={t('inputs.array.action.duplicate')}
icon={AddDocumentIcon}
onClick={handleDuplicate}
/>
{insertBefore.menuItem}
{insertAfter.menuItem}
</Menu>
}
menu={<Menu>{menuItems}</Menu>}
popover={MENU_POPOVER_PROPS}
/>
{insertBefore.popover}
{insertAfter.popover}
</>
),
[readOnly, insertBefore, insertAfter, props.inputId, t, onRemove, handleCopy, handleDuplicate],
[menuItems, readOnly, insertBefore, insertAfter, props.inputId],
)

const tone = getTone({readOnly, hasErrors, hasWarnings})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export function ArrayOfPrimitivesFunctions<
? 'inputs.array.action.add-item-select-type'
: 'inputs.array.action.add-item'

if (schemaType.options?.disableActions?.includes('add')) {
return null
}

if (readOnly) {
return (
<Tooltip portal content={t('inputs.array.read-only-label')}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {AddDocumentIcon, CopyIcon, TrashIcon} from '@sanity/icons'
import {AddDocumentIcon, CopyIcon, InsertAboveIcon, InsertBelowIcon, TrashIcon} from '@sanity/icons'
import {type SchemaType} from '@sanity/types'
import {Box, Flex, Menu} from '@sanity/ui'
import {type ForwardedRef, forwardRef, useCallback, useMemo} from 'react'
Expand All @@ -9,7 +9,7 @@ import {useTranslation} from '../../../../i18n'
import {FieldPresence} from '../../../../presence'
import {FormFieldValidationStatus} from '../../../components/formField'
import {type PrimitiveItemProps} from '../../../types/itemProps'
import {InsertMenuGroups} from '../ArrayOfObjectsInput/InsertMenuGroups'
import {InsertMenuGroup} from '../ArrayOfObjectsInput/InsertMenuGroups'
import {RowLayout} from '../layouts/RowLayout'
import {getEmptyValue} from './getEmptyValue'

Expand All @@ -33,6 +33,7 @@ export const ItemRow = forwardRef(function ItemRow(
onRemove,
readOnly,
inputId,
parentSchemaType,
validation,
children,
presence,
Expand Down Expand Up @@ -72,28 +73,61 @@ export const ItemRow = forwardRef(function ItemRow(

const {t} = useTranslation()

const disableActions = parentSchemaType.options?.disableActions || []

const menuItems = [
!disableActions.includes('remove') && (
<MenuItem
key="remove"
text={t('inputs.array.action.remove')}
tone="critical"
icon={TrashIcon}
onClick={onRemove}
/>
),
!disableActions.includes('copy') && (
<MenuItem
key="copy"
text={t('inputs.array.action.copy')}
icon={CopyIcon}
onClick={handleCopy}
/>
),
!disableActions.includes('duplicate') && (
<MenuItem
key="duplicate"
text={t('inputs.array.action.duplicate')}
icon={AddDocumentIcon}
onClick={handleDuplicate}
/>
),
!(disableActions.includes('add') || disableActions.includes('addBefore')) && (
<InsertMenuGroup
pos="before"
types={insertableTypes}
onInsert={handleInsert}
text={t('inputs.array.action.add-before')}
icon={InsertAboveIcon}
/>
),
!disableActions.includes('add') &&
!(disableActions.includes('addAfter') && disableActions.includes('addBefore')) && (
<InsertMenuGroup
pos="after"
types={insertableTypes}
onInsert={handleInsert}
text={t('inputs.array.action.add-after')}
icon={InsertBelowIcon}
/>
),
]

const menu = (
<MenuButton
button={<ContextMenuButton />}
id={`${inputId}-menuButton`}
popover={MENU_BUTTON_POPOVER_PROPS}
menu={
<Menu>
<MenuItem
text={t('inputs.array.action.remove')}
tone="critical"
icon={TrashIcon}
onClick={onRemove}
/>
<MenuItem text={t('inputs.array.action.copy')} icon={CopyIcon} onClick={handleCopy} />
<MenuItem
text={t('inputs.array.action.duplicate')}
icon={AddDocumentIcon}
onClick={handleDuplicate}
/>
<InsertMenuGroups types={insertableTypes} onInsert={handleInsert} />
</Menu>
}
menu={<Menu>{menuItems}</Menu>}
/>
)

Expand Down

0 comments on commit 8e079c5

Please sign in to comment.