Compare commits

...

10 Commits

Author SHA1 Message Date
Jarrod Flesch
b920012630 Merge branch 'beta' into feat/beta/select-groups 2024-07-19 16:39:12 -04:00
Jarrod Flesch
073ece8674 chore: updates docs for select field 2024-07-19 16:38:58 -04:00
Jarrod Flesch
383539ab0a chore: fix graphql select enums 2024-07-19 14:13:26 -04:00
Jarrod Flesch
f18079c907 chore: fixes graphql enum change 2024-07-19 13:48:13 -04:00
Jarrod Flesch
241b51c17b chore: consolidate code, simplify SelectInput component 2024-07-19 13:35:05 -04:00
Jarrod Flesch
7b06e94684 chore: fixes build issues 2024-07-19 10:56:27 -04:00
Jarrod Flesch
e027c5aa36 chore: fixes display issues 2024-07-19 10:05:37 -04:00
Jarrod Flesch
fc25f797e1 type and schema updates for select groups 2024-07-19 08:38:36 -04:00
Jarrod Flesch
aceee7b66c chore: adjusts graphql and json schema generation files 2024-07-19 01:02:56 -04:00
Jarrod Flesch
9cef308ef4 chore: wip select option groups 2024-07-18 17:02:05 -04:00
27 changed files with 981 additions and 350 deletions

View File

@@ -102,6 +102,88 @@ export const ExampleCollection: CollectionConfig = {
}
```
## Option types
A Select field config accepts three different types of options:
### String options
```ts
{
name: 'example',
type: 'select',
options: ['Option 1', 'Option 2'],
}
```
### Object options
```ts
{
name: 'example',
type: 'select',
options: [
{
label: 'Option 1',
value: 'option_1',
},
{
label: 'Option 2',
value: 'option_2',
},
],
}
```
### Grouped options
```ts
{
name: 'example',
type: 'select',
options: [
{
label: 'Group 1',
options: [
{
label: 'Option 1',
value: 'option_1',
},
],
},
{
label: 'Group 2',
options: [
{
label: 'Option 2',
value: 'option_2',
},
],
},
],
}
```
### Option labels
If your payload config has [localization](/docs/configuration/localization) enabled, option labels can also be localized by providing an object with keys for each language:
```ts
{
name: 'example',
type: 'select',
options: [
{
label: {
en: 'Option 1',
es: 'Opción 1',
},
value: 'option_1',
},
],
}
```
## Customization
The Select field UI component can be customized by providing a custom React component to the `components` object in the Base config.
@@ -109,21 +191,12 @@ The Select field UI component can be customized by providing a custom React comp
```ts
export const CustomSelectField: Field = {
name: 'customSelectField',
type: 'select', // or 'text' if you have dynamic options
// use 'type: text' if you have options
// that will be populated and therefore are dynamic
type: 'text',
admin: {
components: {
Field: CustomSelectComponent({
options: [
{
label: 'Option 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
],
}),
Field: CustomSelectComponent,
},
},
}
@@ -132,43 +205,29 @@ export const CustomSelectField: Field = {
You can import the existing Select component directly from Payload, then extend and customize it as needed.
```ts
import * as React from 'react';
import { SelectInput, useField } from 'payload/components/forms';
import { useAuth } from 'payload/components/utilities';
import * as React from 'react'
import { SelectField } from '@payloadcms/ui'
type CustomSelectProps = {
path: string;
options: {
label: string;
value: string;
}[];
}
export const presidentOptions = [
{
label: 'George Washington',
value: '1',
},
{
label: 'John Adams',
value: '2',
},
]
export const CustomSelectComponent: React.FC<CustomSelectProps> = ({ path, options }) => {
const { value, setValue } = useField<string>({ path })
const { user } = useAuth()
const adjustedOptions = options.filter((option) => {
/*
A common use case for a custom select
is to show different options based on
the current user's role.
*/
return option;
});
export const CustomSelectComponent = async (args) => {
// you would likely want to fetch this data from an API
// this is just an example of async data "fetching"
const fetchedPresidents = await Promise.resolve(presidentOptions)
return (
<div>
<label className="field-label">
Custom Select
</label>
<SelectInput
path={path}
name={path}
options={adjustedOptions}
value={value}
onChange={(e) => setValue(e.value)}
/>
<label className="field-label">Custom Select</label>
<SelectField path={args.path} options={fetchedPresidents} />
</div>
)
}

View File

@@ -34,6 +34,7 @@ import {
fieldAffectsData,
fieldIsLocalized,
fieldIsPresentationalOnly,
flattenOptionValues,
tabHasName,
} from 'payload/shared'
@@ -500,10 +501,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
const baseSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: String,
enum: field.options.map((option) => {
if (typeof option === 'object') return option.value
return option
}),
enum: flattenOptionValues(field.options),
}
if (buildSchemaOptions.draftsEnabled || !field.required) {

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
import type { Field, TabAsField } from 'payload'
import type { Field, Option, TabAsField } from 'payload'
import { relations } from 'drizzle-orm'
import {
@@ -20,7 +19,7 @@ import {
varchar,
} from 'drizzle-orm/pg-core'
import { InvalidConfiguration } from 'payload'
import { fieldAffectsData, optionIsObject } from 'payload/shared'
import { fieldAffectsData, flattenOptionValues } from 'payload/shared'
import toSnakeCase from 'to-snake-case'
import type { GenericColumns, IDType, PostgresAdapter } from '../types.js'
@@ -234,13 +233,7 @@ export const traverseFields = ({
adapter.enums[enumName] = pgEnum(
enumName,
field.options.map((option) => {
if (optionIsObject(option)) {
return option.value
}
return option
}) as [string, ...string[]],
flattenOptionValues(field.options) as [string, ...string[]],
)
if (field.type === 'select' && field.hasMany) {

View File

@@ -12,6 +12,7 @@ import type {
GroupField,
JSONField,
NumberField,
Option,
PointField,
RadioField,
RelationshipField,
@@ -37,9 +38,10 @@ import {
GraphQLString,
} from 'graphql'
import { flattenTopLevelFields, toWords } from 'payload'
import { fieldAffectsData, optionIsObject, tabHasName } from 'payload/shared'
import { fieldAffectsData, tabHasName } from 'payload/shared'
import { GraphQLJSON } from '../packages/graphql-type-json/index.js'
import { buildOptionEnums } from '../utilities/buildOptionEnums.js'
import combineParentName from '../utilities/combineParentName.js'
import formatName from '../utilities/formatName.js'
import { groupOrTabHasRequiredSubfield } from '../utilities/groupOrTabHasRequiredSubfield.js'
@@ -234,23 +236,7 @@ export function buildMutationInputType({
const formattedName = `${combineParentName(parentName, field.name)}_MutationInput`
let type: GraphQLType = new GraphQLEnumType({
name: formattedName,
values: field.options.reduce((values, option) => {
if (optionIsObject(option)) {
return {
...values,
[formatName(option.value)]: {
value: option.value,
},
}
}
return {
...values,
[formatName(option)]: {
value: option,
},
}
}, {}),
values: buildOptionEnums(field.options),
})
type = field.hasMany ? new GraphQLList(type) : type

View File

@@ -11,11 +11,10 @@ import {
GraphQLString,
} from 'graphql'
import { DateTimeResolver, EmailAddressResolver } from 'graphql-scalars'
import { optionIsObject } from 'payload/shared'
import { GraphQLJSON } from '../packages/graphql-type-json/index.js'
import { buildOptionEnums } from '../utilities/buildOptionEnums.js'
import combineParentName from '../utilities/combineParentName.js'
import formatName from '../utilities/formatName.js'
import operators from './operators.js'
type staticTypes =
@@ -147,23 +146,7 @@ const defaults: DefaultsType = {
type: (field: RadioField, parentName): GraphQLType =>
new GraphQLEnumType({
name: `${combineParentName(parentName, field.name)}_Input`,
values: field.options.reduce((values, option) => {
if (optionIsObject(option)) {
return {
...values,
[formatName(option.value)]: {
value: option.value,
},
}
}
return {
...values,
[formatName(option)]: {
value: option,
},
}
}, {}),
values: buildOptionEnums(field.options),
}),
})),
],
@@ -191,23 +174,7 @@ const defaults: DefaultsType = {
type: (field: SelectField, parentName): GraphQLType =>
new GraphQLEnumType({
name: `${combineParentName(parentName, field.name)}_Input`,
values: field.options.reduce((values, option) => {
if (optionIsObject(option)) {
return {
...values,
[formatName(option.value)]: {
value: option.value,
},
}
}
return {
...values,
[formatName(option)]: {
value: option,
},
}
}, {}),
values: buildOptionEnums(field.options),
}),
})),
],

View File

@@ -0,0 +1,29 @@
import type { GraphQLEnumValueConfig, ThunkObjMap } from 'graphql'
import type { Option } from 'payload'
import formatName from './formatName.js'
export function buildOptionEnums(options: Option[]): ThunkObjMap<GraphQLEnumValueConfig> {
return options.reduce((values, option) => {
if (typeof option === 'string') {
return {
...values,
[formatName(option)]: {
value: option,
},
}
} else if ('options' in option) {
return {
...values,
...buildOptionEnums(option.options),
}
} else {
return {
...values,
[formatName(option.value)]: {
value: option.value,
},
}
}
}, {})
}

View File

@@ -1,25 +1,33 @@
import type { RadioField, SelectField } from 'payload'
import type { Option, RadioField, SelectField } from 'payload'
import formatName from './formatName.js'
const formatOptions = (field: RadioField | SelectField) => {
return field.options.reduce((values, option) => {
if (typeof option === 'object') {
const buildOptions = (options: Option[]) =>
options.reduce((values, option) => {
if (typeof option === 'string') {
return {
...values,
[formatName(option)]: {
value: option,
},
}
} else if ('options' in option) {
return {
...values,
...buildOptions(option.options),
}
}
return {
...values,
[formatName(option.value)]: {
value: option.value,
},
}
}
}, {})
return {
...values,
[formatName(option)]: {
value: option,
},
}
}, {})
return buildOptions(field.options)
}
export default formatOptions

View File

@@ -1,7 +1,6 @@
import type { I18nClient } from '@payloadcms/translations'
import type { SelectFieldProps } from '@payloadcms/ui'
import type { MappedField } from '@payloadcms/ui/utilities/buildComponentMap'
import type { OptionObject, SelectField } from 'payload'
import { getSelectedOptionLabels } from '@payloadcms/ui'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
@@ -15,37 +14,6 @@ import './index.scss'
const baseClass = 'select-diff'
const getOptionsToRender = (
value: string,
options: SelectField['options'],
hasMany: boolean,
): (OptionObject | string)[] | OptionObject | string => {
if (hasMany && Array.isArray(value)) {
return value.map(
(val) =>
options.find((option) => (typeof option === 'string' ? option : option.value) === val) ||
String(val),
)
}
return (
options.find((option) => (typeof option === 'string' ? option : option.value) === value) ||
String(value)
)
}
const getTranslatedOptions = (
options: (OptionObject | string)[] | OptionObject | string,
i18n: I18nClient,
): string => {
if (Array.isArray(options)) {
return options
.map((option) => (typeof option === 'string' ? option : getTranslation(option.label, i18n)))
.join(', ')
}
return typeof options === 'string' ? options : getTranslation(options.label, i18n)
}
const Select: React.FC<
{
field: MappedField & SelectFieldProps
@@ -59,12 +27,20 @@ const Select: React.FC<
const comparisonToRender =
typeof comparison !== 'undefined'
? getTranslatedOptions(getOptionsToRender(comparison, options, field.hasMany), i18n)
? getSelectedOptionLabels({
selectedOptions: Array.isArray(comparison) ? comparison : [comparison],
options,
i18n,
}).join(', ')
: placeholder
const versionToRender =
typeof version !== 'undefined'
? getTranslatedOptions(getOptionsToRender(version, options, field.hasMany), i18n)
? getSelectedOptionLabels({
selectedOptions: Array.isArray(version) ? version : [version],
options,
i18n,
}).join(', ')
: placeholder
return (

View File

@@ -12,11 +12,14 @@ export {
fieldSupportsMany,
optionIsObject,
optionIsValue,
optionsAreGrouped,
optionsAreObjects,
tabHasName,
valueIsValueWithRelation,
} from '../fields/config/types.js'
export { flattenOptionValues } from '../fields/flattenOptionValues.js'
export * from '../fields/validations.js'
export { validOperators } from '../types/constants.js'

View File

@@ -230,6 +230,29 @@ export const select = baseField.keys({
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
value: joi.string().required().allow(''),
}),
joi.object({
label: joi
.alternatives()
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
options: joi
.array()
.min(1)
.items(
joi.alternatives().try(
joi.string(),
joi.object({
label: joi
.alternatives()
.try(
joi.func(),
joi.string(),
joi.object().pattern(joi.string(), [joi.string()]),
),
value: joi.string().required().allow(''),
}),
),
),
}),
),
)
.required(),

View File

@@ -203,7 +203,12 @@ export type OptionObject = {
value: string
}
export type Option = OptionObject | string
export type OptionGroup = {
label: LabelFunction | Record<string, string> | string
options: (OptionObject | string)[]
}
export type Option = OptionGroup | OptionObject | string
export type RadioOption = OptionObject | string
export interface FieldBase {
access?: {
@@ -721,7 +726,7 @@ export type RadioField = {
* Customize the DB enum name
*/
enumName?: DBIdentifierName
options: Option[]
options: RadioOption[]
type: 'radio'
} & FieldBase
@@ -889,6 +894,13 @@ export function optionsAreObjects(options: Option[]): options is OptionObject[]
return Array.isArray(options) && typeof options?.[0] === 'object'
}
export function optionsAreGrouped(options: Option[]): options is OptionGroup[] {
return (
Array.isArray(options) &&
options.some((option) => option && typeof option === 'object' && 'options' in option)
)
}
export function optionIsValue(option: Option): option is string {
return typeof option === 'string'
}

View File

@@ -0,0 +1,15 @@
import { Option } from './config/types.js'
export function flattenOptionValues(options: Option[]): string[] {
return options.reduce<string[]>((acc, option) => {
if (typeof option === 'string') {
acc.push(option)
} else if ('options' in option) {
acc.push(...flattenOptionValues(option.options))
} else {
acc.push(option.value)
}
return acc
}, [])
}

View File

@@ -616,25 +616,26 @@ export const select: Validate<unknown, unknown, unknown, SelectField> = (
value,
{ hasMany, options, req: { t }, required },
) => {
if (
Array.isArray(value) &&
value.some(
(input) =>
!options.some(
(option) => option === input || (typeof option !== 'string' && option?.value === input),
),
function isInvalidSelection(values: string[], optionsToMatch: SelectField['options'] = options) {
// if any of the values are not found in options, the selection is invalid
return values.some(
(valueToValidate: string) =>
!optionsToMatch.some((option) => {
if (typeof option === 'string') {
return option === valueToValidate
} else if ('options' in option) {
return !isInvalidSelection([valueToValidate], option.options)
} else {
return option.value === valueToValidate
}
}),
)
) {
return t('validation:invalidSelection')
}
if (
typeof value === 'string' &&
!options.some(
(option) => option === value || (typeof option !== 'string' && option.value === value),
)
) {
return t('validation:invalidSelection')
if (Array.isArray(value) || typeof value === 'string') {
if (isInvalidSelection(Array.isArray(value) ? value : [value])) {
return t('validation:invalidSelection')
}
}
if (

View File

@@ -902,9 +902,11 @@ export type {
NumberField,
Option,
OptionObject,
OptionGroup,
PointField,
PolymorphicRelationshipField,
RadioField,
RadioOption,
RelationshipField,
RelationshipValue,
RichTextField,

View File

@@ -14,6 +14,7 @@ import { fieldAffectsData, tabHasName } from '../fields/config/types.js'
import { deepCopyObject } from './deepCopyObject.js'
import { toWords } from './formatLabels.js'
import { getCollectionIDFieldTypes } from './getCollectionIDFieldTypes.js'
import { flattenOptionValues } from '../fields/flattenOptionValues.js'
const fieldIsRequired = (field: Field) => {
const isConditional = Boolean(field?.admin && field?.admin?.condition)
@@ -40,16 +41,6 @@ const fieldIsRequired = (field: Field) => {
return false
}
function buildOptionEnums(options: Option[]): string[] {
return options.map((option) => {
if (typeof option === 'object' && 'value' in option) {
return option.value
}
return option
})
}
function generateEntitySchemas(
entities: (SanitizedCollectionConfig | SanitizedGlobalConfig)[],
): JSONSchema4 {
@@ -247,14 +238,14 @@ export function fieldsToJSONSchema(
case 'radio': {
fieldSchema = {
type: withNullableJSONSchemaType('string', isRequired),
enum: buildOptionEnums(field.options),
enum: flattenOptionValues(field.options),
}
break
}
case 'select': {
const optionEnums = buildOptionEnums(field.options)
const optionEnums = flattenOptionValues(field.options)
if (field.hasMany) {
fieldSchema = {

View File

@@ -1,11 +1,10 @@
'use client'
import type { CellComponentProps, DefaultCellComponentProps, OptionObject } from 'payload'
import type { CellComponentProps, DefaultCellComponentProps } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { optionsAreObjects } from 'payload/shared'
import React from 'react'
import { useTranslation } from '../../../../../providers/Translation/index.js'
import { getSelectedOptionLabels } from '../../../../../fields/Select/utils.js'
export interface SelectCellProps extends DefaultCellComponentProps<any> {
options: CellComponentProps['options']
@@ -14,25 +13,13 @@ export interface SelectCellProps extends DefaultCellComponentProps<any> {
export const SelectCell: React.FC<SelectCellProps> = ({ cellData, options }) => {
const { i18n } = useTranslation()
const findLabel = (items: string[]) =>
items
.map((i) => {
const found = (options as OptionObject[]).filter((f: OptionObject) => f.value === i)?.[0]
?.label
return getTranslation(found, i18n)
})
.join(', ')
let content = ''
if (optionsAreObjects(options)) {
content = Array.isArray(cellData)
? findLabel(cellData) // hasMany
: findLabel([cellData])
} else {
content = Array.isArray(cellData)
? cellData.join(', ') // hasMany
: cellData
}
const content = [
...getSelectedOptionLabels({
selectedOptions: Array.isArray(cellData) ? cellData : [cellData],
options,
i18n,
}),
].join(', ')
return <span>{content}</span>
}

View File

@@ -1,25 +1,11 @@
'use client'
import type { Option, OptionObject } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
import type { Props } from './types.js'
import { useTranslation } from '../../../../providers/Translation/index.js'
import { ReactSelect } from '../../../ReactSelect/index.js'
const formatOptions = (options: Option[]): OptionObject[] =>
options.map((option) => {
if (typeof option === 'object' && (option.value || option.value === '')) {
return option
}
return {
label: option,
value: option,
} as OptionObject
})
import { buildReactSelectOptions, buildReactSelectValues } from '../../../../fields/Select/utils.js'
export const Select: React.FC<Props> = ({
disabled,
@@ -29,26 +15,21 @@ export const Select: React.FC<Props> = ({
value,
}) => {
const { i18n } = useTranslation()
const [options, setOptions] = React.useState(formatOptions(optionsFromProps))
const options = React.useMemo(
() =>
buildReactSelectOptions({
options: optionsFromProps,
i18n,
}),
[optionsFromProps],
)
const isMulti = ['in', 'not_in'].includes(operator)
let valueToRender
if (isMulti && Array.isArray(value)) {
valueToRender = value.map((val) => {
const matchingOption = options.find((option) => option.value === val)
return {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : val,
value: matchingOption?.value ?? val,
}
})
} else if (value) {
const matchingOption = options.find((option) => option.value === value)
valueToRender = {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : value,
value: matchingOption?.value ?? value,
}
}
const values = buildReactSelectValues({
options: optionsFromProps,
values: typeof value === 'string' ? [value] : value,
i18n,
})
const onSelect = React.useCallback(
(selectedOption) => {
@@ -70,10 +51,6 @@ export const Select: React.FC<Props> = ({
[isMulti, onChange],
)
React.useEffect(() => {
setOptions(formatOptions(optionsFromProps))
}, [optionsFromProps])
React.useEffect(() => {
if (!isMulti && Array.isArray(value)) {
onChange(value[0])
@@ -85,8 +62,8 @@ export const Select: React.FC<Props> = ({
disabled={disabled}
isMulti={isMulti}
onChange={onSelect}
options={options.map((option) => ({ ...option, label: getTranslation(option.label, i18n) }))}
value={valueToRender}
options={options}
value={values}
/>
)
}

View File

@@ -105,6 +105,12 @@ export { RelationshipField } from '../../fields/Relationship/index.js'
export { RichTextField } from '../../fields/RichText/index.js'
export { RowField } from '../../fields/Row/index.js'
export { SelectField, type SelectFieldProps, SelectInput } from '../../fields/Select/index.js'
export {
getSelectedOptionLabels,
buildReactSelectOptions,
buildReactSelectValues,
sanitizeServerSideOptions,
} from '../../fields/Select/utils.js'
export { TabsField, type TabsFieldProps } from '../../fields/Tabs/index.js'
export { TextField, TextInput } from '../../fields/Text/index.js'
export type { TextFieldProps, TextInputProps } from '../../fields/Text/index.js'

View File

@@ -1,5 +1,5 @@
'use client'
import type { Option } from 'payload'
import type { RadioOption } from 'payload'
import { optionIsObject } from 'payload/shared'
import React, { useCallback } from 'react'
@@ -24,7 +24,7 @@ export type RadioFieldProps = {
layout?: 'horizontal' | 'vertical'
name?: string
onChange?: OnChange
options?: Option[]
options?: RadioOption[]
path?: string
value?: string
width?: string

View File

@@ -1,7 +1,6 @@
'use client'
import type { OptionObject } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
import type { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js'
@@ -17,9 +16,9 @@ import './index.scss'
export type SelectInputProps = {
onChange?: ReactSelectAdapterProps['onChange']
options?: OptionObject[]
options?: ReactSelectAdapterProps['options']
showError?: boolean
value?: string | string[]
value?: ReactSelectAdapterProps['value']
} & Omit<
SelectFieldProps,
| 'custom'
@@ -62,26 +61,6 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
width,
} = props
const { i18n } = useTranslation()
let valueToRender
if (hasMany && Array.isArray(value)) {
valueToRender = value.map((val) => {
const matchingOption = options.find((option) => option.value === val)
return {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : val,
value: matchingOption?.value ?? val,
}
})
} else if (value) {
const matchingOption = options.find((option) => option.value === value)
valueToRender = {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : value,
value: matchingOption?.value ?? value,
}
}
return (
<div
className={[
@@ -114,12 +93,9 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
isMulti={hasMany}
isSortable={isSortable}
onChange={onChange}
options={options.map((option) => ({
...option,
label: getTranslation(option.label, i18n),
}))}
options={options}
showError={showError}
value={valueToRender as OptionObject}
value={value}
/>
{AfterInput}
</div>

View File

@@ -11,6 +11,8 @@ import { useFieldProps } from '../../forms/FieldPropsProvider/index.js'
import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js'
import { SelectInput } from './Input.js'
import { buildReactSelectOptions, buildReactSelectValues } from './utils.js'
import { useTranslation } from '@payloadcms/ui'
export type SelectFieldProps = {
hasMany?: boolean
@@ -24,18 +26,6 @@ export type SelectFieldProps = {
width?: string
} & FormFieldBase
const formatOptions = (options: Option[]): OptionObject[] =>
options.map((option) => {
if (typeof option === 'object' && (option.value || option.value === '')) {
return option
}
return {
label: option,
value: option,
} as OptionObject
})
const _SelectField: React.FC<SelectFieldProps> = (props) => {
const {
name,
@@ -62,7 +52,16 @@ const _SelectField: React.FC<SelectFieldProps> = (props) => {
width,
} = props
const options = React.useMemo(() => formatOptions(optionsFromProps), [optionsFromProps])
const { i18n } = useTranslation()
const options = React.useMemo(
() =>
buildReactSelectOptions({
options: optionsFromProps,
i18n,
}),
[optionsFromProps],
)
const memoizedValidate: ClientValidate = useCallback(
(value, validationOptions) => {
@@ -82,7 +81,7 @@ const _SelectField: React.FC<SelectFieldProps> = (props) => {
const disabled = readOnlyFromProps || readOnlyFromContext || formProcessing || formInitializing
const onChange: ReactSelectAdapterProps['onChange'] = useCallback(
(selectedOption: OptionObject | OptionObject[]) => {
(selectedOption: { value: string; label: string }) => {
if (!disabled) {
let newValue: string | string[] = null
if (selectedOption && hasMany) {
@@ -105,6 +104,12 @@ const _SelectField: React.FC<SelectFieldProps> = (props) => {
[disabled, hasMany, setValue, onChangeFromProps],
)
const values = buildReactSelectValues({
options: optionsFromProps,
i18n,
values: Array.isArray(value) ? value : [value],
})
return (
<SelectInput
AfterInput={AfterInput}
@@ -128,7 +133,7 @@ const _SelectField: React.FC<SelectFieldProps> = (props) => {
required={required}
showError={showError}
style={style}
value={value as string | string[]}
value={values}
width={width}
/>
)

View File

@@ -0,0 +1,151 @@
import type { Option } from 'payload'
import { getTranslation, I18nClient, TFunction } from '@payloadcms/translations'
import { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js'
export function getSelectedOptionLabels({
selectedOptions,
options,
i18n,
}: {
selectedOptions: string[]
options: Option[]
i18n: I18nClient
}): string[] {
const selectLabels = selectedOptions.reduce((acc, selectedOption) => {
options.forEach((option) => {
if (typeof option === 'string') {
if (option === selectedOption) acc.push(getTranslation(option, i18n))
} else if ('options' in option) {
acc = [
...acc,
...getSelectedOptionLabels({ selectedOptions, options: option.options, i18n }),
]
} else if (option.value === selectedOption) {
acc.push(getTranslation(option.label, i18n))
}
})
return acc
}, [])
return [...new Set(selectLabels)]
}
export const buildReactSelectOptions = ({
options,
i18n,
}: {
options: Option[]
i18n: I18nClient
}): ReactSelectAdapterProps['options'] => {
return options.map((option) => {
if (typeof option === 'string') {
return {
label: getTranslation(option, i18n),
value: option,
}
} else if ('options' in option) {
return {
label: getTranslation(option.label, i18n),
options: buildReactSelectOptions({
options: option.options,
i18n,
}),
} as any
} else {
return {
label: getTranslation(option.label, i18n),
value: option.value,
}
}
})
}
function getReactSelectValues({
values,
options,
i18n,
}: {
values: string[]
options: Option[]
i18n: I18nClient
}): ReactSelectAdapterProps['value'] {
return values.reduce((acc, selectedOption) => {
options.forEach((option) => {
if (typeof option === 'string') {
if (option === selectedOption)
acc.push({
label: getTranslation(option, i18n),
value: option,
})
} else if ('options' in option) {
const subOptions = getReactSelectValues({
values: [selectedOption],
options: option.options,
i18n,
})
if (subOptions.length) {
acc = [...acc, ...(Array.isArray(subOptions) ? subOptions : [subOptions])]
}
} else if (option.value === selectedOption) {
acc.push({
label: getTranslation(option.label, i18n),
value: option.value,
})
}
})
return acc
}, [])
}
export const buildReactSelectValues = ({
options,
values,
i18n,
}: {
options: Option[]
values: string[]
i18n: I18nClient
}): ReactSelectAdapterProps['value'] => {
const nonUniqueValues = getReactSelectValues({ values, options, i18n })
return [...new Set(Array.isArray(nonUniqueValues) ? nonUniqueValues : [nonUniqueValues])]
}
export const sanitizeServerSideOptions = ({
options,
t,
}: {
options: Option[]
t: TFunction
}): Option[] => {
function createOptionLabel(option: Option) {
if (typeof option === 'object' && typeof option.label === 'function') {
return option.label({ t })
}
return typeof option === 'string' ? option : option.label
}
function generateOptions(options: Option[]) {
return options.map((option) => {
if (typeof option === 'string') {
return {
label: option,
value: option,
}
} else if ('options' in option) {
return {
label: createOptionLabel(option),
options: generateOptions(option.options),
}
} else {
return {
label: createOptionLabel(option),
value: option.value,
}
}
})
}
return generateOptions(options)
}

View File

@@ -48,6 +48,7 @@ import type {
import { FieldDescription } from '../../../exports/client/index.js'
// eslint-disable-next-line payload/no-imports-from-exports-dir
import { HiddenField } from '../../../exports/client/index.js'
import { sanitizeServerSideOptions } from '../../../fields/Select/utils.js'
function generateFieldPath(parentPath, name) {
let tabPath = parentPath || ''
@@ -231,28 +232,13 @@ export const mapFields = (args: {
let fieldComponentProps: FieldComponentProps
let fieldOptions: Option[]
if ('options' in field) {
fieldOptions = field.options.map((option) => {
if (typeof option === 'object' && typeof option.label === 'function') {
return {
label: option.label({ t }),
value: option.value,
}
}
return option
})
}
const cellComponentProps: CellComponentProps = {
name: 'name' in field ? field.name : undefined,
fieldType: field.type,
isFieldAffectingData,
label: labelProps?.label || undefined,
labels: 'labels' in field ? field.labels : undefined,
options: 'options' in field ? fieldOptions : undefined,
options: undefined,
relationTo: 'relationTo' in field ? field.relationTo : undefined,
schemaPath: path,
}
@@ -557,20 +543,31 @@ export const mapFields = (args: {
break
}
case 'radio': {
const radioOptions = field.options.map((option) => {
if (typeof option === 'object' && typeof option.label === 'function') {
return {
label: option.label({ t }),
value: option.value,
}
}
return option
})
const radioField: RadioFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
layout: field.admin?.layout,
options: fieldOptions,
options: radioOptions,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
cellComponentProps.options = fieldOptions
cellComponentProps.options = radioOptions
fieldComponentProps = radioField
break
}
@@ -741,6 +738,8 @@ export const mapFields = (args: {
break
}
case 'select': {
const selectOptions = sanitizeServerSideOptions({ options: field.options, t })
const selectField: SelectFieldProps = {
...baseFieldProps,
name: field.name,
@@ -748,14 +747,14 @@ export const mapFields = (args: {
disabled: field.admin?.disabled,
hasMany: field.hasMany,
isClearable: field.admin?.isClearable,
options: fieldOptions,
options: selectOptions,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
cellComponentProps.options = fieldOptions
cellComponentProps.options = selectOptions
fieldComponentProps = selectField
break
}

View File

@@ -1,16 +1,26 @@
import React from 'react'
import * as React from 'react'
import { SelectInput } from '@payloadcms/ui'
import type { Post } from '../../payload-types.js'
export const presidentOptions = [
{
label: 'Thomas Jefferson',
value: '3',
},
{
label: 'James Madison',
value: '4',
},
]
export const MyComponent: React.FC = () => {
const test: Post = {
id: 'string',
createdAt: 'string',
text: 'string',
updatedAt: 'string',
}
export const MyComponent = async (args) => {
// you would likely want to fetch this data from an API
// this is just an example of async data "fetching"
const fetchedPresidents = await Promise.resolve(presidentOptions)
console.log({ test })
return <p>hi</p>
return (
<div>
<label className="field-label">Custom Select</label>
<SelectInput hasMany={true} path={args.path} options={fetchedPresidents} />
</div>
)
}

View File

@@ -2,6 +2,8 @@ import type { CollectionConfig } from 'payload'
import { BlocksFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
// import { MyComponent } from './MyComponent.js'
export const postsSlug = 'posts'
export const PostsCollection: CollectionConfig = {
@@ -40,6 +42,44 @@ export const PostsCollection: CollectionConfig = {
],
}),
},
{
name: 'presidents',
type: 'select',
hasMany: false,
// admin: {
// components: {
// Field: MyComponent,
// },
// },
options: [
{
label: '1700s',
options: [
{
label: 'George Washington',
value: '1',
},
{
label: 'John Adams',
value: '2',
},
],
},
{
label: '1800s',
options: [
{
label: 'Thomas Jefferson',
value: '3',
},
{
label: 'James Madison',
value: '4',
},
],
},
],
},
// {
// type: 'row',
// fields: [],

View File

@@ -16,6 +16,9 @@ export interface Config {
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
db: {
defaultIDType: string;
};
globals: {
menu: Menu;
'custom-ts': CustomT;
@@ -30,13 +33,16 @@ export interface UserAuthOperations {
email: string;
};
login: {
password: string;
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -75,6 +81,7 @@ export interface Post {
};
[k: string]: unknown;
} | null;
presidents?: ('1' | '2' | '3' | '4') | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;

View File

@@ -12,17 +12,13 @@ type Query {
meUser: usersMe
initializedUser: Boolean
PayloadPreference(id: String!, draft: Boolean): PayloadPreference
PayloadPreferences(
draft: Boolean
where: PayloadPreference_where
limit: Int
page: Int
sort: String
): PayloadPreferences
PayloadPreferences(draft: Boolean, where: PayloadPreference_where, limit: Int, page: Int, sort: String): PayloadPreferences
countPayloadPreferences(draft: Boolean, where: PayloadPreference_where): countPayloadPreferences
docAccessPayloadPreference(id: String!): payload_preferencesDocAccess
Menu(draft: Boolean): Menu
docAccessMenu: menuDocAccess
CustomT(draft: Boolean): CustomT
docAccessCustomT: custom_tsDocAccess
Access: Access
}
@@ -31,6 +27,7 @@ type Post {
text: String
richText(depth: Int): JSON
richText2(depth: Int): JSON
presidents: [Post_presidents!]
updatedAt: DateTime
createdAt: DateTime
_status: Post__status
@@ -39,8 +36,15 @@ type Post {
"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar JSON
@specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
enum Post_presidents {
_1
_2
_3
_4
JamesMonroe
}
"""
A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.
@@ -70,6 +74,7 @@ input Post_where {
text: Post_text_operator
richText: Post_richText_operator
richText2: Post_richText2_operator
presidents: Post_presidents_operator
updatedAt: Post_updatedAt_operator
createdAt: Post_createdAt_operator
_status: Post__status_operator
@@ -105,6 +110,23 @@ input Post_richText2_operator {
exists: Boolean
}
input Post_presidents_operator {
equals: Post_presidents_Input
not_equals: Post_presidents_Input
in: [Post_presidents_Input]
not_in: [Post_presidents_Input]
all: [Post_presidents_Input]
exists: Boolean
}
enum Post_presidents_Input {
_1
_2
_3
_4
JamesMonroe
}
input Post_updatedAt_operator {
equals: DateTime
not_equals: DateTime
@@ -156,6 +178,7 @@ input Post_where_and {
text: Post_text_operator
richText: Post_richText_operator
richText2: Post_richText2_operator
presidents: Post_presidents_operator
updatedAt: Post_updatedAt_operator
createdAt: Post_createdAt_operator
_status: Post__status_operator
@@ -168,6 +191,7 @@ input Post_where_or {
text: Post_text_operator
richText: Post_richText_operator
richText2: Post_richText2_operator
presidents: Post_presidents_operator
updatedAt: Post_updatedAt_operator
createdAt: Post_createdAt_operator
_status: Post__status_operator
@@ -193,6 +217,7 @@ type PostsDocAccessFields {
text: PostsDocAccessFields_text
richText: PostsDocAccessFields_richText
richText2: PostsDocAccessFields_richText2
presidents: PostsDocAccessFields_presidents
updatedAt: PostsDocAccessFields_updatedAt
createdAt: PostsDocAccessFields_createdAt
_status: PostsDocAccessFields__status
@@ -267,6 +292,29 @@ type PostsDocAccessFields_richText2_Delete {
permission: Boolean!
}
type PostsDocAccessFields_presidents {
create: PostsDocAccessFields_presidents_Create
read: PostsDocAccessFields_presidents_Read
update: PostsDocAccessFields_presidents_Update
delete: PostsDocAccessFields_presidents_Delete
}
type PostsDocAccessFields_presidents_Create {
permission: Boolean!
}
type PostsDocAccessFields_presidents_Read {
permission: Boolean!
}
type PostsDocAccessFields_presidents_Update {
permission: Boolean!
}
type PostsDocAccessFields_presidents_Delete {
permission: Boolean!
}
type PostsDocAccessFields_updatedAt {
create: PostsDocAccessFields_updatedAt_Create
read: PostsDocAccessFields_updatedAt_Read
@@ -344,8 +392,7 @@ type PostsCreateDocAccess {
"""
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar JSONObject
@specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
scalar JSONObject @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
type PostsReadDocAccess {
permission: Boolean!
@@ -380,11 +427,20 @@ type PostVersion_Version {
text: String
richText(depth: Int): JSON
richText2(depth: Int): JSON
presidents: [PostVersion_Version_presidents!]
updatedAt: DateTime
createdAt: DateTime
_status: PostVersion_Version__status
}
enum PostVersion_Version_presidents {
_1
_2
_3
_4
JamesMonroe
}
enum PostVersion_Version__status {
draft
published
@@ -409,6 +465,7 @@ input versionsPost_where {
version__text: versionsPost_version__text_operator
version__richText: versionsPost_version__richText_operator
version__richText2: versionsPost_version__richText2_operator
version__presidents: versionsPost_version__presidents_operator
version__updatedAt: versionsPost_version__updatedAt_operator
version__createdAt: versionsPost_version__createdAt_operator
version___status: versionsPost_version___status_operator
@@ -456,6 +513,23 @@ input versionsPost_version__richText2_operator {
exists: Boolean
}
input versionsPost_version__presidents_operator {
equals: versionsPost_version__presidents_Input
not_equals: versionsPost_version__presidents_Input
in: [versionsPost_version__presidents_Input]
not_in: [versionsPost_version__presidents_Input]
all: [versionsPost_version__presidents_Input]
exists: Boolean
}
enum versionsPost_version__presidents_Input {
_1
_2
_3
_4
JamesMonroe
}
input versionsPost_version__updatedAt_operator {
equals: DateTime
not_equals: DateTime
@@ -536,6 +610,7 @@ input versionsPost_where_and {
version__text: versionsPost_version__text_operator
version__richText: versionsPost_version__richText_operator
version__richText2: versionsPost_version__richText2_operator
version__presidents: versionsPost_version__presidents_operator
version__updatedAt: versionsPost_version__updatedAt_operator
version__createdAt: versionsPost_version__createdAt_operator
version___status: versionsPost_version___status_operator
@@ -552,6 +627,7 @@ input versionsPost_where_or {
version__text: versionsPost_version__text_operator
version__richText: versionsPost_version__richText_operator
version__richText2: versionsPost_version__richText2_operator
version__presidents: versionsPost_version__presidents_operator
version__updatedAt: versionsPost_version__updatedAt_operator
version__createdAt: versionsPost_version__createdAt_operator
version___status: versionsPost_version___status_operator
@@ -580,8 +656,7 @@ type User {
"""
A field whose value conforms to the standard internet email address format as specified in HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.
"""
scalar EmailAddress
@specifiedBy(url: "https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address")
scalar EmailAddress @specifiedBy(url: "https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address")
type Users {
docs: [User]
@@ -807,6 +882,7 @@ type UsersUnlockDocAccess {
type usersMe {
collection: String
exp: Int
strategy: String
token: String
user: User
}
@@ -1193,12 +1269,160 @@ type MenuUpdateDocAccess {
where: JSONObject
}
type CustomT {
custom: String
withDefinitionsUsage: String
json: JSON!
updatedAt: DateTime
createdAt: DateTime
}
type custom_tsDocAccess {
fields: CustomTsDocAccessFields
read: CustomTsReadDocAccess
update: CustomTsUpdateDocAccess
}
type CustomTsDocAccessFields {
custom: CustomTsDocAccessFields_custom
withDefinitionsUsage: CustomTsDocAccessFields_withDefinitionsUsage
json: CustomTsDocAccessFields_json
updatedAt: CustomTsDocAccessFields_updatedAt
createdAt: CustomTsDocAccessFields_createdAt
}
type CustomTsDocAccessFields_custom {
create: CustomTsDocAccessFields_custom_Create
read: CustomTsDocAccessFields_custom_Read
update: CustomTsDocAccessFields_custom_Update
delete: CustomTsDocAccessFields_custom_Delete
}
type CustomTsDocAccessFields_custom_Create {
permission: Boolean!
}
type CustomTsDocAccessFields_custom_Read {
permission: Boolean!
}
type CustomTsDocAccessFields_custom_Update {
permission: Boolean!
}
type CustomTsDocAccessFields_custom_Delete {
permission: Boolean!
}
type CustomTsDocAccessFields_withDefinitionsUsage {
create: CustomTsDocAccessFields_withDefinitionsUsage_Create
read: CustomTsDocAccessFields_withDefinitionsUsage_Read
update: CustomTsDocAccessFields_withDefinitionsUsage_Update
delete: CustomTsDocAccessFields_withDefinitionsUsage_Delete
}
type CustomTsDocAccessFields_withDefinitionsUsage_Create {
permission: Boolean!
}
type CustomTsDocAccessFields_withDefinitionsUsage_Read {
permission: Boolean!
}
type CustomTsDocAccessFields_withDefinitionsUsage_Update {
permission: Boolean!
}
type CustomTsDocAccessFields_withDefinitionsUsage_Delete {
permission: Boolean!
}
type CustomTsDocAccessFields_json {
create: CustomTsDocAccessFields_json_Create
read: CustomTsDocAccessFields_json_Read
update: CustomTsDocAccessFields_json_Update
delete: CustomTsDocAccessFields_json_Delete
}
type CustomTsDocAccessFields_json_Create {
permission: Boolean!
}
type CustomTsDocAccessFields_json_Read {
permission: Boolean!
}
type CustomTsDocAccessFields_json_Update {
permission: Boolean!
}
type CustomTsDocAccessFields_json_Delete {
permission: Boolean!
}
type CustomTsDocAccessFields_updatedAt {
create: CustomTsDocAccessFields_updatedAt_Create
read: CustomTsDocAccessFields_updatedAt_Read
update: CustomTsDocAccessFields_updatedAt_Update
delete: CustomTsDocAccessFields_updatedAt_Delete
}
type CustomTsDocAccessFields_updatedAt_Create {
permission: Boolean!
}
type CustomTsDocAccessFields_updatedAt_Read {
permission: Boolean!
}
type CustomTsDocAccessFields_updatedAt_Update {
permission: Boolean!
}
type CustomTsDocAccessFields_updatedAt_Delete {
permission: Boolean!
}
type CustomTsDocAccessFields_createdAt {
create: CustomTsDocAccessFields_createdAt_Create
read: CustomTsDocAccessFields_createdAt_Read
update: CustomTsDocAccessFields_createdAt_Update
delete: CustomTsDocAccessFields_createdAt_Delete
}
type CustomTsDocAccessFields_createdAt_Create {
permission: Boolean!
}
type CustomTsDocAccessFields_createdAt_Read {
permission: Boolean!
}
type CustomTsDocAccessFields_createdAt_Update {
permission: Boolean!
}
type CustomTsDocAccessFields_createdAt_Delete {
permission: Boolean!
}
type CustomTsReadDocAccess {
permission: Boolean!
where: JSONObject
}
type CustomTsUpdateDocAccess {
permission: Boolean!
where: JSONObject
}
type Access {
canAccessAdmin: Boolean!
posts: postsAccess
users: usersAccess
payload_preferences: payload_preferencesAccess
menu: menuAccess
custom_ts: custom_tsAccess
}
type postsAccess {
@@ -1214,6 +1438,7 @@ type PostsFields {
text: PostsFields_text
richText: PostsFields_richText
richText2: PostsFields_richText2
presidents: PostsFields_presidents
updatedAt: PostsFields_updatedAt
createdAt: PostsFields_createdAt
_status: PostsFields__status
@@ -1288,6 +1513,29 @@ type PostsFields_richText2_Delete {
permission: Boolean!
}
type PostsFields_presidents {
create: PostsFields_presidents_Create
read: PostsFields_presidents_Read
update: PostsFields_presidents_Update
delete: PostsFields_presidents_Delete
}
type PostsFields_presidents_Create {
permission: Boolean!
}
type PostsFields_presidents_Read {
permission: Boolean!
}
type PostsFields_presidents_Update {
permission: Boolean!
}
type PostsFields_presidents_Delete {
permission: Boolean!
}
type PostsFields_updatedAt {
create: PostsFields_updatedAt_Create
read: PostsFields_updatedAt_Read
@@ -1757,6 +2005,145 @@ type MenuUpdateAccess {
where: JSONObject
}
type custom_tsAccess {
fields: CustomTsFields
read: CustomTsReadAccess
update: CustomTsUpdateAccess
}
type CustomTsFields {
custom: CustomTsFields_custom
withDefinitionsUsage: CustomTsFields_withDefinitionsUsage
json: CustomTsFields_json
updatedAt: CustomTsFields_updatedAt
createdAt: CustomTsFields_createdAt
}
type CustomTsFields_custom {
create: CustomTsFields_custom_Create
read: CustomTsFields_custom_Read
update: CustomTsFields_custom_Update
delete: CustomTsFields_custom_Delete
}
type CustomTsFields_custom_Create {
permission: Boolean!
}
type CustomTsFields_custom_Read {
permission: Boolean!
}
type CustomTsFields_custom_Update {
permission: Boolean!
}
type CustomTsFields_custom_Delete {
permission: Boolean!
}
type CustomTsFields_withDefinitionsUsage {
create: CustomTsFields_withDefinitionsUsage_Create
read: CustomTsFields_withDefinitionsUsage_Read
update: CustomTsFields_withDefinitionsUsage_Update
delete: CustomTsFields_withDefinitionsUsage_Delete
}
type CustomTsFields_withDefinitionsUsage_Create {
permission: Boolean!
}
type CustomTsFields_withDefinitionsUsage_Read {
permission: Boolean!
}
type CustomTsFields_withDefinitionsUsage_Update {
permission: Boolean!
}
type CustomTsFields_withDefinitionsUsage_Delete {
permission: Boolean!
}
type CustomTsFields_json {
create: CustomTsFields_json_Create
read: CustomTsFields_json_Read
update: CustomTsFields_json_Update
delete: CustomTsFields_json_Delete
}
type CustomTsFields_json_Create {
permission: Boolean!
}
type CustomTsFields_json_Read {
permission: Boolean!
}
type CustomTsFields_json_Update {
permission: Boolean!
}
type CustomTsFields_json_Delete {
permission: Boolean!
}
type CustomTsFields_updatedAt {
create: CustomTsFields_updatedAt_Create
read: CustomTsFields_updatedAt_Read
update: CustomTsFields_updatedAt_Update
delete: CustomTsFields_updatedAt_Delete
}
type CustomTsFields_updatedAt_Create {
permission: Boolean!
}
type CustomTsFields_updatedAt_Read {
permission: Boolean!
}
type CustomTsFields_updatedAt_Update {
permission: Boolean!
}
type CustomTsFields_updatedAt_Delete {
permission: Boolean!
}
type CustomTsFields_createdAt {
create: CustomTsFields_createdAt_Create
read: CustomTsFields_createdAt_Read
update: CustomTsFields_createdAt_Update
delete: CustomTsFields_createdAt_Delete
}
type CustomTsFields_createdAt_Create {
permission: Boolean!
}
type CustomTsFields_createdAt_Read {
permission: Boolean!
}
type CustomTsFields_createdAt_Update {
permission: Boolean!
}
type CustomTsFields_createdAt_Delete {
permission: Boolean!
}
type CustomTsReadAccess {
permission: Boolean!
where: JSONObject
}
type CustomTsUpdateAccess {
permission: Boolean!
where: JSONObject
}
type Mutation {
createPost(data: mutationPostInput!, draft: Boolean): Post
updatePost(id: String!, autosave: Boolean, data: mutationPostUpdateInput!, draft: Boolean): Post
@@ -1766,34 +2153,39 @@ type Mutation {
createUser(data: mutationUserInput!, draft: Boolean): User
updateUser(id: String!, autosave: Boolean, data: mutationUserUpdateInput!, draft: Boolean): User
deleteUser(id: String!): User
refreshTokenUser(token: String): usersRefreshedUser
refreshTokenUser: usersRefreshedUser
logoutUser: String
unlockUser(email: String!): Boolean!
loginUser(email: String, password: String): usersLoginResult
forgotPasswordUser(disableEmail: Boolean, email: String!, expiration: Int): Boolean!
loginUser(email: String!, password: String): usersLoginResult
forgotPasswordUser(disableEmail: Boolean, expiration: Int, email: String!): Boolean!
resetPasswordUser(password: String, token: String): usersResetPassword
verifyEmailUser(token: String): Boolean
createPayloadPreference(data: mutationPayloadPreferenceInput!, draft: Boolean): PayloadPreference
updatePayloadPreference(
id: String!
autosave: Boolean
data: mutationPayloadPreferenceUpdateInput!
draft: Boolean
): PayloadPreference
updatePayloadPreference(id: String!, autosave: Boolean, data: mutationPayloadPreferenceUpdateInput!, draft: Boolean): PayloadPreference
deletePayloadPreference(id: String!): PayloadPreference
duplicatePayloadPreference(id: String!): PayloadPreference
updateMenu(data: mutationMenuInput!, draft: Boolean): Menu
updateCustomT(data: mutationCustomTInput!, draft: Boolean): CustomT
}
input mutationPostInput {
text: String
richText: JSON
richText2: JSON
presidents: [Post_presidents_MutationInput]
updatedAt: String
createdAt: String
_status: Post__status_MutationInput
}
enum Post_presidents_MutationInput {
_1
_2
_3
_4
JamesMonroe
}
enum Post__status_MutationInput {
draft
published
@@ -1803,11 +2195,20 @@ input mutationPostUpdateInput {
text: String
richText: JSON
richText2: JSON
presidents: [PostUpdate_presidents_MutationInput]
updatedAt: String
createdAt: String
_status: PostUpdate__status_MutationInput
}
enum PostUpdate_presidents_MutationInput {
_1
_2
_3
_4
JamesMonroe
}
enum PostUpdate__status_MutationInput {
draft
published
@@ -1842,6 +2243,7 @@ input mutationUserUpdateInput {
type usersRefreshedUser {
exp: Int
refreshedToken: String
strategy: String
user: usersJWT
}
@@ -1900,3 +2302,11 @@ input mutationMenuInput {
updatedAt: String
createdAt: String
}
input mutationCustomTInput {
custom: String
withDefinitionsUsage: String
json: JSON!
updatedAt: String
createdAt: String
}