Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(form): add support for disabling various array input capabilities #7615

Draft
wants to merge 1 commit into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading