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

Add support for Array Fields in component blocks (breaking) #7428

Merged
merged 137 commits into from
May 25, 2022

Conversation

emmatown
Copy link
Member

@emmatown emmatown commented Apr 4, 2022

Warning: This new feature includes breaking changes that may affect you


This pull request was originally going to land a new field, tentatively called the "structured" field, that takes the schema defined for component blocks and allows storing them in a standalone field.
For now, we're landing some of the improvements made to support that work such as adding array fields.

This pull request introduces array fields for component blocks.
Array fields are a new component block field that contain 0 to many of another field type, like an object, conditional, form or child field.

This pull request additionally contains substantial changes to the component blocks API.
The breaking changes are entirely about defining components, no data migration is required.

Additionally, there are some smaller improvements:

  • All preview props now have an onChange function so that you can update more than one field in a component at a time
  • All preview props now have a schema property to access the schema for those preview props
  • Preview props are now referentially stable between renders when their value is stable

The breaking changes for @keystone-6/fields-document/component-blocks are:

  • ⚠️ For the arguments of the component function, rename component to preview
  • ⚠️ For the arguments of the component function, rename props to schema
  • ⚠️ For your component .schema (previously .props), rename props.{innerFieldName} to props.fields.{innerFieldName}.
  • ⚠️ When rendering child field React components, change props.{innerFieldName} to props.{innerFieldName}.element.

For example, use props.fields.title instead of props.title.
For a nested example, use props.fields.someObject.fields.title instead of props.someObject.title.

As an example, the changes needed for updating the "Hero" component block as seen on https://keystonejs.com/docs/guides/document-field-demo is shown shown below

   hero: component({
-    component: props => {
+    preview: props => {
       return (
         <div
           css={{
             backgroundColor: 'white',
-            backgroundImage: `url(${props.imageSrc.value})`,
+            backgroundImage: `url(${props.fields.imageSrc.value})`,
             backgroundPosition: 'center',
             backgroundSize: 'cover',
             display: 'flex',
@@ -51,7 +189,7 @@ export const componentBlocks = {
               textShadow: '0px 1px 3px black',
             }}
           >
-            {props.title}
+            {props.fields.title.element}
           </div>
           <div
             css={{
@@ -63,9 +201,9 @@ export const componentBlocks = {
               textShadow: '0px 1px 3px black',
             }}
           >
-            {props.content}
+            {props.fields.content.element}
           </div>
-          {props.cta.discriminant ? (
+          {props.fields.cta.discriminant ? (
             <div
               css={{
                 backgroundColor: '#F9BF12',
@@ -78,14 +216,14 @@ export const componentBlocks = {
                 padding: '12px 16px',
               }}
             >
-              {props.cta.value.text}
+              {props.fields.cta.value.fields.text.element}
             </div>
           ) : null}
         </div>
       );
     },
     label: 'Hero',
-    props: {
+    schema: {
       title: fields.child({ kind: 'inline', placeholder: 'Title...' }),
       content: fields.child({ kind: 'block', placeholder: '...' }),
       imageSrc: fields.text({

fields.array

The new array field for component blocks represents an array of another field type, such as an object, conditional, form or other child field.
When there is a single child field within an array field, the document editor will allow pressing enter at the end of an element to add/delete at the start to delete, this allows creating editing experiences similar to the built-in lists.

See below for an example of a question & answers component block with the new array field:

import { fields, component, NotEditable } from '@keystone-6/fields-document/component-blocks';

component({
  label: 'Questions & Answers',
  schema: {
    questions: fields.array(
      fields.object({
        question: fields.child({ placeholder: 'Question', kind: 'inline' }),
        answer: fields.child({ placeholder: 'Answer', formatting: 'inherit', kind: 'block' }),
      })
    ),
  },
  preview: props => {
    return (
      <div>
        {props.fields.questions.elements.map(questionAndAnswer => {
          return (
            <div key={questionAndAnswer.key}>
              <h2>{questionAndAnswer.fields.question.element}</h2>
              <p>{questionAndAnswer.fields.answer.element}</p>
              <NotEditable>
                <Button
                  onClick={() => {
                    props.fields.questions.onChange(
                      props.fields.questions.elements
                        .filter(x => x.key !== questionAndAnswer.key)
                        .map(x => ({ key: x.key }))
                    );
                  }}
                >
                  Remove
                </Button>
              </NotEditable>
            </div>
          );
        })}
        <NotEditable>
          <Button
            onClick={() => {
              props.fields.questions.onChange([
                ...props.fields.questions.elements,
                { key: undefined },
              ]);
            }}
          >
            Insert
          </Button>
        </NotEditable>
      </div>
    );
  },
});

Similar to the built-in document-editor lists, when an array field has only 1 element, pressing enter adds a new element and pressing delete removes an element.
For example, here's a list of checkboxes:

/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx } from '@keystone-ui/core';
import { useEffect } from 'react';
import { fields, component } from '@keystone-6/fields-document/component-blocks';

component({
  label: 'Checkbox List',
  schema: {
    children: fields.array(
      fields.object({
        done: fields.checkbox({ label: 'Done' }),
        content: fields.child({ kind: 'inline', placeholder: '', formatting: 'inherit' }),
      })
    ),
  },
  chromeless: true,
  preview: (props) => {
    useEffect(() => {
      if (!props.fields.children.elements.length) {
        props.fields.children.onChange([{ key: undefined }]);
      }
    });

    return (
      <ul css={{ padding: 0 }}>
        {props.fields.children.elements.map(element => (
          <li css={{ listStyle: 'none' }} key={element.key}>
            <input
              contentEditable="false"
              css={{ marginRight: 8 }}
              type="checkbox"
              checked={element.fields.done.value}
              onChange={event => element.fields.done.onChange(event.target.checked)}
            />
            <span
              style={{
                textDecoration: element.fields.done.value ? 'line-through' : undefined,
              }}
            >
              {element.fields.content.element}
            </span>
          </li>
        ))}
      </ul>
    );
  },
});

Finally, some other changes introduced in this release are:

  • Each of the preview props fields (and their inner fields, if any) now have an onChange function so that you can update more than one field in a component at a time
  • Each of the preview props fields (and their inner fields, if any) now have a schema property to access their respective schema at that level
  • Generally, preview props are now referentially stable between renders when their value is stable

Some internal breaking changes that are unlikely to affect users are:

  • The ComponentPropField type is now named ComponentSchema
  • FormField's are now constrained to prevent storing undefined.
    They must be a string, number, boolean, null, array of one of these or an object with one of these.
    This is required so that they can be represented within a JSON array.
  • Within the database, for the props object on a component-block node, child fields are now stored as null instead of undefined.
    This is required so that they can be represented within a JSON array.
    Component-block nodes that previously had undefined instead of null for a child field will continue to work though, no data migration is required.
  • The ObjectField type now has inner fields on a property named fields instead of value
  • The ConditionalField type now has two type parameters that look like this:
    type ConditionalField<
      DiscriminantField extends FormField<string | boolean, any>,
      ConditionalValues extends {
        [Key in `${DiscriminantField['defaultValue']}`]: ComponentSchema;
      }
    > = ...

@vercel vercel bot temporarily deployed to Preview May 25, 2022 01:45 Inactive
@vercel vercel bot temporarily deployed to Preview May 25, 2022 04:42 Inactive
@vercel vercel bot temporarily deployed to Preview May 25, 2022 04:58 Inactive
@vercel vercel bot temporarily deployed to Preview May 25, 2022 05:14 Inactive
@vercel vercel bot temporarily deployed to Preview May 25, 2022 06:05 Inactive
@vercel vercel bot temporarily deployed to Preview May 25, 2022 06:10 Inactive
@vercel vercel bot temporarily deployed to Preview May 25, 2022 06:23 Inactive
@dcousens dcousens enabled auto-merge (squash) May 25, 2022 06:31
@vercel vercel bot temporarily deployed to Preview May 25, 2022 06:33 Inactive
@dcousens dcousens force-pushed the component-field-type branch from ab205e9 to efa2c79 Compare May 25, 2022 06:37
@vercel vercel bot temporarily deployed to Preview May 25, 2022 06:40 Inactive
@dcousens dcousens merged commit ccbc248 into main May 25, 2022
@dcousens dcousens deleted the component-field-type branch May 25, 2022 06:48
@dcousens dcousens changed the title Component Block Updates Add support for Array Fields in component blocks (breaking) May 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants