Skip to content

Commit

Permalink
Move relationship config for relationships in component blocks (#7264)
Browse files Browse the repository at this point in the history
* Move relationship config for relationships in component blocks

* Change from cardinality to many

* Update document-fields.mdx
  • Loading branch information
emmatown committed Feb 28, 2022
1 parent 81e663d commit c9ec91c
Show file tree
Hide file tree
Showing 24 changed files with 367 additions and 362 deletions.
5 changes: 5 additions & 0 deletions .changeset/lemon-penguins-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/fields-document': major
---

The config for relationships in props on component blocks in the document field has moved so that it's configured at the relationship prop rather than in the `relationships` key on the document field config. The `relationships` key in the document field config now exclusively refers to inline relationships.
2 changes: 1 addition & 1 deletion docs/components/docs/DocumentEditorDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ const DocumentFeaturesContext = React.createContext<{

export function DocumentFeaturesProvider({ children }: { children: ReactNode }) {
const [formValue, setFormValue] = useState<DocumentFeaturesFormValue>(() =>
getInitialPropsValue(documentFeaturesProp, {})
getInitialPropsValue(documentFeaturesProp)
);
return (
<DocumentFeaturesContext.Provider
Expand Down
104 changes: 66 additions & 38 deletions docs/pages/docs/guides/document-fields.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@ export default config({
content: document({
relationships: {
mention: {
kind: 'inline',
listKey: 'Author',
label: 'Mention',
selection: 'id name',
Expand All @@ -190,9 +189,6 @@ export default config({
});
```

We use the `kind: 'inline'` option to indicate that we want to have an inline relationship.
The other option, `kind: 'prop'`, is used with custom component blocks, which are discussed [below](#component-blocks).

When you add an inline relationship to your document field, it becomes accessible in the Admin UI behind the `+` icon.
This menu uses the `label` specified in the relationship config.

Expand Down Expand Up @@ -506,7 +502,60 @@ export const componentBlocks = {

There are a variety of fields you can configure.

{/** TBC: ### Child Fields **/}
#### Child Fields

Child fields allow you to embed an editable region inside of a component block preview.

They can either have `kind: 'inline'`, or `kind: 'block'`.
This refers to **what elements can be inside of them**.
`kind: 'inline'` child fields can only contain text with marks/links/inline relationships.
`kind: 'block'` child fields contain block-level elements such as paragraph, lists, etc.

They require a placeholder which is shown when there is no text inside the child field.
The placeholder is required though it can be an empty string if it'll be clear to editors that the location of the child field is editable.

By default, child fields can only contain plain text, if you'd like to enable other features of the document editor inside a child field, you can enable the features similarly to enabling them in the document field config.
Unlike the document field config though, these options accept `'inherit'` instead of `true`, this is because if `'inherit'` is set then that feature will be enabled **if it's also enabled at the document field config level** so you can't enable features in a child field but not in the rest of the document field.

In the preview, child fields appear as React nodes that should be rendered.
Note that you **must** render child fields in the preview.
If they are not rendered, you will receive errors.

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

component({
component: ({ attribution, content }) => {
return (
<div
style={{
borderLeft: '3px solid #CBD5E0',
paddingLeft: 16,
}}
>
<div style={{ fontStyle: 'italic', color: '#4A5568' }}>{content}</div>
<div style={{ fontWeight: 'bold', color: '#718096' }}>
<NotEditable>— </NotEditable>
{attribution}
</div>
</div>
);
},
label: 'Quote',
props: {
content: fields.child({
kind: 'block',
placeholder: 'Quote...',
formatting: { inlineMarks: 'inherit', softBreaks: 'inherit' },
links: 'inherit',
}),
attribution: fields.child({ kind: 'inline', placeholder: 'Attribution...' }),
},
chromeless: true,
})
```

> Note: You have to be careful to wrap `NotEditable` around other elements in the preview but you cannot wrap it around a child field.
#### Form Fields

Expand Down Expand Up @@ -568,42 +617,21 @@ fields.object({

#### Relationship Fields

To use relationship fields on component blocks, you need to add a relationship to the document field config.

```tsx
import { config, list } from '@keystone-6/core';
import { document } from '@keystone-6/fields-document';

export default config({
lists: {
ListName: list({
fields: {
fieldName: document({
relationships: {
featuredAuthors: {
kind: 'prop',
listKey: 'Author',
selection: 'id title',
many: true,
},
},
/* ... */
}),
/* ... */
},
}),
/* ... */
},
/* ... */
});
```

You can reference the key of the relationship in the relationship and in the form, it will render a relationship select like the relationship field on lists.
To use relationship fields on component blocks, you need to add a relationship field and provide a list key, label and options selection.
In the form, it will render a relationship select like the relationship field on lists.
Similarly to inline relationships, they will be hydrated with this selection if `hydrateRelationships: true` is provided when fetching.

```tsx
import { fields } from '@keystone-6/fields-document/component-blocks';

fields.relationship({ label: 'Authors', relationship: 'featuredAuthors' });
...
someField: fields.relationship({
label: 'Authors',
listKey: 'Author',
selection: 'id name posts { title }',
many: true,
});
...
```

> Note: Like inline relationships, relationship fields on component blocks are not stored like relationship fields on lists, they are stored as ids in the document structure.
Expand Down Expand Up @@ -637,7 +665,7 @@ fields.conditional(fields.checkbox({ label: 'Show Call to action' }), {

> You might find `fields.empty()` useful which stores and renders nothing if you want to have a field in one case and nothing anything in another
{/** TBC: ### Preview Props **/}
{/* * TBC: ### Preview Props * */}

### Chromeless

Expand Down
7 changes: 6 additions & 1 deletion examples-staging/basic/admin/fieldViews/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,12 @@ export const componentBlocks = {
},
props: {
title: fields.child({ kind: 'inline', placeholder: 'Title...' }),
authors: fields.relationship<'many'>({ label: 'Authors', relationship: 'featuredAuthors' }),
authors: fields.relationship({
label: 'Authors',
listKey: 'User',
many: true,
selection: `posts(take: 10) { title }`,
}),
},
}),
notice: component({
Expand Down
9 changes: 0 additions & 9 deletions examples-staging/basic/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,18 +153,9 @@ export const lists: Keystone.Lists = {
ui: { views: require.resolve('./admin/fieldViews/Content.tsx') },
relationships: {
mention: {
kind: 'inline',
label: 'Mention',
listKey: 'User',
},
featuredAuthors: {
kind: 'prop',
listKey: 'User',
many: true,
selection: `posts(take: 10) {
title
}`,
},
},
formatting: true,
layouts: [
Expand Down
1 change: 0 additions & 1 deletion examples/document-field/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export const lists = {
// inline relationship which references the `Author` list.
relationships: {
mention: {
kind: 'inline',
listKey: 'Author',
label: 'Mention', // This will display in the Admin UI toolbar behind the `+` icon
selection: 'id name', // These fields will be available to the renderer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ export type ChildField = {
};
};

export type RelationshipField<Cardinality extends 'one' | 'many'> = {
export type RelationshipField<Many extends boolean> = {
kind: 'relationship';
relationship: string;
listKey: string;
selection: string | undefined;
label: string;
cardinality?: Cardinality;
many: Many;
};

export interface ObjectField<
Expand Down Expand Up @@ -122,7 +123,7 @@ export type ComponentPropField =
| FormField<any, any>
| ObjectField
| ConditionalField<any, any, any>
| RelationshipField<'one' | 'many'>;
| RelationshipField<boolean>;

export const fields = {
text({
Expand Down Expand Up @@ -387,14 +388,25 @@ export const fields = {
values: values,
};
},
relationship<Cardinality extends 'one' | 'many'>({
relationship,
relationship<Many extends boolean | undefined = false>({
listKey,
selection,
label,
many,
}: {
relationship: string;
listKey: string;
label: string;
}): RelationshipField<Cardinality> {
return { kind: 'relationship', relationship, label };
selection?: string;
} & (Many extends undefined | false ? { many?: Many } : { many: Many })): RelationshipField<
Many extends true ? true : false
> {
return {
kind: 'relationship',
listKey,
selection,
label,
many: (many ? true : false) as any,
};
},
};

Expand Down Expand Up @@ -450,17 +462,16 @@ export type ExtractPropFromComponentPropFieldForPreview<Prop extends ComponentPr
: never;
};
}[DiscriminantToString<Discriminant>]
: Prop extends RelationshipField<infer Cardinality>
: Prop extends RelationshipField<true>
? {
one: {
readonly value: HydratedRelationshipData | null;
onChange(relationshipData: HydratedRelationshipData | null): void;
};
many: {
readonly value: readonly HydratedRelationshipData[];
onChange(relationshipData: readonly HydratedRelationshipData[]): void;
};
}[Cardinality]
readonly value: readonly HydratedRelationshipData[];
onChange(relationshipData: readonly HydratedRelationshipData[]): void;
}
: Prop extends RelationshipField<false>
? {
readonly value: HydratedRelationshipData | null;
onChange(relationshipData: HydratedRelationshipData | null): void;
}
: never;

type ExtractPropFromComponentPropFieldForToolbar<Prop extends ComponentPropField> =
Expand All @@ -487,17 +498,16 @@ type ExtractPropFromComponentPropFieldForToolbar<Prop extends ComponentPropField
: never;
};
}[DiscriminantToString<Discriminant>]
: Prop extends RelationshipField<infer Cardinality>
: Prop extends RelationshipField<true>
? {
one: {
readonly value: HydratedRelationshipData | null;
onChange(relationshipData: HydratedRelationshipData | null): void;
};
many: {
readonly value: readonly HydratedRelationshipData[];
onChange(relationshipData: readonly HydratedRelationshipData[]): void;
};
}[Cardinality]
readonly value: readonly HydratedRelationshipData[];
onChange(relationshipData: readonly HydratedRelationshipData[]): void;
}
: Prop extends RelationshipField<false>
? {
readonly value: HydratedRelationshipData | null;
onChange(relationshipData: HydratedRelationshipData | null): void;
}
: never;

export type HydratedRelationshipData = {
Expand Down Expand Up @@ -585,11 +595,10 @@ type ExtractPropFromComponentPropFieldForRendering<Prop extends ComponentPropFie
: never;
};
}[DiscriminantToString<Discriminant>]
: Prop extends RelationshipField<infer Cardinality>
? {
one: HydratedRelationshipData | null;
many: readonly HydratedRelationshipData[];
}[Cardinality]
: Prop extends RelationshipField<true>
? readonly HydratedRelationshipData[]
: Prop extends RelationshipField<false>
? HydratedRelationshipData | null
: never;

type ExtractPropsForPropsForRendering<Props extends Record<string, ComponentPropField>> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,6 @@ function makeEditorWithChildField(
},
relationships: {
mention: {
kind: 'inline',
label: 'Mention',
listKey: 'User',
selection: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { FieldContainer, FieldLabel } from '@keystone-ui/fields';
import React, { useState } from 'react';
import { Button as KeystoneUIButton } from '@keystone-ui/button';
import { ComponentPropField, RelationshipData, ComponentBlock } from '../../component-blocks';
import { useDocumentFieldRelationships, Relationships } from '../relationship';
import { assertNever, getPropsForConditionalChange } from './utils';
import { RelationshipField } from './api';

Expand All @@ -20,17 +19,12 @@ function RelationshipFormInput({
stringifiedPropPathToAutoFocus,
}: {
path: (string | number)[];
prop: RelationshipField<any>;
prop: RelationshipField<boolean>;
value: any;
onChange(value: any): void;
stringifiedPropPathToAutoFocus: string;
}) {
const relationships = useDocumentFieldRelationships();
const keystone = useKeystone();
const relationship = relationships[prop.relationship] as Extract<
Relationships[string],
{ kind: 'prop' }
>;
const stringifiedPath = JSON.stringify(path);
return (
<FieldContainer>
Expand All @@ -39,11 +33,11 @@ function RelationshipFormInput({
autoFocus={stringifiedPath === stringifiedPropPathToAutoFocus}
controlShouldRenderValue
isDisabled={false}
list={keystone.adminMeta.lists[relationship.listKey]}
extraSelection={relationship.selection || ''}
list={keystone.adminMeta.lists[prop.listKey]}
extraSelection={prop.selection || ''}
portalMenu
state={
relationship.many
prop.many
? {
kind: 'many',
value: (value as RelationshipData[]).map(x => ({
Expand Down Expand Up @@ -84,7 +78,6 @@ export function FormValueContent({
stringifiedPropPathToAutoFocus: string;
forceValidation: boolean;
}) {
const relationships = useDocumentFieldRelationships();
if (prop.kind === 'child') return null;
if (prop.kind === 'object') {
return (
Expand Down Expand Up @@ -113,12 +106,7 @@ export function FormValueContent({
value={value.discriminant}
onChange={discriminant => {
onChange(
getPropsForConditionalChange(
{ discriminant, value: value.value },
value,
prop,
relationships
)
getPropsForConditionalChange({ discriminant, value: value.value }, value, prop)
);
}}
forceValidation={forceValidation && !prop.discriminant.validate(value)}
Expand Down
Loading

0 comments on commit c9ec91c

Please sign in to comment.