sanity-io / sanity

Sanity Studio – Rapidly configure content workspaces powered by structured content
https://www.sanity.io
MIT License
5.2k stars 420 forks source link

It should be possible to edit array items inline #921

Open bjoerge opened 6 years ago

bjoerge commented 6 years ago

Currently array items are rendered as previews. It would be useful to offer a mode where array items can be edited inline/in-place. This is especially useful for arrays that consists of objects with very few fields.

Inline editing is currently the only mode for arrays of primitives. We should also consider if we could support the same option here (but keeping current behavior as default).

Example schema:

// array of object values
{
  type: 'array',
  itemEditMode: 'inline', // default is 'modal'
  of: [{type: 'object', fields: [{name: 'value', type: 'string'}]}]
}

// array of primitive values
{
  type: 'array',
  itemEditMode: 'modal', // default is 'inline'
  of: [{type: 'string'}]
}
djfarly commented 4 years ago

I know this is kinda old… but is it still considered? Would be cool to have something like this. Also for object types inside the block editor. 😬

EECOLOR commented 2 years ago

This should now be fairly simple to implement. This is a hacked version that depends on classes that are not officially public (I think):

import ArrayInput from 'part:@sanity/form-builder/input/array'
import { ObjectInput } from '@sanity/form-builder/lib/inputs/ObjectInput'
import { DragHandle } from '@sanity/form-builder/lib/inputs/arrays/common/DragHandle'
import React from 'react'
import ArrayFunctions from 'part:@sanity/form-builder/input/array/functions'
import { resolveInitialValueForType } from '@sanity/initial-value-templates'

const pretendToBeAReference = { to: [] }
export const page = {
  type: 'document',
  name: 'page',
  title: 'Page',
  fields: [
    {
      type: 'string',
      name: 'title',
      title: 'Title',
    },
    {
      type: 'array',
      name: 'content',
      title: 'Content',
      of: [
        {
          type: 'object',
          name: 'test-object',
          title: 'Test object',
          fields: [
            {
              type: 'string',
              name: 'title',
              title: 'Titel',
              validation: Rule => Rule.min(5)
            }
          ],
        },
      ],
      inputComponent: React.forwardRef(function CustomArrayInput(props, ref) {
        const originalType = props.type
        const newType = {
          ...originalType,
          of: originalType.of.map(x =>
            x.type.name === 'object'
              ? { ...x, ...pretendToBeAReference }
              : x
          )
        }

        return (
          <ArrayInput
            {...props}
            type={newType}
            ref={ref}
            ReferenceItemComponent={React.forwardRef(InlineObjectInput)}
            // resolveUploader={arrayResolveUploader}
            resolveInitialValue={resolveInitialValueForType}
            ArrayFunctionsImpl={ArrayFunctions}
          />
        )
      })
    },

  ],
})

function InlineObjectInput(props, ref) {
  return (
    <>
      <DragHandle />
      <ObjectInput {...props} ref={ref} />
    </>
  )
}
Glavin001 commented 2 years ago

@EECOLOR and others: Did you end up with a solid solution? The above code doesn't work 💯% for me:

Error: Invalid call to useReporter(...): A component reporting on "field-..." is already mounted in the subtree. Make sure that all reporters within the same <Tracker> subtree have unique ids.

and also it unmounts & re-mounts the InlineObjectInput on each change (e.g. keypress in a string input).

EECOLOR commented 2 years ago

@Glavin001 No, I was hoping a Sanity developer would use it as inspiration to create a proper inline version.

I got this idea once I saw the 'inline reference editor'. With that addition the internal machinery seems all present for inline editing.

kmelve commented 2 years ago

Hi folks! Just wanted to chime in and say that we're still considering how to tackle this issue. The good news is that with v3 we'll be able to move faster and more reliably and that we've initated new dedicated efforts for improving the editor experience specifically. Stay tuned!

hallatore commented 1 year ago

@kmelve Any update on this, or how to achieve with some workaround?

We have an array of objects, that each contain a list of documents. We want to be able to drag a document from one object list to another in one go. Today the editors have to remove if from the first. Navigate. And then find it again on the last item.

andrewabril commented 7 months ago

Would love this feature as well - this workflow makes it much more convenient and faster to write content in a single pane, without the added mouse clicking and waiting for a modal to appear and disappear. It's also a huge benefit to be able to see all the items in an array that you can edit when you have custom components like a FAQ section within portable text.

cronin4392 commented 4 months ago

This is a major thing holding my team back from adopting Sanity. We're utilizing array fields quite a bit and some of our data structures are a few levels deep (an array item will have it's own array field). Right now they have to open a modal for each layer that has an array field and it's not a very nice experience.

Vishwas26 commented 2 months ago

Hi, There is a work around to achieve editing array item inline. But it is not straight forward. Each of the array item has item components, it provides you with ObjectItemProps. ObjectItemProps has inputProps, inputProps has renderPreview, pass ObjectItemProps.props.children. This should replace preview with form fields.

import { defineArrayMember, defineField, defineType } from "sanity";
import { ObjectItemProps } from "sanity";
import { Card } from "@sanity/ui";

export default defineType({
  name: "test",
  type: "document",
  title: "Test",
  fields: [
    defineField({
      name: "bbw",
      type: "array",
      of: [
        defineArrayMember({
          type: "object",
          fields: [
            defineField({
              name: "test_header",
              type: "string",
              validation: (Rule) => Rule.required(),
            }),
          ],
          components: {
            item: (props: ObjectItemProps) => {
              const { inputProps, ...restProps } = props;
              const { renderPreview, ...restInputProps } = inputProps;

              return (
                <Card>
                  {restProps.renderDefault({
                    ...restProps,
                    inputProps: {
                      ...restInputProps,
                      renderPreview: (previewProps) => (
                        <Card>{props.children}</Card>
                      ),
                    },
                    open: false,
                  })}
                </Card>
              );
            },
          },
        }),
      ],
    }),
  ],
});