👋 Next.js Conf 2024: Come build, party, run, and connect with us! See all events

Remove Array item's actions from the context menu

By Sigve Skaugvoll

This Input component is used for removing e.g the Duplicate action for items in a list

How to apply the custom Input component

import { InputProps, defineArrayMember, defineField, defineType } from 'sanity';
import { ArrayMembersWithoutActions } from '@components/input/ArrayMembersWithoutActions';

export default defineType({
  name: 'Test',
  title: 'Test',
  type: 'document',
  fields: [
    defineField({
      name: 'content',
      title: 'Content',
      type: 'array',
      components: {
        input: (props: InputProps) =>
          ArrayMembersWithoutActions(props, {
            removeActions: ['Duplicate'],
          }),
      },
      of: [
        defineArrayMember({
          type: 'customType',
        }),
      ],
    }),
  ],
});

Custom Input Component

// ./components/input/ArrayMembersWithoutActions.tsx
import type { InputProps } from 'sanity';

/**
 * If context menu changes, e.g removes or adds new actions, this should be updated to reflect the changes and allow for removal of new action or remove option to remove non-exisiting action.
 */
type Actions = 'Remove' | 'Duplicate' | 'Add item before' | 'Add item after';

type Context = {
  /* List of Array Member context menu's actions to remove */
  removeActions: Actions[];
};

/**
 * Important to note, if the sanity context menu changes, this order needs to be maintained, to reflect the options and order
 * The action is mapped to the order of listing in the Context menu from Top (1) To Bottom (N > 1)
 */
const ActionsMenuOrder: Record<Actions, number> = {
  Remove: 1,
  Duplicate: 2,
  'Add item before': 3,
  'Add item after': 4,
};

/**
 * Array Input wrapper component to remove ArrayMember Menu action(s).
 * @param props {@link InputProps}
 * @param context {@link Context}
 * @returns Array Input Component that removes specified actions from ArrayMembers context menu.
 * 
 * @example
 * ```ts
 * defineField({
  name: "a",
  title: "A",
  type: "array",
  components: {
    input: (props: InputProps) =>
      ArrayMembersWithoutAction(props, {
        actions: ["Duplicate"],
      }),
  },
  of: [...],
  ...,
});
 * ```
 */
export const ArrayMembersWithoutActions = (props: InputProps, context: Context) => {
  const arraySchemaName = props.elementProps.id;
  const buttonRemovals = context.removeActions.map((action) => {
    return `
      div[data-ui="Menu"][aria-labelledby^="${arraySchemaName}"] div[data-ui="Stack"] button[data-ui="MenuItem"]:nth-child(${ActionsMenuOrder[action]}) {
        display: none;
      }
    `;
  });

  return (
    <div>
      <style>{buttonRemovals.join('\n')}</style>
      {props.renderDefault(props)}
    </div>
  );
};

This custom Input component is applied to a field of type arrayand allows you to remove one or more actions from the context menu for each array member.

When or why would I want to remove actions from the context menu?
When duplicating an array item which has a reference to another document, the duplicate array item will keep the reference to the same referenced entity as the original array item.

Thus, if the referenced entity is edited through the duplicate array item, the changes to the referenced entity apply to both the original array item and the duplicate.

To avoid making unwanted changes to the referenced content, it can be smart to remove the action for duplicate, to avoid unwanted changes and add a custom button to the end of the item component, which will clone the referenced document and create a new array item referencing the clone.

Contributor