Skip to content

Commit

Permalink
First pass of structure field from early 2022
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown committed Sep 19, 2022
1 parent f3c1a2b commit 16806ac
Show file tree
Hide file tree
Showing 13 changed files with 1,544 additions and 14 deletions.
123 changes: 112 additions & 11 deletions packages/fields-document/src/DocumentEditor/component-blocks/api.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @jsxRuntime classic */
/** @jsx jsx */
import { graphql } from '@keystone-6/core';
import { jsx } from '@keystone-ui/core';
import {
FieldContainer,
Expand Down Expand Up @@ -50,6 +51,22 @@ export type FormField<Value extends FormFieldValue, Options> = {
* a potentially malicious client
*/
validate(value: unknown): boolean;
preview?: PreviewComponent;
};

export type FormFieldWithGraphQLField<Value extends FormFieldValue, Options> = FormField<
Value,
Options
> & {
graphql: {
output: graphql.Field<
{ value: Value },
Record<string, graphql.Arg<graphql.InputType, boolean>>,
graphql.OutputType,
'value'
>;
input: graphql.NullableInputType;
};
};

type InlineMarksConfig =
Expand Down Expand Up @@ -100,6 +117,7 @@ export type ChildField = {
export type ArrayField<ElementField extends ComponentSchema> = {
kind: 'array';
element: ElementField;
preview?: PreviewComponent;
};

export type RelationshipField<Many extends boolean> = {
Expand All @@ -108,13 +126,15 @@ export type RelationshipField<Many extends boolean> = {
selection: string | undefined;
label: string;
many: Many;
preview?: PreviewComponent;
};

export interface ObjectField<
Fields extends Record<string, ComponentSchema> = Record<string, ComponentSchema>
> {
kind: 'object';
fields: Fields;
preview?: PreviewComponent;
}

export type ConditionalField<
Expand All @@ -126,25 +146,44 @@ export type ConditionalField<
kind: 'conditional';
discriminant: DiscriminantField;
values: ConditionalValues;
preview?: PreviewComponent;
};

type PreviewComponent = (props: unknown) => ReactElement | null;
type TypedPreviewComponent<Field extends ComponentSchema> = (
props: PreviewProps<Field> & { autoFocus?: boolean }
) => ReactElement | null;

export type ComponentSchema =
| ChildField
| FormField<any, any>
| ObjectField
| ConditionalField<FormField<any, any>, { [key: string]: ComponentSchema }>
| RelationshipField<boolean>
// this is written like this rather than ArrayField<ComponentSchema> to avoid TypeScript erroring about circularity
| { kind: 'array'; element: ComponentSchema };
| { kind: 'array'; element: ComponentSchema; preview?: PreviewComponent };

export type ComponentSchemaForGraphQL =
| FormFieldWithGraphQLField<any, any>
| ObjectField<Record<string, ComponentSchemaForGraphQL>>
| ConditionalField<
FormFieldWithGraphQLField<any, any>,
{ [key: string]: ComponentSchemaForGraphQL }
>
| RelationshipField<boolean>
// this is written like this rather than ArrayField<ComponentSchemaForGraphQL> to avoid TypeScript erroring about circularity
| { kind: 'array'; element: ComponentSchemaForGraphQL; preview?: PreviewComponent };

export const fields = {
text({
label,
defaultValue = '',
preview,
}: {
label: string;
defaultValue?: string;
}): FormField<string, undefined> {
preview?: TypedPreviewComponent<FormFieldWithGraphQLField<string, undefined>>;
}): FormFieldWithGraphQLField<string, undefined> {
return {
kind: 'form',
Input({ value, onChange, autoFocus }) {
Expand All @@ -166,15 +205,22 @@ export const fields = {
validate(value) {
return typeof value === 'string';
},
graphql: {
input: graphql.String,
output: graphql.field({ type: graphql.String }),
},
preview: preview as PreviewComponent,
};
},
url({
label,
defaultValue = '',
preview,
}: {
label: string;
defaultValue?: string;
}): FormField<string, undefined> {
preview?: TypedPreviewComponent<FormFieldWithGraphQLField<string, undefined>>;
}): FormFieldWithGraphQLField<string, undefined> {
const validate = (value: unknown) => {
return typeof value === 'string' && (value === '' || isValidURL(value));
};
Expand Down Expand Up @@ -203,17 +249,24 @@ export const fields = {
options: undefined,
defaultValue,
validate,
graphql: {
input: graphql.String,
output: graphql.field({ type: graphql.String }),
},
preview: preview as PreviewComponent,
};
},
select<Option extends { label: string; value: string }>({
label,
options,
defaultValue,
preview,
}: {
label: string;
options: readonly Option[];
defaultValue: Option['value'];
}): FormField<Option['value'], readonly Option[]> {
preview?: TypedPreviewComponent<FormFieldWithGraphQLField<Option['value'], readonly Option[]>>;
}): FormFieldWithGraphQLField<Option['value'], readonly Option[]> {
const optionValuesSet = new Set(options.map(x => x.value));
if (!optionValuesSet.has(defaultValue)) {
throw new Error(
Expand Down Expand Up @@ -244,17 +297,32 @@ export const fields = {
validate(value) {
return typeof value === 'string' && optionValuesSet.has(value);
},
graphql: {
input: graphql.String,
output: graphql.field({
type: graphql.String,
// TODO: investigate why this resolve is required here
resolve({ value }) {
return value;
},
}),
},
preview: preview as PreviewComponent,
};
},
multiselect<Option extends { label: string; value: string }>({
label,
options,
defaultValue,
preview,
}: {
label: string;
options: readonly Option[];
defaultValue: readonly Option['value'][];
}): FormField<readonly Option['value'][], readonly Option[]> {
preview?: TypedPreviewComponent<
FormFieldWithGraphQLField<readonly Option['value'][], readonly Option[]>
>;
}): FormFieldWithGraphQLField<readonly Option['value'][], readonly Option[]> {
const valuesToOption = new Map(options.map(x => [x.value, x]));
return {
kind: 'form',
Expand All @@ -281,15 +349,28 @@ export const fields = {
value.every(value => typeof value === 'string' && valuesToOption.has(value))
);
},
graphql: {
input: graphql.list(graphql.nonNull(graphql.String)),
output: graphql.field({
type: graphql.list(graphql.nonNull(graphql.String)),
// TODO: investigate why this resolve is required here
resolve({ value }) {
return value;
},
}),
},
preview: preview as PreviewComponent,
};
},
checkbox({
label,
defaultValue = false,
preview,
}: {
label: string;
defaultValue?: boolean;
}): FormField<boolean, undefined> {
preview?: TypedPreviewComponent<FormFieldWithGraphQLField<boolean, undefined>>;
}): FormFieldWithGraphQLField<boolean, undefined> {
return {
kind: 'form',
Input({ value, onChange, autoFocus }) {
Expand All @@ -312,6 +393,11 @@ export const fields = {
validate(value) {
return typeof value === 'boolean';
},
graphql: {
input: graphql.Boolean,
output: graphql.field({ type: graphql.Boolean }),
},
preview: preview as PreviewComponent,
};
},
empty(): FormField<null, undefined> {
Expand Down Expand Up @@ -384,8 +470,13 @@ export const fields = {
},
};
},
object<Fields extends Record<string, ComponentSchema>>(fields: Fields): ObjectField<Fields> {
return { kind: 'object', fields };
object<Value extends Record<string, ComponentSchema>>(
value: Value,
opts?: {
preview?: TypedPreviewComponent<ObjectField<Value>>;
}
): ObjectField<Value> {
return { kind: 'object', fields: value, preview: opts?.preview as PreviewComponent };
},
conditional<
DiscriminantField extends FormField<string | boolean, any>,
Expand All @@ -394,7 +485,10 @@ export const fields = {
}
>(
discriminant: DiscriminantField,
values: ConditionalValues
values: ConditionalValues,
opts?: {
preview?: TypedPreviewComponent<ConditionalField<DiscriminantField, ConditionalValues>>;
}
): ConditionalField<DiscriminantField, ConditionalValues> {
if (
(discriminant.validate('true') || discriminant.validate('false')) &&
Expand All @@ -408,16 +502,19 @@ export const fields = {
kind: 'conditional',
discriminant,
values: values,
preview: opts?.preview as PreviewComponent,
};
},
relationship<Many extends boolean | undefined = false>({
listKey,
selection,
label,
preview,
many,
}: {
listKey: string;
label: string;
preview?: TypedPreviewComponent<RelationshipField<Many extends true ? true : false>>;
selection?: string;
} & (Many extends undefined | false ? { many?: Many } : { many: Many })): RelationshipField<
Many extends true ? true : false
Expand All @@ -428,10 +525,14 @@ export const fields = {
selection,
label,
many: (many ? true : false) as any,
preview: preview as PreviewComponent,
};
},
array<ElementField extends ComponentSchema>(element: ElementField): ArrayField<ElementField> {
return { kind: 'array', element };
array<ElementField extends ComponentSchema>(
element: ElementField,
opts?: { preview?: TypedPreviewComponent<ArrayField<ElementField>> }
): ArrayField<ElementField> {
return { kind: 'array', element, preview: opts?.preview as PreviewComponent };
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export type NonChildFieldComponentSchema =
| RelationshipField<boolean>
| ArrayField<ComponentSchema>;

function isNonChildFieldPreviewProps(
export function isNonChildFieldPreviewProps(
props: GenericPreviewProps<ComponentSchema, unknown>
): props is GenericPreviewProps<NonChildFieldComponentSchema, unknown> {
return props.schema.kind !== 'child';
Expand Down
Loading

0 comments on commit 16806ac

Please sign in to comment.